Compare commits

..

206 commits
1.4.2 ... main

Author SHA1 Message Date
Kenta Kubo
5d7446ca80
Merge pull request #186 from kkebo/dependabot/github_actions/github/codeql-action-4.32.6
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
2026-03-09 12:13:09 +09:00
dependabot[bot]
866aa6bd90
ci: bump github/codeql-action from 4.32.4 to 4.32.6
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.4 to 4.32.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](89a39a4e59...0d579ffd05)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 02:55:55 +00:00
Kenta Kubo
5ab07664d3
Merge pull request #185 from kkebo/dependabot/github_actions/github/codeql-action-4.32.4
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
ci: bump github/codeql-action from 4.32.3 to 4.32.4
2026-02-23 12:25:45 +09:00
dependabot[bot]
38f3de8942
ci: bump github/codeql-action from 4.32.3 to 4.32.4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.3 to 4.32.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](9e907b5e64...89a39a4e59)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 02:56:47 +00:00
Kenta Kubo
32433ed572
Merge pull request #184 from kkebo/dependabot/github_actions/github/codeql-action-4.32.3
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
2026-02-16 12:56:41 +09:00
dependabot[bot]
e28aa08cd7
ci: bump github/codeql-action from 4.32.2 to 4.32.3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.2 to 4.32.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](45cbd0c69e...9e907b5e64)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 02:56:21 +00:00
Kenta Kubo
57d1ac26cb
Merge pull request #182 from kkebo/dependabot/github_actions/github/codeql-action-4.32.2
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
2026-02-09 13:00:05 +09:00
dependabot[bot]
656c1cdf55
ci: bump github/codeql-action from 4.32.0 to 4.32.2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.0 to 4.32.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](b20883b0cd...45cbd0c69e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-09 02:56:57 +00:00
Kenta Kubo
a95157e7da
Merge pull request #179 from kkebo/dependabot/github_actions/github/codeql-action-4.32.0
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
2026-02-02 13:26:02 +09:00
dependabot[bot]
aeaa69df28
ci: bump github/codeql-action from 4.31.11 to 4.32.0
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.11 to 4.32.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](19b2f06db2...b20883b0cd)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-02 02:56:24 +00:00
Kenta Kubo
6bd8827961
Merge pull request #178 from kkebo/dependabot/github_actions/actions/checkout-6.0.2
Some checks failed
ci / lint (push) Has been cancelled
ci / yamllint (push) Has been cancelled
CodeQL Advanced / Analyze (swift) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
2026-01-26 12:36:30 +09:00
dependabot[bot]
959492944b
ci: bump actions/checkout from 6.0.1 to 6.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8e8c483db8...de0fac2e45)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:16:17 +00:00
Kenta Kubo
27a2c48bf2
Merge pull request #177 from kkebo/dependabot/github_actions/github/codeql-action-4.31.11 2026-01-26 12:15:22 +09:00
dependabot[bot]
37a53acf0e
ci: bump github/codeql-action from 4.31.10 to 4.31.11
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.10 to 4.31.11.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](cdefb33c0f...19b2f06db2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 02:57:41 +00:00
Kenta Kubo
0a2c6d4531
Merge pull request #176 from kkebo/dependabot/github_actions/github/codeql-action-4.31.10 2026-01-19 12:54:42 +09:00
dependabot[bot]
bcda3a46e1
ci: bump github/codeql-action from 4.31.9 to 4.31.10
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.9 to 4.31.10.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](5d4e8d1aca...cdefb33c0f)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:08:55 +00:00
Kenta Kubo
0c1ca9b3d8
Merge pull request #175 from kkebo/happy-new-year-2026 2026-01-08 00:20:22 +09:00
Kenta Kubo
e2c37d9417
chore: happy new year! 2026-01-04 21:36:30 +09:00
Kenta Kubo
e6af1fa26f
Merge pull request #174 from kkebo/bump-version-to-1.6.2
chore: bump version to 1.6.2
2025-12-28 21:56:54 +09:00
Kenta Kubo
7ddca0724b
chore: bump version to 1.6.2 2025-12-28 21:40:58 +09:00
Kenta Kubo
7c419fcccf
Merge pull request #173 from kkebo/show-instructions-on-iphone 2025-12-28 21:38:46 +09:00
Kenta Kubo
29adf98043
feat: revive "Instructions" row when the app is not active and its layout is a single column 2025-12-28 04:28:39 +09:00
Kenta Kubo
c8015a381d
Merge pull request #171 from kkebo/dependabot/github_actions/github/codeql-action-4.31.9 2025-12-22 13:40:30 +09:00
dependabot[bot]
07f5247e5b
ci: bump github/codeql-action from 4.31.8 to 4.31.9
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.8 to 4.31.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b168cd394...5d4e8d1aca)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 02:20:19 +00:00
Kenta Kubo
2c5fac8da4
Merge pull request #170 from kkebo/dependabot/github_actions/github/codeql-action-4.31.8 2025-12-15 12:51:25 +09:00
dependabot[bot]
153e51bf66
ci: bump github/codeql-action from 4.31.7 to 4.31.8
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.31.7 to 4.31.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](cf1bb45a27...1b168cd394)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 02:22:53 +00:00
Kenta Kubo
d0e1425f42
Merge pull request #169 from kkebo/dependabot/github_actions/actions/checkout-6.0.1 2025-12-08 13:15:16 +09:00
dependabot[bot]
b4414f4845
ci: bump actions/checkout from 6.0.0 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...8e8c483db84b4bee98b60c0593521ed34d9990e8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 02:23:48 +00:00
Kenta Kubo
0ea4f2743e
Merge pull request #168 from kkebo/codeql 2025-12-08 00:26:23 +09:00
Kenta Kubo
54860148b4
ci: add codeql.yml
Updated CodeQL workflow to modify build modes and action versions.
2025-12-07 23:09:38 +09:00
Kenta Kubo
65bc5ba39a
Merge pull request #167 from kkebo/refactor-package.swift
refactor: refactor Package.swift
2025-11-28 02:50:57 +09:00
Kenta Kubo
6a19c36e83
refactor: refactor Package.swift 2025-11-28 02:47:51 +09:00
Kenta Kubo
c9f1bb633a
Merge pull request #166 from kkebo/dependabot/github_actions/actions/checkout-6.0.0 2025-11-24 14:40:12 +09:00
dependabot[bot]
032ec025ac
ci: bump actions/checkout from 5.0.0 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...1af3b93b68)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 02:28:52 +00:00
Kenta Kubo
0bb8fdad23
Merge pull request #164 from kkebo/set-euo-pipefail 2025-10-18 03:02:38 +09:00
Kenta Kubo
b79afcfd3e
ci: set -euo pipefail 2025-10-18 02:30:27 +09:00
Kenta Kubo
e3878d4d3a
Merge pull request #162 from kkebo/bump-version-1.6.1
chore: bump version to 1.6.1
2025-10-14 18:48:10 +09:00
Kenta Kubo
f1fe1c06f8
chore: bump version to 1.6.1 2025-10-14 18:42:39 +09:00
Kenta Kubo
9fe66c693a
Merge pull request #160 from kkebo/fix-typos
fix: fix typos in log messages
2025-10-14 18:37:02 +09:00
Kenta Kubo
f41b114632 fix: fix typos in log messages 2025-10-14 18:31:36 +09:00
Kenta Kubo
e4775b3d44
Merge pull request #161 from kkebo/fix-missing-sidebar-issue
fix: fix missing sidebar issue
2025-10-14 18:31:25 +09:00
Kenta Kubo
21cb297b3b
fix: fix missing sidebar issue
fixes #158
2025-10-14 18:15:44 +09:00
Kenta Kubo
87a00a8c3e
Merge pull request #157 from kkebo/gh-actions-explicit-permissions 2025-10-12 12:10:56 +09:00
Kenta Kubo
e8426f1465
ci: restrict permissions 2025-10-11 22:18:27 +09:00
Kenta Kubo
fbc9514922
Merge pull request #151 from kkebo/sha-pinning 2025-10-01 01:41:57 +09:00
Kenta Kubo
f587bdf722
ci: pin GitHub Actions dependencies to commit SHA
This is a defense against supply chain attacks. Moreover, GitHub
[recommends SHA
pinning](https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/) these days. Even with this change, dependabot should continue to function.
2025-10-01 00:59:08 +09:00
Kenta Kubo
92090b4fcd
Merge pull request #150 from kkebo/update-swift-format 2025-09-19 01:22:12 +09:00
Kenta Kubo
e102e47f50 ci: bump Swift to 6.2 2025-09-19 00:53:54 +09:00
Kenta Kubo
08101f9766 chore: update .swift-format to match the default in Swift 6.2 2025-09-19 00:53:54 +09:00
Kenta Kubo
e5c25ad31d
Merge pull request #149 from kkebo/127-deactivate-on-editing
fix: deactivate the active server when it's modified
2025-09-08 23:10:23 +09:00
Kenta Kubo
87c6069581
fix: deactivate the active server when it's modified
fixes #127
2025-09-08 23:02:58 +09:00
Kenta Kubo
5516a3f1e0
Merge pull request #148 from kkebo/bump-version-1.6.0
chore: bump version to 1.6.0
2025-09-07 23:24:49 +09:00
Kenta Kubo
8d6c8d6b5e
chore: bump version to 1.6.0 2025-09-07 23:17:36 +09:00
Kenta Kubo
7e4e5e6f37
Merge pull request #147 from kkebo/fix-donebutton-color-on-mac-catalyst
fix: fix `EditButton` color on Mac Catalyst
2025-09-07 22:40:30 +09:00
Kenta Kubo
8e47f93b16 fix: fix EditButton color on Mac Catalyst 2025-09-07 22:33:17 +09:00
Kenta Kubo
4f34aad38a
Merge pull request #146 from kkebo/139-fix-editbutton-on-mac-catalyst
fix: add a workaround for FB20137863
2025-09-07 22:31:50 +09:00
Kenta Kubo
cbff3bcedb
fix: add a workaround for FB20137863
fixes #139
2025-09-07 21:36:10 +09:00
Kenta Kubo
f3f050d1c6
Merge pull request #145 from kkebo/liquid-glass-icon 2025-09-07 19:17:33 +09:00
Kenta Kubo
b29d266a37
chore: Liquid Glass App Icon 2025-09-07 03:24:58 +09:00
Kenta Kubo
5110ae80dd
Merge pull request #144 from kkebo/single-size-icon
chore: use single size app icon
2025-09-07 01:40:25 +09:00
Kenta Kubo
f6dd718180
chore: use single size app icon 2025-09-07 01:36:14 +09:00
Kenta Kubo
f63745cb4a
Merge pull request #142 from kkebo/fix-warn 2025-09-06 22:28:07 +09:00
Kenta Kubo
e5bcbd1ad2 fix: fix a warning
The warning message was "'init(tag:selection:destination🏷️)' was deprecated in iOS 16.0: use NavigationLink(value🏷️), or navigationDestination(isPresented:destination:), inside a NavigationStack or NavigationSplitView".
2025-09-06 22:22:35 +09:00
Kenta Kubo
b9ac25eb4c
Merge pull request #143 from kkebo/140-revert-commenting-out 2025-09-06 22:22:25 +09:00
Kenta Kubo
bfa62b8f88 fix: uncomment logger.debugs that were commented out by mistake
fixes #140
2025-09-06 22:03:06 +09:00
Kenta Kubo
f3feb893e0
Merge pull request #141 from kkebo/older-sdks
fix: support older iOS SDKs again
2025-09-06 22:01:58 +09:00
Kenta Kubo
a8d2123154
fix: support older iOS SDKs again 2025-09-06 21:54:49 +09:00
Kenta Kubo
e963a83588
Merge pull request #136 from kkebo/116-improve-help-ui-ux
fix: improve help UI/UX
2025-09-06 03:21:28 +09:00
Kenta Kubo
71617ff998 fix: improve help UI/UX 2025-09-06 03:16:24 +09:00
Kenta Kubo
47c4f34d32
Merge pull request #137 from kkebo/refactor-with-switch-expressions 2025-09-05 04:32:55 +09:00
Kenta Kubo
b941334934
refactor: refactor with switch expressions 2025-09-05 03:18:41 +09:00
Kenta Kubo
994f7dcaa5
Merge pull request #135 from kkebo/revert-128-127-deactivate-on-editing 2025-09-05 01:15:25 +09:00
Kenta Kubo
4eca25530c
Revert "fix: deactivate the active server when it's modified" 2025-09-05 01:10:47 +09:00
Kenta Kubo
055e40d2cb
Merge pull request #115 from kkebo/fix-apply-with-excluded-domains
fix: Apply with excluded domains
2025-09-05 00:52:35 +09:00
Kenta Kubo
bf98ce1860
fix: improve text field UX in ExcludedDomainsView 2025-09-03 23:15:40 +09:00
Kenta Kubo
3d5397fa7e refactor: Split ExcludedDomainsView into a separate file 2025-09-03 23:13:18 +09:00
Kenta Kubo
031f9408a0 fix: Apply with excluded domains 2025-09-03 23:13:18 +09:00
Kenta Kubo
832c3b8405
Merge pull request #134 from kkebo/show-server-name-on-system-ui
feat: show the server name in the system UI
2025-09-03 23:10:21 +09:00
Kenta Kubo
d5534d6e34 feat: show the server name in the system UI
I changed the display name from "DNSecure" to the current server name. It is used in the system's settings UI.
2025-09-03 23:03:06 +09:00
Kenta Kubo
91b4c755f2
Merge pull request #133 from kkebo/add-hover-effect-to-add-buttons
fix: add hover effects to "Add" buttons
2025-09-03 23:02:54 +09:00
Kenta Kubo
dd5eda3793
fix: add hover effects to "Add" buttons 2025-09-03 22:56:58 +09:00
Kenta Kubo
0f72faaede
Merge pull request #132 from kkebo/update-project-file
chore: update Xcode project to the latest settings
2025-09-03 22:55:38 +09:00
Kenta Kubo
8dd0f2d50e
chore: update Xcode project to the latest settings 2025-09-03 22:46:05 +09:00
Kenta Kubo
b0715bc56f
Merge pull request #131 from kkebo/update-project-settings
chore: update project settings
2025-09-03 21:26:23 +09:00
Kenta Kubo
9033c43d84
chore: update project settings
"Editor" > "Validate Settings..."
2025-09-03 21:11:09 +09:00
Kenta Kubo
a5cb503490
Merge pull request #130 from kkebo/fix-warns
fix: fix warnings
2025-09-03 21:02:38 +09:00
Kenta Kubo
b047b9042c
fix: fix warnings 2025-09-03 20:56:44 +09:00
Kenta Kubo
17ce2d56a0
Merge pull request #128 from kkebo/127-deactivate-on-editing 2025-09-03 20:42:34 +09:00
Kenta Kubo
fcec5ef24f
fix: deactivate the active server when it's modified
fixes #127
2025-09-03 20:38:51 +09:00
Kenta Kubo
3313b1d9dc
Merge pull request #129 from kkebo/fix-typo 2025-09-03 19:52:15 +09:00
Kenta Kubo
e42f7dd7fa fix: fix typo
"On Demand Rules" -> "On-Demand Rules"
2025-09-03 19:47:55 +09:00
Kenta Kubo
219df8ccc1
Merge pull request #126 from kkebo/improve-textfield-ux-in-ruleview 2025-09-03 19:47:38 +09:00
Kenta Kubo
e7710e9928
fix: improve text field UX in RuleView 2025-09-03 04:05:34 +09:00
Kenta Kubo
547b548525
Merge pull request #120 from kkebo/119-remove-as-is-action 2025-09-03 02:34:27 +09:00
Kenta Kubo
3591b9840a
revert: As is 2025-09-03 02:28:26 +09:00
Kenta Kubo
06c5a5fc65
test: migrate to Testing from XCTest 2025-09-03 00:39:24 +09:00
Kenta Kubo
f6aaedfdb8
chore: bump Swift language mode to 6 in test targets 2025-09-03 00:37:07 +09:00
Kenta Kubo
5c96c63613
Merge pull request #121 from kkebo/refine-ruleview
feat: refine `RuleView`
2025-09-03 00:35:16 +09:00
Kenta Kubo
caf33b9ce1
fix: fix iOS 16 issue 2025-09-03 00:29:20 +09:00
Kenta Kubo
ba48c966a1
fix: fix build failure 2025-09-02 23:36:57 +09:00
Kenta Kubo
103dc02919
chore: fix project file 2025-09-02 23:07:04 +09:00
Kenta Kubo
3d2c9d6019
fix: fix build failure 2025-09-02 23:06:10 +09:00
Kenta Kubo
0cec7bbf9c
refactor: fix lint warnings 2025-09-02 22:36:42 +09:00
Kenta Kubo
67a8db045d
refactor: use LabeledContent on newer OSes 2025-09-02 22:36:42 +09:00
Kenta Kubo
7433d55450
fix: fix build failure by converting groups into folders 2025-09-02 22:36:42 +09:00
Kenta Kubo
17f950ecba
fix: fix build failure 2025-09-02 22:36:42 +09:00
Kenta Kubo
2c3f8a4b2f
refactor: divide each Form into its own separate file 2025-09-02 22:36:42 +09:00
Kenta Kubo
6faca0f72b
test: fix RuleView preview 2025-09-02 22:36:41 +09:00
Kenta Kubo
55b541403e
feat: refine RuleView 2025-09-02 22:36:37 +09:00
Kenta Kubo
febbf73029
Merge pull request #125 from kkebo/fix-clear-button
fix: fix the bug where the clear button on `LazyTextField` doesn't work
2025-09-02 22:31:36 +09:00
Kenta Kubo
f2db5aa19b fix: fix the bug where the clear button on LazyTextField doesn't work 2025-09-02 22:28:04 +09:00
Kenta Kubo
4bbda1c4cc
Merge pull request #122 from kkebo/117-redesign-restorationview
feat: redesign `RestorationView`
2025-09-02 22:26:25 +09:00
Kenta Kubo
01d5a48a63 refactor: refactor Button 2025-09-02 22:20:37 +09:00
Kenta Kubo
13fbfc849a feat: redesign RestorationView
resolves #117
2025-09-02 22:20:37 +09:00
Kenta Kubo
2b1277347d
Merge pull request #124 from kkebo/refactor-buttons-and-menus
refactor: refactor `Button`s and `Menu`s
2025-09-02 22:18:44 +09:00
Kenta Kubo
4bea9abec9
refactor: refactor Buttons and Menus 2025-09-02 22:14:49 +09:00
Kenta Kubo
41dfa8b900
Merge pull request #123 from kkebo/naming-convention 2025-09-02 22:09:11 +09:00
Kenta Kubo
60f2036303
refactor: change naming convention
`somethingIsPresented` -> `isSomethingPresented`
2025-09-02 02:59:30 +09:00
dependabot[bot]
9a555a97a5
Merge pull request #114 from kkebo/dependabot/github_actions/actions/checkout-5 2025-08-18 12:22:49 +00:00
dependabot[bot]
cdd65efb28
ci: bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 12:17:34 +00:00
Kenta Kubo
1302d2b374
chore: fix CODEOWNERS 2025-08-18 21:13:49 +09:00
Kenta Kubo
f282f8088a
ci: introduce dependabot 2025-07-18 01:30:00 +09:00
Kenta Kubo
e0db86ee54
Merge pull request #112 from kkebo/reset-build-number 2025-05-22 01:49:24 +09:00
Kenta Kubo
307843e01a
chore: reset the build number to 1
We are now using Xcode Cloud, so no longer need to manage the build
number manually.
2025-05-20 00:24:59 +09:00
Kenta Kubo
e1a4acc360
Merge pull request #111 from kkebo/bump-version-1.5.1
chore: bump version to 1.5.1
2025-05-19 23:42:46 +09:00
Kenta Kubo
302d034d53
chore: bump version to 1.5.1 2025-05-19 23:37:37 +09:00
Kenta Kubo
f3725d67f3
Merge pull request #110 from kkebo/revert-109-bump-version-1.5.1-25
Revert "chore: bump version to 1.5.1 (25)"
2025-05-19 23:31:46 +09:00
Kenta Kubo
5c7051fec3
Revert "chore: bump version to 1.5.1 (25)" 2025-05-19 23:30:08 +09:00
Kenta Kubo
7e0d0b858d
Merge pull request #109 from kkebo/bump-version-1.5.1-25
chore: bump version to 1.5.1 (25)
2025-05-19 23:12:22 +09:00
Kenta Kubo
1d4cbbeb73
chore: bump version to 1.5.1 (25) 2025-05-19 01:08:04 +09:00
Kenta Kubo
d2502718db
Merge pull request #107 from kkebo/realtime-status-update 2025-05-08 00:14:01 +09:00
Kenta Kubo
1adf591e1c
feat: update the activation status in real time
The activation status on the bottom bar is now updated in real time. So
we don't need the refresh button anymore.
2025-05-05 02:25:11 +09:00
Kenta Kubo
89c35e840e
Merge pull request #106 from kkebo/refine-layouts 2025-05-05 02:13:11 +09:00
Kenta Kubo
3b29799e70
fix: refine layout of the bottom bar on the sidebar 2025-05-05 01:25:27 +09:00
Kenta Kubo
9997724409
fix: refine layout of HowToActivateView 2025-05-05 01:25:27 +09:00
Kenta Kubo
4579a7cb75
Merge pull request #105 from kkebo/fix-errors-on-swift-playground 2025-05-02 23:08:41 +09:00
Kenta Kubo
e2994b78c4
fix: fix errors when running on Swift Playground
`NEDNSSettingsManager` APIs are not supported on Swift Playground's App
Preview.
2025-04-30 01:33:04 +09:00
Kenta Kubo
cab0456355
Merge pull request #102 from kkebo/ci-arm64 2025-04-20 02:01:28 +09:00
Kenta Kubo
48c8612f2c
ci: use GitHub-hosted arm64 Linux runners
https://github.com/orgs/community/discussions/148648
2025-04-16 00:06:18 +09:00
Kenta Kubo
d92b2d1999
Merge pull request #101 from kkebo/add-ci 2025-04-09 08:57:46 +09:00
Kenta Kubo
dcb6962205
ci: add CI 2025-04-06 05:53:45 +09:00
Kenta Kubo
83533600ad
chore: add .yamllint.yml 2025-04-06 05:53:32 +09:00
Kenta Kubo
5faedd45d9
style: swift-format 2025-04-06 05:51:39 +09:00
Kenta Kubo
3e97eec20b
chore: add .swift-format 2025-04-06 05:51:29 +09:00
Kenta Kubo
8f7ba01404
Merge pull request #97 from kkebo/swift-6-language-mode 2025-03-22 03:24:57 +09:00
Kenta Kubo
76de00f801
build: apply recommended Xcode settings 2025-03-09 01:57:16 +09:00
Kenta Kubo
c2f2953b4e
fix: fix retroactive conformance warnings 2025-03-09 01:56:23 +09:00
Kenta Kubo
56cd5b15ed
build: enable Swift 6 language mode on Xcode 2025-03-09 01:55:50 +09:00
Kenta Kubo
ebb9cf90a7
fix: fix errors in Swift 6 language mode 2025-03-09 01:41:10 +09:00
Kenta Kubo
5d42bc0833
revert: fix: avoid errors in Swift Playgrounds
Swift Playground can't import the PlaygroundSupport module anymore when
opening an App project.
2025-03-09 01:40:27 +09:00
Kenta Kubo
8ef17080a8
build: bump swift-tools-version to 6.0
It also enables Swift 6 language mode on Swift Playground.
2025-03-09 01:38:00 +09:00
Kenta Kubo
03f8802e3b
chore: happy new year! 2025-01-20 02:12:51 +09:00
Kenta Kubo
e32b754cc5
Merge pull request #95 from kkebo/fix-readme
Fix App Store badges in README.md
2024-11-13 02:02:06 +09:00
Kenta Kubo
cfdb06a06f
Fix App Store badges in README.md 2024-11-13 02:01:03 +09:00
Kenta Kubo
53e26ff328
Merge pull request #94 from kkebo/gitattributes 2024-10-22 00:03:24 +09:00
Kenta Kubo
f82bc5acc1
chore: add .gitattributes
- Use LF
- Use `union` as a merge driver for pbxproj
- Colorize `Package.resolved` and `.swift-format`
2024-10-12 02:33:37 +09:00
Kenta Kubo
851d565f76
chore: bump version to 1.5.0 (24) 2024-07-18 01:01:05 +09:00
Kenta Kubo
eac8267d4e
chore: bump version to 1.5.0 (23) 2024-07-17 03:14:52 +09:00
Kenta Kubo
2a5c61fb6b
Merge pull request #84 from kkebo/restore-from-presets 2024-07-17 03:11:02 +09:00
Kenta Kubo
b18c5b60dc
feat: add "Restore from Presets" button
This feature allows you to select and add any server from the presets. Without it, when a new server like Freifunk München DNS was added to the preset, existing users would have to reinstall the app in order to add it to their server list. This change resolves such a problem.
2024-07-17 02:34:38 +09:00
Kenta Kubo
9e4a505697
build: fix iOS Deployment Target of DNSecureTests 2024-07-17 01:21:36 +09:00
Kenta Kubo
853fb0df13
chore: bump version to 1.5.0 (22) 2024-07-17 00:56:29 +09:00
Kenta Kubo
143b2845b6
Merge pull request #82 from T0biii/patch-1
add FFMUC DNS Server (doh & dot)
2024-07-17 00:44:44 +09:00
Tobias
7f97b6cf48
fix duplicate IPs 2024-06-28 09:52:58 +02:00
Tobias
a68dccf9d1
add FFMUC DNS Server 2024-06-28 09:43:33 +02:00
Kenta Kubo
1da5ecef36
Merge pull request #81 from kkebo/use-preview-macros 2024-06-25 00:03:25 +09:00
Kenta Kubo
0ad8d115bc refactor: use #Preview macros
The `#Preview` macro is a simple replacement for the `PreviewProvider`
protocol.
2024-06-24 02:07:01 +09:00
Kenta Kubo
d873148619
Merge pull request #80 from kkebo/replace-deprecated-apis 2024-06-24 02:06:49 +09:00
Kenta Kubo
fcc41a2bcd
refactor: replace deprecated APIs
I replaced deprecated SwiftUI APIs with new ones.
2024-06-06 02:17:32 +09:00
Kenta Kubo
bbab4c95b7
chore: bump version to 1.4.5 (21) 2024-01-14 00:54:59 +09:00
Kenta Kubo
b3e49d5b36
Merge pull request #77 from kkk669/issues/72 2024-01-14 00:53:20 +09:00
Kenta Kubo
716cd5653d
fix: fix UUID mismatch issue (#72)
fixes #72
2024-01-12 22:58:01 +09:00
Kenta Kubo
e4753bce4f
fix: avoid errors in Swift Playgrounds 2024-01-12 22:01:51 +09:00
Kenta Kubo
c0d42ee1c5
refactor: make it work in Swift Playgrounds again 2024-01-12 21:47:58 +09:00
Kenta Kubo
fe7cfcfcea
docs: add a TestFlight link for macOS 2024-01-07 13:04:26 +09:00
Kenta Kubo
de988fb3b9
chore: bump version to 1.4.4 (20) 2024-01-07 12:54:51 +09:00
Kenta Kubo
3d54ac1ae8
build: update Xcode build settings 2024-01-07 12:54:51 +09:00
Kenta Kubo
0df3ebc797
fix: fix a compiler error 2024-01-07 12:52:32 +09:00
Kenta Kubo
2b39c60dfc
docs: add instructions for macOS 13 or later to README.md 2024-01-07 12:46:28 +09:00
Kenta Kubo
6641bf6a25
Merge pull request #76 from kkk669/update-instructions
feat: add instructions for macOS 13 or later (#75)
2024-01-07 12:37:30 +09:00
Kenta Kubo
6c081a2876
refactor: refactor image in text 2024-01-07 12:21:25 +09:00
Kenta Kubo
de654c15c0
feat: add instructions for macOS 13 or later 2024-01-07 12:18:18 +09:00
Kenta Kubo
01daf552da
refactor: rename image sets 2024-01-07 11:51:31 +09:00
Kenta Kubo
bd048a0132
refactor: use generated constants instead of string literals 2024-01-07 11:22:18 +09:00
Kenta Kubo
66a219c146
docs: happy new year! 2024-01-04 00:28:56 +09:00
Kenta Kubo
786c200267
Update LICENSE.txt 2023-11-14 00:59:30 +09:00
Kenta Kubo
4f3c78890b
Refactor button style 2023-06-12 00:07:58 +09:00
Kenta Kubo
ec054cde2b
Merge pull request #68 from kkk669/playground
Add Package.swift for Swift Playgrounds
2023-05-01 00:09:07 +09:00
Kenta Kubo
4ddcef7988
Rename LICENSE to LICENSE.txt for Swift Playgrounds 2023-04-30 15:35:49 +09:00
Kenta Kubo
34a1d4f81b
Add Package.swift for Swift Playgrounds
Swift Playgrounds support is currently limited because it doesn't
support essential entitlements for DNSecure. However, this is useful
for me to modify UI with Swift Playgrounds.
2023-04-30 15:33:19 +09:00
Kenta Kubo
4b13699ddd
Fix typo 2023-04-30 15:21:37 +09:00
Kenta Kubo
ba4065ee7b Bump version to 1.4.3 (19) 2023-04-26 00:52:01 +09:00
Kenta Kubo
d833ea49c1
Merge pull request #67 from kkk669/issues/64
FIx UI issues in iOS 15 (#64)
2023-04-26 00:51:23 +09:00
Kenta Kubo
c489603ff6 FIx UI issues in iOS 15 (#64)
fixes #64
2023-04-26 00:50:47 +09:00
Kenta Kubo
8c50f30b7b Bump version to 1.4.3 (18) 2023-04-25 21:58:37 +09:00
Kenta Kubo
a6de71384f
Add ITSAppUsesNonExemptEncryption to Info.plist 2023-04-25 21:58:00 +09:00
Kenta Kubo
78d4cd0b09 Add a clear button on the right side of LazyTextField 2023-04-25 21:57:22 +09:00
Kenta Kubo
e28358acfe
Merge pull request #65 from kkk669/issues/63
Fix laggy TextFields (#63)
2023-04-25 21:44:43 +09:00
Kenta Kubo
dec111f01c Fix laggy TextFields (#63)
fixes #63
2023-04-25 21:42:47 +09:00
Kenta Kubo
7be92ee8af Change alignment of TextFields from trailing to leading 2023-04-25 02:37:12 +09:00
Kenta Kubo
d61e4c7a1b Revert intended changes 2023-04-25 02:32:16 +09:00
Kenta Kubo
f9bb5dbdf9 Replace deprecated APIs 2023-04-25 02:05:40 +09:00
Kenta Kubo
ef7916d5e7 Refactor DoTSections and DoHSections 2023-04-25 01:41:51 +09:00
Kenta Kubo
4d7f7c2ef3 Bump version to 1.4.3 (17) 2023-04-24 00:48:24 +09:00
Kenta Kubo
3901506dd0
Merge pull request #62 from kkk669/issues/61
Fix #61
2023-04-24 00:47:18 +09:00
Kenta Kubo
4f1e67e190 Fix #61 2023-04-24 00:40:46 +09:00
90 changed files with 1781 additions and 1138 deletions

4
.gitattributes vendored Normal file
View file

@ -0,0 +1,4 @@
* text=auto eol=lf
*.pbxproj merge=union
Package.resolved text linguist-language=json linguist-generated=false
.swift-format text linguist-language=json

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @kkebo

10
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
assignees:
- "kkebo"
commit-message:
prefix: "ci"

27
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: ci
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
permissions: {}
defaults:
run:
shell: bash -euo pipefail {0}
jobs:
lint:
runs-on: ubuntu-24.04-arm
container: swift:6.2
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- run: swift format lint -rsp .
yamllint:
runs-on: ubuntu-24.04-arm
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- run: yamllint --version
- run: yamllint --strict --config-file .yamllint.yml .

89
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,89 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: '40 19 * * 3'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: swift
build-mode: manual
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
if: matrix.build-mode == 'manual'
shell: bash
env:
DEVELOPER_DIR: /Applications/Xcode_26.1.1.app
run: xcodebuild build -project DNSecure.xcodeproj -scheme DNSecure -destination "platform=iOS Simulator,name=iPad Pro 13-inch (M5),OS=26.1" -quiet -showBuildTimingSummary
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
category: "/language:${{matrix.language}}"

2
.gitignore vendored
View file

@ -47,7 +47,7 @@ playground.xcworkspace
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.swiftpm
.build/

75
.swift-format Normal file
View file

@ -0,0 +1,75 @@
{
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"indentConditionalCompilationBlocks" : true,
"indentSwitchCaseLabels" : false,
"indentation" : {
"spaces" : 4
},
"lineBreakAroundMultilineExpressionChainComponents" : true,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : true,
"lineBreakBeforeEachGenericRequirement" : true,
"lineBreakBetweenDeclarationAttributes" : false,
"lineLength" : 120,
"maximumBlankLines" : 1,
"multiElementCollectionTrailingCommas" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether" : true,
"reflowMultilineStringLiterals" : "never",
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : true,
"AlwaysUseLowerCamelCase" : true,
"AmbiguousTrailingClosureOverload" : true,
"AvoidRetroactiveConformances" : false,
"BeginDocumentationCommentWithOneLineSummary" : true,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : true,
"NeverUseImplicitlyUnwrappedOptionals" : true,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyLinesOpeningClosingBraces" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : true,
"NoParensAroundConditions" : true,
"NoPlaygroundLiterals" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : true,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"TypeNamesShouldBeCapitalized" : true,
"UseEarlyExits" : true,
"UseExplicitNilCheckInConditions" : true,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : true,
"ValidateDocumentationComments" : true
},
"spacesAroundRangeFormationOperators" : false,
"spacesBeforeEndOfLineComments" : 2,
"tabWidth" : 4,
"version" : 1
}

7
.yamllint.yml Normal file
View file

@ -0,0 +1,7 @@
extends: default
rules:
line-length: false
document-start: false
truthy:
check-keys: false

View file

@ -3,32 +3,11 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80D4251DC3A20046BAA0 /* DetailView.swift */; };
890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890B80DE251DC6B50046BAA0 /* Presets.swift */; };
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */; };
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */; };
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */; };
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */; };
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */; };
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */; };
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */; };
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023B24ACBD2700EBE74B /* DNSecureApp.swift */; };
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940023D24ACBD2700EBE74B /* ContentView.swift */; };
8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940023F24ACBD2800EBE74B /* Assets.xcassets */; };
8940024324ACBD2800EBE74B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8940024224ACBD2800EBE74B /* Preview Assets.xcassets */; };
8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940024D24ACBD2800EBE74B /* DNSecureTests.swift */; };
8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940025824ACBD2800EBE74B /* DNSecureUITests.swift */; };
8940026924ACBE4900EBE74B /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8940026824ACBE4900EBE74B /* NetworkExtension.framework */; };
894958AD2548405E009691D5 /* RuleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 894958AC2548405E009691D5 /* RuleView.swift */; };
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */; };
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8986CDCE251D9B3400D947CD /* Resolver.swift */; };
8998041628DCDED800C8B421 /* DoTSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998041528DCDED800C8B421 /* DoTSections.swift */; };
8998041828DCDEEF00C8B421 /* DoHSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8998041728DCDEEF00C8B421 /* DoHSections.swift */; };
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CB922025209DD100B6983C /* HowToActivateView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -49,38 +28,44 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
890B80D4251DC3A20046BAA0 /* DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailView.swift; sourceTree = "<group>"; };
890B80DE251DC6B50046BAA0 /* Presets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presets.swift; sourceTree = "<group>"; };
8924EDF324C9CDF1004AF871 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CaseIterable.swift"; sourceTree = "<group>"; };
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+CustomStringConvertible.swift"; sourceTree = "<group>"; };
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+Codable.swift"; sourceTree = "<group>"; };
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleInterfaceType+ssidIsUsed.swift"; sourceTree = "<group>"; };
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CaseIterable.swift"; sourceTree = "<group>"; };
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+CustomStringConvertible.swift"; sourceTree = "<group>"; };
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NEOnDemandRuleAction+Codable.swift"; sourceTree = "<group>"; };
8940023824ACBD2700EBE74B /* DNSecure.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DNSecure.app; sourceTree = BUILT_PRODUCTS_DIR; };
8940023B24ACBD2700EBE74B /* DNSecureApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureApp.swift; sourceTree = "<group>"; };
8940023D24ACBD2700EBE74B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
8940023F24ACBD2800EBE74B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
8940024224ACBD2800EBE74B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
8940024424ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940024924ACBD2800EBE74B /* DNSecureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8940024D24ACBD2800EBE74B /* DNSecureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureTests.swift; sourceTree = "<group>"; };
8940024F24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DNSecureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
8940025824ACBD2800EBE74B /* DNSecureUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSecureUITests.swift; sourceTree = "<group>"; };
8940025A24ACBD2800EBE74B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8940026624ACBE4900EBE74B /* DNSecure.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DNSecure.entitlements; sourceTree = "<group>"; };
8940026824ACBE4900EBE74B /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
894958AC2548405E009691D5 /* RuleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleView.swift; sourceTree = "<group>"; };
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+displayName.swift"; sourceTree = "<group>"; };
8986CDCE251D9B3400D947CD /* Resolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resolver.swift; sourceTree = "<group>"; };
8998041528DCDED800C8B421 /* DoTSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoTSections.swift; sourceTree = "<group>"; };
8998041728DCDEEF00C8B421 /* DoHSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoHSections.swift; sourceTree = "<group>"; };
89CB922025209DD100B6983C /* HowToActivateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowToActivateView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
899AD03F2E66100500449710 /* Exceptions for "DNSecure" folder in "DNSecure" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 8940023724ACBD2700EBE74B /* DNSecure */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
899AD0232E66100500449710 /* DNSecure */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
899AD03F2E66100500449710 /* Exceptions for "DNSecure" folder in "DNSecure" target */,
);
path = DNSecure;
sourceTree = "<group>";
};
899AD0422E66100E00449710 /* DNSecureTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = DNSecureTests;
sourceTree = "<group>";
};
899AD0472E66101100449710 /* DNSecureUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = DNSecureUITests;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
8940023524ACBD2700EBE74B /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
@ -107,50 +92,13 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
893AA816258F790D0060B022 /* Views */ = {
isa = PBXGroup;
children = (
8940023D24ACBD2700EBE74B /* ContentView.swift */,
89CB922025209DD100B6983C /* HowToActivateView.swift */,
890B80D4251DC3A20046BAA0 /* DetailView.swift */,
894958AC2548405E009691D5 /* RuleView.swift */,
8998041528DCDED800C8B421 /* DoTSections.swift */,
8998041728DCDEEF00C8B421 /* DoHSections.swift */,
);
path = Views;
sourceTree = "<group>";
};
893AA817258F79F70060B022 /* Models */ = {
isa = PBXGroup;
children = (
8986CDCE251D9B3400D947CD /* Resolver.swift */,
890B80DE251DC6B50046BAA0 /* Presets.swift */,
);
path = Models;
sourceTree = "<group>";
};
893AA818258F7A0C0060B022 /* Extensions */ = {
isa = PBXGroup;
children = (
8963FDFA251DF1BC00E3DFE7 /* Bundle+displayName.swift */,
893AA852258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift */,
893AA857258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift */,
893AA85C258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift */,
893AA861258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift */,
893AA866258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift */,
893AA86B258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift */,
893AA870258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
8940022F24ACBD2700EBE74B = {
isa = PBXGroup;
children = (
8924EDF324C9CDF1004AF871 /* README.md */,
8940023A24ACBD2700EBE74B /* DNSecure */,
8940024C24ACBD2800EBE74B /* DNSecureTests */,
8940025724ACBD2800EBE74B /* DNSecureUITests */,
899AD0232E66100500449710 /* DNSecure */,
899AD0422E66100E00449710 /* DNSecureTests */,
899AD0472E66101100449710 /* DNSecureUITests */,
8940023924ACBD2700EBE74B /* Products */,
8940026724ACBE4900EBE74B /* Frameworks */,
);
@ -166,47 +114,6 @@
name = Products;
sourceTree = "<group>";
};
8940023A24ACBD2700EBE74B /* DNSecure */ = {
isa = PBXGroup;
children = (
8940026624ACBE4900EBE74B /* DNSecure.entitlements */,
8940023B24ACBD2700EBE74B /* DNSecureApp.swift */,
893AA816258F790D0060B022 /* Views */,
893AA817258F79F70060B022 /* Models */,
893AA818258F7A0C0060B022 /* Extensions */,
8940023F24ACBD2800EBE74B /* Assets.xcassets */,
8940024424ACBD2800EBE74B /* Info.plist */,
8940024124ACBD2800EBE74B /* Preview Content */,
);
path = DNSecure;
sourceTree = "<group>";
};
8940024124ACBD2800EBE74B /* Preview Content */ = {
isa = PBXGroup;
children = (
8940024224ACBD2800EBE74B /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
8940024C24ACBD2800EBE74B /* DNSecureTests */ = {
isa = PBXGroup;
children = (
8940024D24ACBD2800EBE74B /* DNSecureTests.swift */,
8940024F24ACBD2800EBE74B /* Info.plist */,
);
path = DNSecureTests;
sourceTree = "<group>";
};
8940025724ACBD2800EBE74B /* DNSecureUITests */ = {
isa = PBXGroup;
children = (
8940025824ACBD2800EBE74B /* DNSecureUITests.swift */,
8940025A24ACBD2800EBE74B /* Info.plist */,
);
path = DNSecureUITests;
sourceTree = "<group>";
};
8940026724ACBE4900EBE74B /* Frameworks */ = {
isa = PBXGroup;
children = (
@ -230,6 +137,9 @@
);
dependencies = (
);
fileSystemSynchronizedGroups = (
899AD0232E66100500449710 /* DNSecure */,
);
name = DNSecure;
productName = DNSecure;
productReference = 8940023824ACBD2700EBE74B /* DNSecure.app */;
@ -248,6 +158,9 @@
dependencies = (
8940024B24ACBD2800EBE74B /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
899AD0422E66100E00449710 /* DNSecureTests */,
);
name = DNSecureTests;
productName = DNSecureTests;
productReference = 8940024924ACBD2800EBE74B /* DNSecureTests.xctest */;
@ -266,6 +179,9 @@
dependencies = (
8940025624ACBD2800EBE74B /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
899AD0472E66101100449710 /* DNSecureUITests */,
);
name = DNSecureUITests;
productName = DNSecureUITests;
productReference = 8940025424ACBD2800EBE74B /* DNSecureUITests.xctest */;
@ -277,8 +193,9 @@
8940023024ACBD2700EBE74B /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 1200;
LastUpgradeCheck = 2600;
TargetAttributes = {
8940023724ACBD2700EBE74B = {
CreatedOnToolsVersion = 12.0;
@ -294,7 +211,6 @@
};
};
buildConfigurationList = 8940023324ACBD2700EBE74B /* Build configuration list for PBXProject "DNSecure" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
@ -302,6 +218,8 @@
Base,
);
mainGroup = 8940022F24ACBD2700EBE74B;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 8940023924ACBD2700EBE74B /* Products */;
projectDirPath = "";
projectRoot = "";
@ -318,8 +236,6 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8940024324ACBD2800EBE74B /* Preview Assets.xcassets in Resources */,
8940024024ACBD2800EBE74B /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -344,23 +260,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8986CDCF251D9B3400D947CD /* Resolver.swift in Sources */,
89CB922125209DD100B6983C /* HowToActivateView.swift in Sources */,
890B80D5251DC3A20046BAA0 /* DetailView.swift in Sources */,
893AA853258F99630060B022 /* NEOnDemandRuleInterfaceType+CaseIterable.swift in Sources */,
893AA871258F99AD0060B022 /* NEOnDemandRuleAction+Codable.swift in Sources */,
893AA85D258F997A0060B022 /* NEOnDemandRuleInterfaceType+Codable.swift in Sources */,
8940023E24ACBD2700EBE74B /* ContentView.swift in Sources */,
894958AD2548405E009691D5 /* RuleView.swift in Sources */,
8998041828DCDEEF00C8B421 /* DoHSections.swift in Sources */,
890B80DF251DC6B50046BAA0 /* Presets.swift in Sources */,
893AA858258F996F0060B022 /* NEOnDemandRuleInterfaceType+CustomStringConvertible.swift in Sources */,
8940023C24ACBD2700EBE74B /* DNSecureApp.swift in Sources */,
8998041628DCDED800C8B421 /* DoTSections.swift in Sources */,
893AA862258F998C0060B022 /* NEOnDemandRuleInterfaceType+ssidIsUsed.swift in Sources */,
893AA867258F99990060B022 /* NEOnDemandRuleAction+CaseIterable.swift in Sources */,
893AA86C258F99A10060B022 /* NEOnDemandRuleAction+CustomStringConvertible.swift in Sources */,
8963FDFB251DF1BC00E3DFE7 /* Bundle+displayName.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -368,7 +267,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8940024E24ACBD2800EBE74B /* DNSecureTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -376,7 +274,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8940025924ACBD2800EBE74B /* DNSecureUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -400,10 +297,10 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
@ -431,9 +328,11 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
@ -448,12 +347,14 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 6.0;
};
name = Debug;
};
@ -461,10 +362,10 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
@ -492,9 +393,11 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
@ -503,11 +406,12 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 6.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@ -517,24 +421,35 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_ASSET_PATHS = "\"DNSecure/Preview Content\"";
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_APP_SANDBOX = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DNSecure/Info.plist;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.2;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@ -544,24 +459,35 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;
CODE_SIGN_ENTITLEMENTS = DNSecure/DNSecure.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
DEVELOPMENT_ASSET_PATHS = "\"DNSecure/Preview Content\"";
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
ENABLE_APP_SANDBOX = YES;
ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = DNSecure/Info.plist;
INFOPLIST_KEY_ITSAppUsesNonExemptEncryption = NO;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiredDeviceCapabilities = armv7;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.4.2;
MARKETING_VERSION = 1.6.2;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecure;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@ -569,62 +495,48 @@
8940026124ACBD2800EBE74B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
INFOPLIST_FILE = DNSecureTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/DNSecure";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DNSecure";
};
name = Debug;
};
8940026224ACBD2800EBE74B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
INFOPLIST_FILE = DNSecureTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/DNSecure";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DNSecure.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/DNSecure";
};
name = Release;
};
8940026424ACBD2800EBE74B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
INFOPLIST_FILE = DNSecureUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = DNSecure;
};
@ -633,18 +545,14 @@
8940026524ACBD2800EBE74B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = X4678G5DL2;
INFOPLIST_FILE = DNSecureUITests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = xyz.kebo.DNSecureUITests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = DNSecure;
};

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "2600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,29 @@
{
"fill" : {
"automatic-gradient" : "srgb:0.96863,0.77255,0.28235,1.00000"
},
"groups" : [
{
"layers" : [
{
"image-name" : "DNSecure-icon.svg",
"name" : "DNSecure-icon"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

View file

@ -0,0 +1,14 @@
{
"images" : [
{
"filename" : "Icon-1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,116 +0,0 @@
{
"images" : [
{
"filename" : "Icon-40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon-58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon-80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-121.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "Icon-41.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "Icon-59.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-42.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "Icon-81.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "Icon-152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "AllDone.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "FiltersSettings.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "MakeItEnabled.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Screen Shot 2021-02-13 at 11.25.16 AM.jpg",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -5,7 +5,7 @@
"scale" : "1x"
},
{
"filename" : "Screen Shot 2021-02-13 at 11.25.16 AM.jpg",
"filename" : "NetworkSettings.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "SystemSettingsIcon.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -8,9 +8,5 @@
</array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View file

@ -5,15 +5,22 @@
// Created by Kenta Kubo on 7/1/20.
//
import os.log
import SwiftUI
import os.log
let logger = Logger()
@main
struct DNSecureApp {
@AppStorage("servers") private var servers = Presets.servers
@AppStorage("servers") private var servers: Resolvers = []
@AppStorage("usedID") private var usedID: String?
init() {
if UserDefaults.standard.object(forKey: "servers") == nil {
// Set the default value in order to fix UUIDs
self.servers = Presets.servers
}
}
}
extension DNSecureApp: App {

View file

@ -0,0 +1,10 @@
//
// NEEvaluateConnectionRuleAction+Codable.swift
// DNSecure
//
// Created by Kenta Kubo on 8/31/25.
//
import NetworkExtension
extension NEEvaluateConnectionRuleAction: @retroactive Codable {}

View file

@ -1,14 +0,0 @@
//
// NEOnDemandRuleAction+CaseIterable.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleAction: CaseIterable {
public static var allCases: [Self] {
[.connect, .disconnect, .evaluateConnection, .ignore]
}
}

View file

@ -7,4 +7,4 @@
import NetworkExtension
extension NEOnDemandRuleAction: Codable {}
extension NEOnDemandRuleAction: @retroactive Codable {}

View file

@ -7,19 +7,14 @@
import NetworkExtension
extension NEOnDemandRuleAction: CustomStringConvertible {
extension NEOnDemandRuleAction: @retroactive CustomStringConvertible {
public var description: String {
switch self {
case .connect:
return "Apply settings"
case .disconnect:
return "Do not apply settings"
case .evaluateConnection:
return "Apply with excluded domains"
case .ignore:
return "As is"
default:
return "Unknown"
case .connect: "Apply settings"
case .disconnect: "Do not apply settings"
case .evaluateConnection: "Apply with excluded domains"
case .ignore: "As is"
@unknown case _: "Unknown"
}
}
}

View file

@ -7,12 +7,12 @@
import NetworkExtension
extension NEOnDemandRuleInterfaceType: CaseIterable {
extension NEOnDemandRuleInterfaceType: @retroactive CaseIterable {
public static var allCases: [Self] {
#if os(macOS)
return [.any, .ethernet, .wiFi]
[.any, .ethernet, .wiFi]
#else
return [.any, .wiFi, .cellular]
[.any, .wiFi, .cellular]
#endif
}
}

View file

@ -7,4 +7,4 @@
import NetworkExtension
extension NEOnDemandRuleInterfaceType: Codable {}
extension NEOnDemandRuleInterfaceType: @retroactive Codable {}

View file

@ -7,23 +7,18 @@
import NetworkExtension
extension NEOnDemandRuleInterfaceType: CustomStringConvertible {
extension NEOnDemandRuleInterfaceType: @retroactive CustomStringConvertible {
public var description: String {
switch self {
case .any:
return "Any"
case .any: "Any"
#if os(macOS)
case .ethernet:
return "Ethernet"
case .ethernet: "Ethernet"
#endif
case .wiFi:
return "Wi-Fi"
case .wiFi: "Wi-Fi"
#if os(iOS)
case .cellular:
return "Cellular"
case .cellular: "Cellular"
#endif
default:
return "Unknown"
@unknown case _: "Unknown"
}
}
}

View file

@ -0,0 +1,17 @@
//
// NEOnDemandRuleInterfaceType+isSSIDUsed.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleInterfaceType {
var isSSIDUsed: Bool {
switch self {
case .any, .wiFi: true
case _: false
}
}
}

View file

@ -1,19 +0,0 @@
//
// NEOnDemandRuleInterfaceType+ssidIsUsed.swift
// DNSecure
//
// Created by Kenta Kubo on 12/20/20.
//
import NetworkExtension
extension NEOnDemandRuleInterfaceType {
var ssidIsUsed: Bool {
switch self {
case .any, .wiFi:
return true
default:
return false
}
}
}

View file

@ -2,51 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchScreen</key>
<dict/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1,86 @@
//
// OnDemandRule.swift
// DNSecure
//
// Created by Kenta Kubo on 8/31/25.
//
import Foundation
import NetworkExtension
struct OnDemandRule {
var id = UUID()
var name: String
var action: NEOnDemandRuleAction = .connect
var interfaceType: NEOnDemandRuleInterfaceType = .any
var ssidMatch: [String] = []
var dnsSearchDomainMatch: [String] = []
var dnsServerAddressMatch: [String] = []
var probeURL: URL?
var excludedDomains: [String]?
}
extension OnDemandRule: Identifiable {}
extension OnDemandRule: Equatable {}
extension OnDemandRule: Hashable {}
extension OnDemandRule: Codable {}
extension [OnDemandRule] {
func toNEOnDemandRules() -> [NEOnDemandRule] {
self.map { rule in
switch rule.action {
case .connect:
let newRule = NEOnDemandRuleConnect()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.isSSIDUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
case .disconnect:
let newRule = NEOnDemandRuleDisconnect()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.isSSIDUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
case .evaluateConnection:
let newRule = NEOnDemandRuleEvaluateConnection()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.isSSIDUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
newRule.connectionRules =
switch rule.excludedDomains {
case let domains? where !domains.isEmpty:
[.init(matchDomains: domains, andAction: .neverConnect)]
case _: []
}
return newRule
case .ignore:
let newRule = NEOnDemandRuleIgnore()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.isSSIDUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
@unknown case _:
preconditionFailure("Unexpected NEOnDemandRuleAction")
}
}
}
}

View file

@ -126,7 +126,7 @@ enum Presets {
configuration: .dnsOverTLS(
DoTConfiguration(
servers: [
"116.202.176.26",
"116.202.176.26"
],
serverName: "dot.libredns.gr"
)
@ -137,7 +137,7 @@ enum Presets {
configuration: .dnsOverHTTPS(
DoHConfiguration(
servers: [
"116.202.176.26",
"116.202.176.26"
],
serverURL: URL(string: "https://doh.libredns.gr/dns-query")
)
@ -148,7 +148,7 @@ enum Presets {
configuration: .dnsOverHTTPS(
DoHConfiguration(
servers: [
"116.202.176.26",
"116.202.176.26"
],
serverURL: URL(string: "https://doh.libredns.gr/ads")
)
@ -210,5 +210,33 @@ enum Presets {
)
)
),
.init(
name: "Freifunk Muenchen DNS",
configuration: .dnsOverTLS(
DoTConfiguration(
servers: [
"5.1.66.255",
"185.150.99.255",
"2001:678:e68:f000::",
"2001:678:ed0:f000::",
],
serverName: "dot.ffmuc.net"
)
)
),
.init(
name: "Freifunk Muenchen DNS",
configuration: .dnsOverHTTPS(
DoHConfiguration(
servers: [
"5.1.66.255",
"185.150.99.255",
"2001:678:e68:f000::",
"2001:678:ed0:f000::",
],
serverURL: URL(string: "https://doh.ffmuc.net/dns-query")
)
)
),
]
}

View file

@ -48,9 +48,9 @@ enum Configuration {
func toDNSSettings() -> NEDNSSettings {
switch self {
case let .dnsOverTLS(configuration):
case .dnsOverTLS(let configuration):
return configuration.toDNSSettings()
case let .dnsOverHTTPS(configuration):
case .dnsOverHTTPS(let configuration):
return configuration.toDNSSettings()
}
}
@ -87,10 +87,10 @@ extension Configuration: Codable {
var container = encoder.container(keyedBy: Self.CodingKeys.self)
switch self {
case let .dnsOverTLS(configuration):
case .dnsOverTLS(let configuration):
try container.encode(Self.Base.dnsOverTLS, forKey: .base)
try container.encode(configuration, forKey: .dotConfiguration)
case let .dnsOverHTTPS(configuration):
case .dnsOverHTTPS(let configuration):
try container.encode(Self.Base.dnsOverHTTPS, forKey: .base)
try container.encode(configuration, forKey: .dohConfiguration)
}
@ -106,77 +106,6 @@ extension Configuration: CustomStringConvertible {
}
}
struct OnDemandRule {
var id = UUID()
var name: String
var action: NEOnDemandRuleAction = .ignore
var interfaceType: NEOnDemandRuleInterfaceType = .any
var ssidMatch: [String] = []
var dnsSearchDomainMatch: [String] = []
var dnsServerAddressMatch: [String] = []
var probeURL: URL?
}
extension OnDemandRule: Identifiable {}
extension OnDemandRule: Equatable {}
extension OnDemandRule: Hashable {}
extension OnDemandRule: Codable {}
extension Array where Self.Element == OnDemandRule {
func toNEOnDemandRules() -> [NEOnDemandRule] {
self.lazy
.map { rule in
switch rule.action {
case .connect:
let newRule = NEOnDemandRuleConnect()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.ssidIsUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
case .disconnect:
let newRule = NEOnDemandRuleDisconnect()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.ssidIsUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
case .evaluateConnection:
let newRule = NEOnDemandRuleEvaluateConnection()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.ssidIsUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
case .ignore:
let newRule = NEOnDemandRuleIgnore()
newRule.interfaceTypeMatch = rule.interfaceType
if rule.interfaceType.ssidIsUsed {
newRule.ssidMatch = rule.ssidMatch
}
newRule.dnsSearchDomainMatch = rule.dnsSearchDomainMatch
newRule.dnsServerAddressMatch = rule.dnsServerAddressMatch
newRule.probeURL = rule.probeURL
return newRule
default:
preconditionFailure("Unexpected NEOnDemandRuleAction")
}
}
}
}
struct Resolver {
var id = UUID()
var name: String
@ -200,10 +129,11 @@ extension Resolver: Codable {
self.id = try container.decode(UUID.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.configuration = try container.decode(Configuration.self, forKey: .configuration)
self.onDemandRules = try container.decodeIfPresent(
[OnDemandRule].self,
forKey: .onDemandRules
) ?? []
self.onDemandRules =
try container.decodeIfPresent(
[OnDemandRule].self,
forKey: .onDemandRules
) ?? []
}
}
@ -215,10 +145,10 @@ extension Resolvers {
}
}
extension Resolvers: RawRepresentable {
extension Resolvers: @retroactive RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Self.self, from: data)
let result = try? JSONDecoder().decode(Self.self, from: data)
else {
return nil
}
@ -227,7 +157,7 @@ extension Resolvers: RawRepresentable {
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}

View file

@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -5,19 +5,33 @@
// Created by Kenta Kubo on 7/1/20.
//
import NetworkExtension
@preconcurrency import NetworkExtension
import SwiftUI
struct ContentView {
@Environment(\.scenePhase) private var scenePhase
@Environment(\.horizontalSizeClass) private var hSizeClass
@Binding var servers: Resolvers
@Binding var usedID: String?
@State private var isEnabled = false
@State private var isActivated = false
@State private var selection: Int?
@State private var alertIsPresented = false
@State private var isAlertPresented = false
@State private var alertTitle = ""
@State private var alertMessage = ""
@State private var guideIsPresented = false
@State private var isGuidePresented = false
@State private var isRestoring = false
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
@State private var sidebarEditMode: EditMode = .inactive
@State private var detailEditMode: EditMode = .inactive
#endif
private var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode {
if #available(iOS 26, *) {
.inline
} else {
.automatic
}
}
private func addNewDoTServer() {
self.servers.append(
@ -39,6 +53,10 @@ struct ContentView {
self.selection = self.servers.count - 1
}
private func restoreFromPresets(resolver: Resolver) {
self.servers.append(resolver)
}
private func removeServers(at indexSet: IndexSet) {
if let current = self.selection, indexSet.contains(where: { $0 <= current }) {
// FIXME: This is a workaround not to crash on deletion.
@ -61,13 +79,18 @@ struct ContentView {
private func updateStatus() {
#if !targetEnvironment(simulator)
// Early return if running on Swift Playground or Xcode Previews
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else {
return
}
let manager = NEDNSSettingsManager.shared()
manager.loadFromPreferences {
if let err = $0 {
logger.error("\(err.localizedDescription)")
self.alert("Load Error", err.localizedDescription)
} else {
self.isEnabled = manager.isEnabled
self.isActivated = manager.isEnabled
}
}
#endif
@ -79,13 +102,21 @@ struct ContentView {
}
#if !targetEnvironment(simulator)
// Early return if running on Swift Playground or Xcode Previews
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else {
return
}
let manager = NEDNSSettingsManager.shared()
manager.localizedDescription = server.name
manager.dnsSettings = server.configuration.toDNSSettings()
manager.onDemandRules = server.onDemandRules.toNEOnDemandRules()
manager.saveToPreferences { saveError in
if let saveError = saveError as NSError? {
guard saveError.domain != "NEConfigurationErrorDomain"
|| saveError.code != 9 else {
guard
saveError.domain != "NEConfigurationErrorDomain"
|| saveError.code != 9
else {
// Nothing was changed
return
}
@ -94,7 +125,7 @@ struct ContentView {
self.removeSettings()
return
}
logger.debug("DNS settings was saved")
logger.debug("The DNS settings were saved")
}
#endif
}
@ -103,6 +134,11 @@ struct ContentView {
self.usedID = nil
#if !targetEnvironment(simulator)
// Early return if running on Swift Playground or Xcode Previews
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else {
return
}
let manager = NEDNSSettingsManager.shared()
guard manager.dnsSettings != nil else {
// Already removed
@ -115,7 +151,7 @@ struct ContentView {
self.alert("Remove Error", removeError.localizedDescription)
return
}
logger.debug("DNS settings was removed")
logger.debug("The DNS settings were removed")
}
#endif
}
@ -123,14 +159,18 @@ struct ContentView {
private func alert(_ title: String, _ message: String) {
self.alertTitle = title
self.alertMessage = message
self.alertIsPresented = true
self.isAlertPresented = true
}
}
@MainActor
extension ContentView: View {
var body: some View {
if #available(iOS 16, *) {
self.modernBody
} else if self.hSizeClass == .compact {
// Workaround for iOS 15
self.legacyBody.navigationViewStyle(.stack)
} else {
self.legacyBody
}
@ -140,7 +180,9 @@ extension ContentView: View {
private var modernBody: some View {
NavigationSplitView {
List(selection: self.$selection) {
NavigationLink("Instruction", value: -1)
if !self.isActivated && self.hSizeClass == .compact {
NavigationLink("Instructions", value: -1)
}
Section("Servers") {
ForEach(0..<self.servers.count, id: \.self) { i in
NavigationLink(value: i) {
@ -152,59 +194,86 @@ extension ContentView: View {
}
}
.navigationTitle(Bundle.main.displayName!)
.toolbar { self.toolbarContent }
.alert(self.alertTitle, isPresented: self.$alertIsPresented) {
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
.toolbar {
#if canImport(SwiftUI, _version: 7)
if #available(iOS 26, *) {
ToolbarItem(placement: .subtitle) {
self.statusIndicator
.foregroundStyle(.secondary)
}
}
#endif
self.toolbarContent
}
.alert(self.alertTitle, isPresented: self.$isAlertPresented) {
} message: {
Text(self.alertMessage)
}
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
.environment(\.editMode, self.$sidebarEditMode)
#endif
} detail: {
if self.selection == -1 {
HowToActivateView(isSheet: false)
} else if let i = self.selection {
self.detailView(at: i)
} else if !self.isEnabled {
HowToActivateView(isSheet: false)
if let i = self.selection, i >= 0 {
NavigationStack {
self.detailView(at: i)
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
}
#if targetEnvironment(macCatalyst)
// Workaround for https://github.com/kkebo/DNSecure/issues/139
.environment(\.editMode, self.$detailEditMode)
#endif
} else if !self.isActivated || self.selection == -1 {
HowToActivateView()
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
} else {
Text("Select a server on the sidebar")
.navigationBarHidden(true)
.navigationBarTitleDisplayMode(self.navigationBarTitleDisplayMode)
}
}
.onAppear(perform: self.updateStatus)
.onChange(of: self.scenePhase) { phase in
if phase == .active {
.task {
for await _ in NotificationCenter.default
.notifications(named: .NEDNSSettingsConfigurationDidChange)
.map(\.name)
{
self.updateStatus()
} else if phase == .background {
// FIXME: This is a workaround for self.$severs[i].
// That cannot save settings as soon as it is modified.
guard let id = self.usedID,
let uuid = UUID(uuidString: id),
let server = self.servers.find(by: uuid) else {
return
}
self.saveSettings(of: server)
}
}
}
@available(iOS, deprecated: 16)
private var legacyBody: some View {
NavigationView {
List {
NavigationLink(
"Instructions",
tag: -1,
selection: self.$selection
) {
HowToActivateView(isSheet: false)
if !self.isActivated && self.hSizeClass == .compact {
NavigationLink(
"Instructions",
tag: -1,
selection: self.$selection
) {
HowToActivateView()
}
}
Section("Servers") {
ForEach(0..<self.servers.count, id: \.self) { i in
NavigationLink(
tag: i,
selection: self.$selection
) {
self.detailView(at: i)
} label: {
self.sidebarRow(at: i)
if self.hSizeClass == .compact {
NavigationLink(
tag: i,
selection: self.$selection
) {
self.detailView(at: i)
} label: {
self.sidebarRow(at: i)
}
} else {
// Workaround for iOS 15
Button {
self.selection = i
} label: {
self.sidebarRow(at: i)
}
}
}
.onDelete(perform: self.removeServers)
@ -213,87 +282,122 @@ extension ContentView: View {
}
.navigationTitle(Bundle.main.displayName!)
.toolbar { self.toolbarContent }
.alert(self.alertTitle, isPresented: self.$alertIsPresented) {
.alert(self.alertTitle, isPresented: self.$isAlertPresented) {
} message: {
Text(self.alertMessage)
}
if !self.isEnabled {
HowToActivateView(isSheet: false)
if let i = self.selection, i >= 0 {
self.detailView(at: i)
} else if !self.isActivated || self.selection == -1 {
HowToActivateView()
} else {
Text("Select a server on the sidebar")
.navigationBarHidden(true)
}
}
.onAppear(perform: self.updateStatus)
.onChange(of: self.scenePhase) { phase in
if phase == .active {
.task {
for await _ in NotificationCenter.default
.notifications(named: .NEDNSSettingsConfigurationDidChange)
.map(\.name)
{
self.updateStatus()
} else if phase == .background {
// FIXME: This is a workaround for self.$severs[i].
// That cannot save settings as soon as it is modified.
guard let id = self.usedID,
let uuid = UUID(uuidString: id),
let server = self.servers.find(by: uuid) else {
return
}
self.saveSettings(of: server)
}
}
}
@ToolbarContentBuilder private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Menu {
Button("DNS-over-TLS", action: self.addNewDoTServer)
Button("DNS-over-HTTPS", action: self.addNewDoHServer)
} label: {
Image(systemName: "plus")
ToolbarItem(placement: .topBarLeading) {
if #available(iOS 26, *) {
#if targetEnvironment(macCatalyst)
// Workaround for the issue that the checkmark icon is hard to see if System Settings > Appearance > Theme > Color is Multicolor.
Button {
withAnimation {
if !self.sidebarEditMode.isEditing {
self.sidebarEditMode = .active
} else {
self.sidebarEditMode = .inactive
}
}
} label: {
if !self.sidebarEditMode.isEditing {
Text("Edit")
} else {
Label("Done", systemImage: "checkmark")
}
}
.tint(self.sidebarEditMode.isEditing ? .accentColor : .primary)
#else
EditButton()
#endif
} else {
self.addMenu
}
}
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
ToolbarItem(placement: .topBarTrailing) {
if #available(iOS 26, *) {
self.addMenu
} else {
EditButton()
}
}
ToolbarItem(placement: .status) {
VStack {
HStack {
Circle()
.frame(width: 10, height: 10)
.foregroundColor(self.isEnabled ? .green : .secondary)
Text(self.isEnabled ? "Active" : "Inactive")
#if targetEnvironment(macCatalyst)
Text("-")
Button("Refresh", action: self.updateStatus)
#endif
}
if !self.isEnabled {
Button("How to Activate") {
self.guideIsPresented = true
}
.sheet(isPresented: self.$guideIsPresented) {
HowToActivateView(isSheet: true)
}
}
if #available(iOS 26, *) {
#if !canImport(SwiftUI, _version: 7)
self.statusIndicator
#endif
} else {
self.statusIndicator
}
}
}
@ViewBuilder private func sidebarRow(at i: Int) -> some View {
VStack(alignment: .leading) {
Text(self.servers[i].name)
Text(self.servers[i].configuration.description)
.foregroundColor(.secondary)
private var addMenu: some View {
Menu("Add", systemImage: "plus") {
Button("DNS-over-TLS", action: self.addNewDoTServer)
Button("DNS-over-HTTPS", action: self.addNewDoHServer)
Button("Restore from Presets") {
self.isRestoring = true
}
}
if self.usedID == self.servers[i].id.uuidString {
Spacer()
Image(systemName: "checkmark")
.sheet(isPresented: self.$isRestoring) {
RestorationView(onAdd: self.restoreFromPresets)
}
}
private var statusIndicator: some View {
Label {
Text(self.isActivated ? "Active" : "Inactive")
} icon: {
Circle()
.fill(self.isActivated ? .green : .secondary)
.frame(width: 10, height: 10)
}
.labelStyle(.titleAndIcon)
}
private func sidebarRow(at i: Int) -> some View {
HStack {
VStack(alignment: .leading) {
Text(self.servers[i].name)
Text(self.servers[i].configuration.description)
.foregroundStyle(.secondary)
}
if self.usedID == self.servers[i].id.uuidString {
Spacer()
if !self.isActivated {
Image(systemName: "exclamationmark.triangle")
.foregroundStyle(.red)
}
Image(systemName: "checkmark")
}
}
}
private func detailView(at i: Int) -> some View {
DetailView(
server: self.$servers[i],
isOn: .init(
isSelected: .init(
get: {
self.usedID == self.servers[i].id.uuidString
},
@ -304,13 +408,12 @@ extension ContentView: View {
self.removeSettings()
}
}
)
),
isActivated: self.$isActivated
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(servers: .constant(Presets.servers), usedID: .constant(nil))
}
#Preview {
ContentView(servers: .constant(Presets.servers), usedID: .constant(nil))
}

View file

@ -0,0 +1,50 @@
import NetworkExtension
import SwiftUI
struct DNSSearchDomainMatchView {
@Binding var rule: OnDemandRule
}
extension DNSSearchDomainMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.dnsSearchDomainMatch.count, id: \.self) { i in
LazyTextField(
"Search Domain",
// self.$rule.dnsSearchDomainMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsSearchDomainMatch[i] },
set: { self.rule.dnsSearchDomainMatch[i] = $0 }
)
)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.dnsSearchDomainMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsSearchDomainMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Search Domain") {
self.rule.dnsSearchDomainMatch.append("")
}
} footer: {
Text(
"If the current default search domain is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("DNS Search Domain Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
DNSSearchDomainMatchView(rule: $rule)
}
}

View file

@ -0,0 +1,50 @@
import NetworkExtension
import SwiftUI
struct DNSServerAddressMatchView {
@Binding var rule: OnDemandRule
}
extension DNSServerAddressMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.dnsServerAddressMatch.count, id: \.self) { i in
LazyTextField(
"IP Address",
// self.$rule.dnsServerAddressMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsServerAddressMatch[i] },
set: { self.rule.dnsServerAddressMatch[i] = $0 }
)
)
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.dnsServerAddressMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsServerAddressMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Server Address") {
self.rule.dnsServerAddressMatch.append("")
}
} footer: {
Text(
"If each of the current default DNS servers is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("DNS Server Address Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
DNSServerAddressMatchView(rule: $rule)
}
}

View file

@ -9,85 +9,65 @@ import SwiftUI
struct DetailView {
@Binding var server: Resolver
@Binding var isOn: Bool
@Binding var isSelected: Bool
@Binding var isActivated: Bool
@State private var isGuidePresented = false
private func binding(for rule: OnDemandRule) -> Binding<OnDemandRule> {
guard let index = self.server.onDemandRules.firstIndex(of: rule) else {
private func binding(for id: UUID) -> Binding<OnDemandRule> {
guard let index = self.server.onDemandRules.map(\.id).firstIndex(of: id) else {
preconditionFailure("Can't find rule in array")
}
return self.$server.onDemandRules[index]
}
}
@MainActor
extension DetailView: View {
var body: some View {
if #available(iOS 16, *) {
self.modernBody
} else {
self.legacyBody
}
}
@available(iOS 16, *)
private var modernBody: some View {
NavigationStack {
Form {
Section {
Toggle("Use This Server", isOn: self.$isOn)
}
Section {
HStack {
Text("Name")
TextField("Name", text: self.$server.name)
.multilineTextAlignment(.trailing)
}
}
self.serverConfigurationSections
Section {
ForEach(self.server.onDemandRules) { rule in
NavigationLink(rule.name, value: rule)
}
.onDelete { self.server.onDemandRules.remove(atOffsets: $0) }
.onMove { self.server.onDemandRules.move(fromOffsets: $0, toOffset: $1) }
Button("Add New Rule") {
self.server.onDemandRules
.append(OnDemandRule(name: "New Rule"))
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("On Demand Rules")
}
}
}
.navigationDestination(for: OnDemandRule.self) { rule in
// When RuleView is opened and tap another server on the sidebar, the previous server's rule comes here.
if self.server.onDemandRules.contains(rule) {
RuleView(rule: self.binding(for: rule))
}
}
}
.navigationTitle(self.server.name)
}
private var legacyBody: some View {
Form {
Section {
Toggle("Use This Server", isOn: self.$isOn)
}
Section {
HStack {
Text("Name")
TextField("Name", text: self.$server.name)
.multilineTextAlignment(.trailing)
Toggle("Use This Server", isOn: self.$isSelected)
if self.isSelected && !self.isActivated {
Button("One more step is required.", systemImage: "exclamationmark.triangle") {
self.isGuidePresented = true
}
.labelStyle(.titleAndIcon)
.tint(.red)
.sheet(isPresented: self.$isGuidePresented) {
NavigationView {
HowToActivateView()
.toolbar {
ToolbarItem(placement: .cancellationAction) {
#if canImport(SwiftUI, _version: 7)
if #available(iOS 26, *) {
Button("Close", systemImage: "xmark", role: .close) {
self.isGuidePresented = false
}
} else {
Button("Close", systemImage: "xmark", role: .cancel) {
self.isGuidePresented = false
}
}
#else
Button("Close", systemImage: "xmark", role: .cancel) {
self.isGuidePresented = false
}
#endif
}
}
}
.navigationViewStyle(.stack)
}
}
}
Section("Name") {
LazyTextField("Name", text: self.$server.name)
}
self.serverConfigurationSections
Section {
ForEach(self.server.onDemandRules) { rule in
NavigationLink(rule.name) {
RuleView(rule: self.binding(for: rule))
RuleView(rule: self.binding(for: rule.id))
}
}
.onDelete { self.server.onDemandRules.remove(atOffsets: $0) }
@ -100,33 +80,47 @@ extension DetailView: View {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("On Demand Rules")
Text("On-Demand Rules")
}
}
}
.navigationTitle(self.server.name)
.onChange(of: self.server) { [oldValue = self.server] newValue in
guard self.isSelected, oldValue.id == newValue.id else { return }
// When the selected server's configuration is modified, the server will be deactivated or deselected so that the user can save the changes by toggling the Use This Server switch again.
self.isSelected = false
}
}
@ViewBuilder private var serverConfigurationSections: some View {
switch self.server.configuration {
case .dnsOverTLS(let configuration):
DoTSections(server: self.$server, configuration: configuration)
DoTSections(
configuration: .init(
get: { configuration },
set: { self.server.configuration = .dnsOverTLS($0) }
)
)
case .dnsOverHTTPS(let configuration):
DoHSections(server: self.$server, configuration: configuration)
DoHSections(
configuration: .init(
get: { configuration },
set: { self.server.configuration = .dnsOverHTTPS($0) }
)
)
}
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(
server: .constant(
.init(
name: "My Server",
configuration: .dnsOverTLS(DoTConfiguration())
)
),
isOn: .constant(true)
)
}
#Preview {
DetailView(
server: .constant(
.init(
name: "My Server",
configuration: .dnsOverTLS(DoTConfiguration())
)
),
isSelected: .constant(true),
isActivated: .constant(true)
)
}

View file

@ -7,52 +7,24 @@
import SwiftUI
private enum FocusedField {
case address
case serverURL
}
struct DoHSections {
@Binding var server: Resolver
@State var configuration: DoHConfiguration
@FocusState private var focusedField: FocusedField?
private func commit() {
self.server.configuration = .dnsOverHTTPS(self.configuration)
}
@Binding var configuration: DoHConfiguration
}
extension DoHSections: View {
var body: some View {
Section {
ForEach(0..<self.configuration.servers.count, id: \.self) { i in
TextField(
"IP address",
text: .init(
get: { self.configuration.servers[i] },
set: {
self.configuration.servers[i] = $0
.trimmingCharacters(in: .whitespacesAndNewlines)
}
)
)
.focused(self.$focusedField, equals: .address)
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.autocapitalization(.none)
.disableAutocorrection(true)
}
.onDelete {
self.configuration.servers.remove(atOffsets: $0)
self.commit()
}
.onMove {
self.configuration.servers.move(fromOffsets: $0, toOffset: $1)
self.commit()
LazyTextField("IP address", text: self.$configuration.servers[i])
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.configuration.servers.remove(atOffsets: $0) }
.onMove { self.configuration.servers.move(fromOffsets: $0, toOffset: $1) }
Button("Add New Server") {
configuration.servers.append("")
self.commit()
self.configuration.servers.append("")
}
} header: {
EditButton()
@ -64,67 +36,39 @@ extension DoHSections: View {
Text("The DNS server IP addresses.")
}
Section {
HStack {
Text("Server URL")
Spacer()
TextField(
"Server URL",
text: .init(
get: {
configuration.serverURL?.absoluteString ?? ""
},
set: {
configuration.serverURL = URL(
string: $0.trimmingCharacters(in: .whitespacesAndNewlines)
)
}
)
LazyTextField(
"Server URL",
text: .init(
get: { self.configuration.serverURL?.absoluteString ?? "" },
set: { self.configuration.serverURL = URL(string: $0) }
)
.focused(self.$focusedField, equals: .serverURL)
.multilineTextAlignment(.trailing)
.textContentType(.URL)
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
}
)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
} header: {
Text("DNS-over-HTTPS Settings")
Text("Server URL")
} footer: {
Text("The URL of a DNS-over-HTTPS server.")
}
.onChange(of: self.focusedField) { newValue in
if newValue == nil {
self.commit()
}
}
.onChange(of: self.server) { server in
switch server.configuration {
case .dnsOverTLS:
preconditionFailure("unreachable")
case .dnsOverHTTPS(let configuration):
self.configuration = configuration
}
}
.onDisappear {
self.commit()
}
}
}
struct DoHSections_Previews: PreviewProvider {
static var previews: some View {
let configuration = DoHConfiguration(
servers: [
"1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
],
serverURL: URL(string: "https://cloudflare-dns.com/dns-query")
#Preview {
Form {
DoHSections(
configuration: .constant(
.init(
servers: [
"1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
],
serverURL: URL(string: "https://cloudflare-dns.com/dns-query")
)
)
)
let resolver = Resolver(name: "1.1.1.1", configuration: .dnsOverHTTPS(configuration))
Form {
DoHSections(server: .constant(resolver), configuration: configuration)
}
}
}

View file

@ -7,52 +7,24 @@
import SwiftUI
private enum FocusedField {
case address
case serverName
}
struct DoTSections {
@Binding var server: Resolver
@State var configuration: DoTConfiguration
@FocusState private var focusedField: FocusedField?
private func commit() {
self.server.configuration = .dnsOverTLS(self.configuration)
}
@Binding var configuration: DoTConfiguration
}
extension DoTSections: View {
var body: some View {
Section {
ForEach(0..<self.configuration.servers.count, id: \.self) { i in
TextField(
"IP address",
text: .init(
get: { self.configuration.servers[i] },
set: {
self.configuration.servers[i] = $0
.trimmingCharacters(in: .whitespacesAndNewlines)
}
)
)
.focused(self.$focusedField, equals: .address)
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.autocapitalization(.none)
.disableAutocorrection(true)
}
.onDelete {
self.configuration.servers.remove(atOffsets: $0)
self.commit()
}
.onMove {
self.configuration.servers.move(fromOffsets: $0, toOffset: $1)
self.commit()
LazyTextField("IP address", text: self.$configuration.servers[i])
.textContentType(.URL)
.keyboardType(.numbersAndPunctuation)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.configuration.servers.remove(atOffsets: $0) }
.onMove { self.configuration.servers.move(fromOffsets: $0, toOffset: $1) }
Button("Add New Server") {
self.configuration.servers.append("")
self.commit()
}
} header: {
EditButton()
@ -64,64 +36,39 @@ extension DoTSections: View {
Text("The DNS server IP addresses.")
}
Section {
HStack {
Text("Server Name")
Spacer()
TextField(
"Server Name",
text: .init(
get: { self.configuration.serverName ?? "" },
set: {
self.configuration.serverName = $0
.trimmingCharacters(in: .whitespacesAndNewlines)
}
)
LazyTextField(
"Server Name",
text: .init(
get: { self.configuration.serverName ?? "" },
set: { self.configuration.serverName = $0 }
)
.focused(self.$focusedField, equals: .serverName)
.multilineTextAlignment(.trailing)
.textContentType(.URL)
.keyboardType(.URL)
.autocapitalization(.none)
.disableAutocorrection(true)
}
)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
} header: {
Text("DNS-over-TLS Settings")
Text("Server Name")
} footer: {
Text("The TLS name of a DNS-over-TLS server.")
}
.onChange(of: self.focusedField) { newValue in
if newValue == nil {
self.commit()
}
}
.onChange(of: self.server) { server in
switch server.configuration {
case .dnsOverTLS(let configuration):
self.configuration = configuration
case .dnsOverHTTPS:
preconditionFailure("unreachable")
}
}
.onDisappear {
self.commit()
}
}
}
struct DoTSections_Previews: PreviewProvider {
static var previews: some View {
let configuration = DoTConfiguration(
servers: [
"1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
],
serverName: "cloudflare-dns.com"
#Preview {
Form {
DoTSections(
configuration: .constant(
.init(
servers: [
"1.1.1.1",
"1.0.0.1",
"2606:4700:4700::1111",
"2606:4700:4700::1001",
],
serverName: "cloudflare-dns.com"
)
)
)
let resolver = Resolver(name: "1.1.1.1", configuration: .dnsOverTLS(configuration))
Form {
DoTSections(server: .constant(resolver), configuration: configuration)
}
}
}

View file

@ -0,0 +1,49 @@
import SwiftUI
struct ExcludedDomainsView {
@Binding var domains: [String]
}
extension ExcludedDomainsView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.domains.count, id: \.self) { i in
LazyTextField(
"Domain",
// self.$rule.excludedDomains[i] causes crash on deletion
text: .init(
get: { self.domains[i] },
set: { self.domains[i] = $0 }
)
)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.domains.remove(atOffsets: $0) }
.onMove { self.domains.move(fromOffsets: $0, toOffset: $1) }
Button("Add Domain") {
self.domains.append("")
}
} footer: {
Text(
"Each domain is matched against the destination hostname using suffix matching, and each label in the domain must match an entire label in the hostname. For example, the domain `example.com` will match the hostname `www.example.com` but not `www.anotherexample.com`."
)
}
}
.navigationTitle("Excluded Domains")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var domains = ["example.com"]
NavigationStack {
ExcludedDomainsView(domains: $domains)
}
}

View file

@ -7,20 +7,27 @@
import SwiftUI
private enum MacOSVersion {
case venturaOrLater
case monterey
}
struct HowToActivateView {
@Environment(\.dismiss) private var dismiss
let isSheet: Bool
@State private var macOSVersion: MacOSVersion = .venturaOrLater
}
extension HowToActivateView: View {
var body: some View {
VStack {
if self.isSheet {
Text("How to Activate")
.font(.title)
Spacer()
}
ScrollView {
ScrollView {
VStack {
#if targetEnvironment(macCatalyst)
Picker("", selection: self.$macOSVersion) {
Text("macOS 13 or later").tag(MacOSVersion.venturaOrLater)
Text("macOS 12").tag(MacOSVersion.monterey)
}
.pickerStyle(.segmented)
.fixedSize()
#endif
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("1. Select a DNS server you like, or add another one")
@ -37,44 +44,81 @@ extension HowToActivateView: View {
.frame(maxHeight: 200)
}
#if targetEnvironment(macCatalyst)
VStack(alignment: .leading) {
Text("3. Open the System Preferences")
Image("SystemPreferencesIcon")
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("4. Go to Network settings")
Image("SystemPreferences")
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
HStack {
Text("5. Select \"\(Bundle.main.displayName!)\" and click")
Image(systemName: "ellipsis.circle")
Text("button")
switch self.macOSVersion {
case .venturaOrLater:
VStack(alignment: .leading) {
Text("3. Open the System Settings")
Image(.systemSettingsIcon)
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("4. Go to Network settings and click \"Filters\"")
Image(.networkSettings)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
Text("5. Click on the status of \"\(Bundle.main.displayName!)\"")
Image(.filtersSettings)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
Text("6. Click \"Enabled\"")
Image(.makeItEnabled)
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("7. All done 🎉")
Image(.allDone)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
case .monterey:
VStack(alignment: .leading) {
Text("3. Open the System Preferences")
Image(.montereySystemPreferencesIcon)
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("4. Go to Network settings")
Image(.montereySystemPreferences)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
Text(
"5. Select \"\(Bundle.main.displayName!)\" and click \(Image(systemName: "ellipsis.circle")) button"
)
Image(.montereyNetworkSettings)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
Text("6. Click \"Make Service Active\"")
Image(.montereyMakeServiceActive)
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("7. Click \"Apply\" button")
Image(.montereyNetworkSettingsApply)
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
Image("NetworkSettings")
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
VStack(alignment: .leading) {
Text("6. Click \"Make Service Active\"")
Image("MakeServiceActive")
.resizable()
.scaledToFit()
.frame(maxHeight: 200)
}
VStack(alignment: .leading) {
Text("7. Click \"Apply\" button")
Image("NetworkSettingsApply")
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
}
#else
VStack(alignment: .leading) {
@ -109,23 +153,12 @@ extension HowToActivateView: View {
#endif
}
}
if self.isSheet {
Spacer()
Button {
self.dismiss()
} label: {
Text("Dismiss").padding()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
.padding()
.navigationTitle("How to Activate")
}
}
struct HowToActivateView_Previews: PreviewProvider {
static var previews: some View {
HowToActivateView(isSheet: true)
}
#Preview {
HowToActivateView()
}

View file

@ -0,0 +1,35 @@
import NetworkExtension
import SwiftUI
struct InterfaceTypeMatchView {
@Binding var rule: OnDemandRule
}
extension InterfaceTypeMatchView: View {
var body: some View {
Form {
Section {
Picker("Interface Type", selection: self.$rule.interfaceType) {
ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) {
Text($0.description)
}
}
.pickerStyle(.inline)
.labelsHidden()
} footer: {
Text(
"If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("Interface Type Match")
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
InterfaceTypeMatchView(rule: $rule)
}
}

View file

@ -0,0 +1,53 @@
//
// LazyTextField.swift
// DNSecure
//
// Created by Kenta Kubo on 4/25/23.
//
import SwiftUI
struct LazyTextField {
private let title: String
@Binding private var text: String
@State private var localText: String
@FocusState private var isFocused
init(_ title: some StringProtocol, text: Binding<String>) {
self.title = String(title)
self._text = text
self._localText = .init(initialValue: text.wrappedValue)
}
}
extension LazyTextField: View {
var body: some View {
HStack {
TextField(self.title, text: self.$localText)
.focused(self.$isFocused)
if self.isFocused && !self.localText.isEmpty {
Button("Clear", systemImage: "xmark.circle.fill") {
self.text.removeAll()
self.localText.removeAll()
}
.foregroundStyle(Color.primary)
.opacity(0.2)
.labelStyle(.iconOnly)
.buttonStyle(.borderless)
.hoverEffect()
}
}
.onChange(of: self.isFocused) { isFocused in
if !isFocused {
self.text = self.localText
}
}
.onChange(of: self.text) { text in
self.localText = text
}
}
}
#Preview {
LazyTextField("Name", text: .constant(""))
}

View file

@ -0,0 +1,40 @@
import NetworkExtension
import SwiftUI
struct ProbeURLView {
@Binding var rule: OnDemandRule
private var probeURL: Binding<String> {
.init(
get: { self.rule.probeURL?.absoluteString ?? "" },
set: { self.rule.probeURL = URL(string: $0) }
)
}
}
extension ProbeURLView: View {
var body: some View {
Form {
Section {
LazyTextField("Probe URL", text: self.probeURL)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
} footer: {
Text(
"If a request sent to this URL results in a HTTP 200 OK response and all of the other conditions in the rule match, then the rule matches. If you don't want to use this rule, leave it empty."
)
}
}
.navigationTitle("Probe URL")
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
ProbeURLView(rule: $rule)
}
}

View file

@ -0,0 +1,71 @@
//
// RestorationView.swift
// DNSecure
//
// Created by Kenta Kubo on 7/17/24.
//
import SwiftUI
struct RestorationView {
@Environment(\.dismiss) private var dismiss
@State private var added = Set<Resolver>()
@State private var keyword = ""
let onAdd: (Resolver) -> Void
private var servers: Resolvers {
guard !self.keyword.isEmpty else { return Presets.servers }
return Presets.servers.filter { $0.name.localizedCaseInsensitiveContains(self.keyword) }
}
}
extension RestorationView: View {
var body: some View {
NavigationView {
List(self.servers, id: \.self) { resolver in
HStack {
VStack(alignment: .leading) {
Text(resolver.name)
Text(resolver.configuration.description)
.foregroundStyle(.secondary)
}
Spacer()
if self.added.contains(resolver) {
HStack(spacing: 5) {
Image(systemName: "checkmark")
Text("Added")
}
.foregroundStyle(.secondary)
} else {
Button {
self.added.insert(resolver)
self.onAdd(resolver)
} label: {
HStack(spacing: 5) {
Image(systemName: "plus")
Text("Add")
}
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.hoverEffect()
}
}
}
.navigationTitle("Presets")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel", systemImage: "xmark", role: .cancel) {
self.dismiss()
}
}
}
}
.navigationViewStyle(.stack)
.searchable(text: self.$keyword, placement: .navigationBarDrawer(displayMode: .always))
}
}
#Preview {
RestorationView { _ in }
}

View file

@ -12,140 +12,201 @@ struct RuleView {
@Binding var rule: OnDemandRule
}
@MainActor
extension RuleView: View {
var body: some View {
Form {
Section {
HStack {
Text("Name")
TextField("Name", text: self.$rule.name)
.multilineTextAlignment(.trailing)
Section("Name") {
LazyTextField("Name", text: self.$rule.name)
}
Section("Matching Conditions") {
NavigationLink {
InterfaceTypeMatchView(rule: self.$rule)
} label: {
self.interfaceTypeMatchLabel
}
if self.rule.interfaceType.isSSIDUsed {
NavigationLink {
SSIDMatchView(rule: self.$rule)
} label: {
self.ssidMatchLabel
}
}
NavigationLink {
DNSSearchDomainMatchView(rule: self.$rule)
} label: {
self.dnsSearchDomainMatchLabel
}
NavigationLink {
DNSServerAddressMatchView(rule: self.$rule)
} label: {
self.dnsServerAddressMatchLabel
}
NavigationLink {
ProbeURLView(rule: self.$rule)
} label: {
self.probeURLLabel
}
}
Section {
Picker("Action", selection: self.$rule.action) {
ForEach(NEOnDemandRuleAction.allCases, id: \.self) {
ForEach(
[
NEOnDemandRuleAction.connect,
.evaluateConnection,
.disconnect,
],
id: \.self
) {
Text($0.description)
}
}
}
Section {
Picker("Interface Type", selection: self.$rule.interfaceType) {
ForEach(NEOnDemandRuleInterfaceType.allCases, id: \.self) {
Text($0.description)
}
}
} header: {
Text("Interface Type Match")
} footer: {
Text("If the current primary network interface is of this type and all of the other conditions in the rule match, then the rule matches.")
}
if self.rule.interfaceType.ssidIsUsed {
Section {
ForEach(0..<self.rule.ssidMatch.count, id: \.self) { i in
TextField(
"SSID",
// self.$rule.ssidMatch[i] causes crash on deletion
text: .init(
get: { self.rule.ssidMatch[i] },
set: { self.rule.ssidMatch[i] = $0 }
if self.rule.action == .evaluateConnection {
NavigationLink {
ExcludedDomainsView(
domains: .init(
get: { self.rule.excludedDomains ?? [] },
set: { self.rule.excludedDomains = $0 }
)
)
}
.onDelete { self.rule.ssidMatch.remove(atOffsets: $0) }
.onMove { self.rule.ssidMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add SSID") {
NEHotspotNetwork.fetchCurrent { network in
self.rule.ssidMatch.append(network?.ssid ?? "")
} label: {
HStack {
Text("Excluded Domains")
Spacer()
Text("\(self.rule.excludedDomains?.count ?? 0)")
.foregroundStyle(.secondary)
}
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("SSID Match")
}
} footer: {
Text("If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
}
}
Section {
ForEach(0..<self.rule.dnsSearchDomainMatch.count, id: \.self) { i in
TextField(
"Search Domain",
// self.$rule.dnsSearchDomainMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsSearchDomainMatch[i] },
set: { self.rule.dnsSearchDomainMatch[i] = $0 }
)
)
}
.onDelete { self.rule.dnsSearchDomainMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsSearchDomainMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Search Domain") {
self.rule.dnsSearchDomainMatch.append("")
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("DNS Search Domain Match")
}
} footer: {
Text("If the current default search domain is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
}
Section {
ForEach(0..<self.rule.dnsServerAddressMatch.count, id: \.self) { i in
TextField(
"IP Address",
// self.$rule.dnsServerAddressMatch[i] causes crash on deletion
text: .init(
get: { self.rule.dnsServerAddressMatch[i] },
set: { self.rule.dnsServerAddressMatch[i] = $0 }
)
)
}
.onDelete { self.rule.dnsServerAddressMatch.remove(atOffsets: $0) }
.onMove { self.rule.dnsServerAddressMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add DNS Server Address") {
self.rule.dnsServerAddressMatch.append("")
}
} header: {
EditButton()
.frame(maxWidth: .infinity, alignment: .trailing)
.overlay(alignment: .leading) {
Text("DNS Server Address Match")
}
} footer: {
Text("If each of the current default DNS servers is equal to one of the strings in this array and all of the other conditions in the rule match, then the rule matches.")
}
Section {
HStack {
Text("Probe URL")
TextField(
"URL",
text: .init(
get: { self.rule.probeURL?.absoluteString ?? "" },
set: { self.rule.probeURL = URL(string: $0) }
)
)
.multilineTextAlignment(.trailing)
}
} header: {
Text("Probe URL Match")
} footer: {
Text("If a request sent to this URL results in a HTTP 200 OK response and all of the other conditions in the rule match, then the rule matches. If you don't want to use this rule, leave it empty.")
}
}
.navigationTitle(self.rule.name)
}
}
struct RuleView_Previews: PreviewProvider {
static var previews: some View {
RuleView(rule: .constant(OnDemandRule(name: "Preview Rule")))
@ViewBuilder private var interfaceTypeMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("Interface Type Match", value: self.rule.interfaceType.description)
} else {
HStack {
Text("Interface Type Match")
Spacer()
Text(self.rule.interfaceType.description)
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var ssidMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("SSID Match") {
if !self.rule.ssidMatch.isEmpty {
Text("\(self.rule.ssidMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("SSID Match")
Spacer()
Group {
if !self.rule.ssidMatch.isEmpty {
Text("\(self.rule.ssidMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var dnsSearchDomainMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("DNS Search Domain Match") {
if !self.rule.dnsSearchDomainMatch.isEmpty {
Text("\(self.rule.dnsSearchDomainMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("DNS Search Domain Match")
Spacer()
Group {
if !self.rule.dnsSearchDomainMatch.isEmpty {
Text("\(self.rule.dnsSearchDomainMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var dnsServerAddressMatchLabel: some View {
if #available(iOS 16, *) {
LabeledContent("DNS Server Address Match") {
if !self.rule.dnsServerAddressMatch.isEmpty {
Text("\(self.rule.dnsServerAddressMatch.count)")
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("DNS Server Address Match")
Spacer()
Group {
if !self.rule.dnsServerAddressMatch.isEmpty {
Text("\(self.rule.dnsServerAddressMatch.count)")
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
@ViewBuilder private var probeURLLabel: some View {
if #available(iOS 16, *) {
LabeledContent("Probe URL") {
if let url = self.rule.probeURL?.absoluteString {
Text(url)
} else {
Text("Not Used")
}
}
} else {
HStack {
Text("Probe URL")
Spacer()
Group {
if let url = self.rule.probeURL?.absoluteString {
Text(url)
} else {
Text("Not Used")
}
}
.foregroundStyle(.secondary)
}
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
RuleView(rule: $rule)
}
}

View file

@ -0,0 +1,50 @@
import NetworkExtension
import SwiftUI
struct SSIDMatchView {
@Binding var rule: OnDemandRule
}
extension SSIDMatchView: View {
var body: some View {
Form {
Section {
ForEach(0..<self.rule.ssidMatch.count, id: \.self) { i in
LazyTextField(
"SSID",
// self.$rule.ssidMatch[i] causes crash on deletion
text: .init(
get: { self.rule.ssidMatch[i] },
set: { self.rule.ssidMatch[i] = $0 }
)
)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
.onDelete { self.rule.ssidMatch.remove(atOffsets: $0) }
.onMove { self.rule.ssidMatch.move(fromOffsets: $0, toOffset: $1) }
Button("Add SSID") {
NEHotspotNetwork.fetchCurrent { network in
self.rule.ssidMatch.append(network?.ssid ?? "")
}
}
} footer: {
Text(
"If the Service Set Identifier (SSID) of the current primary connected network matches one of the strings in this array and all of the other conditions in the rule match, then the rule matches."
)
}
}
.navigationTitle("SSID Match")
.toolbar {
EditButton()
}
}
}
@available(iOS 17, *)
#Preview {
@Previewable @State var rule = OnDemandRule(name: "Preview Rule")
NavigationStack {
SSIDMatchView(rule: $rule)
}
}

View file

@ -5,29 +5,6 @@
// Created by Kenta Kubo on 7/1/20.
//
import XCTest
import Testing
@testable import DNSecure
class DNSecureTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -7,8 +7,7 @@
import XCTest
class DNSecureUITests: XCTestCase {
final class DNSecureUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
@ -22,21 +21,20 @@ class DNSecureUITests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}

View file

@ -0,0 +1,32 @@
//
// DNSecureUITestsLaunchTests.swift
// DNSecureUITests
//
// Created by Kenta Kubo on 9/3/25.
//
import XCTest
final class DNSecureUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}

View file

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Kenta Kubo
Copyright (c) 2020-2026 Kenta Kubo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

51
Package.swift Normal file
View file

@ -0,0 +1,51 @@
// swift-tools-version: 6.0
// WARNING:
// This file is automatically generated.
// Do not edit it by hand because the contents will be replaced.
import AppleProductTypes
import PackageDescription
let package = Package(
name: "DNSecure",
platforms: [
.iOS("16.1")
],
products: [
.iOSApplication(
name: "DNSecure",
targets: ["DNSecure"],
bundleIdentifier: "xyz.kebo.DNSecure",
teamIdentifier: "X4678G5DL2",
displayVersion: "1.6.2",
bundleVersion: "1",
appIcon: .asset("AppIcon-legacy"),
accentColor: .asset("AccentColor"),
supportedDeviceFamilies: [
.pad,
.phone,
],
supportedInterfaceOrientations: [
.portrait,
.landscapeRight,
.landscapeLeft,
.portraitUpsideDown(.when(deviceFamilies: [.pad])),
],
appCategory: .utilities
)
],
targets: [
.executableTarget(
name: "DNSecure",
path: "DNSecure"
),
.testTarget(
name: "DNSecureTests",
dependencies: [
.target(name: "DNSecure")
],
path: "DNSecureTests"
),
]
)

View file

@ -4,15 +4,11 @@ iOS 14+, iPadOS 14+, and macOS 11+ have supported encrypted DNS (e.g. DNS-over-T
This app uses the new [DNS Settings API](https://developer.apple.com/documentation/networkextension/dns_settings), so it requires iOS 14+, iPadOS 14+, or macOS 11+.
## Installation (iOS/iPadOS)
## Installation (iOS/iPadOS/macOS)
<a href="https://apps.apple.com/us/app/dnsecure/id1533413232?itsct=apps_box&amp;itscg=30200" style="display: inline-block; overflow: hidden; border-top-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; border-bottom-left-radius: 13px; width: 250px; height: 83px;"><img src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-US?size=250x83&amp;releaseDate=1601251200&h=77f35e8e1cad98287ffaa894b10bb6e2" alt="Download on the App Store" style="border-top-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; border-bottom-left-radius: 13px; width: 250px; height: 83px;"></a>
<a href="https://apps.apple.com/us/app/dnsecure/id1533413232?itscg=30200&itsct=apps_box_badge&mttnsubad=1533413232" style="display: inline-block;"><img src="https://toolbox.marketingtools.apple.com/api/v2/badges/download-on-the-app-store/black/en-us?releaseDate=1601251200" alt="Download on the App Store" style="width: 245px; height: 82px; vertical-align: middle; object-fit: contain;" /></a>
or [TestFlight Beta](https://testflight.apple.com/join/A8GwCnq8)
## Installation (macOS)
<a href="https://apps.apple.com/us/app/dnsecure/id1533413232?itsct=apps_box&amp;itscg=30200" style="display: inline-block; overflow: hidden; border-top-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; border-bottom-left-radius: 13px; width: 250px; height: 83px;"><img src="https://tools.applemediaservices.com/api/badges/download-on-the-mac-app-store/black/en-US?size=250x83&amp;releaseDate=1601251200&h=fccb9f75527e66852c3734e23031dc45" alt="Download on the Mac App Store" style="border-top-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; border-bottom-left-radius: 13px; width: 250px; height: 83px;"></a>
## How to use (iOS/iPadOS)
1. Select a DNS server you like, or add another one
@ -21,7 +17,15 @@ or [TestFlight Beta](https://testflight.apple.com/join/A8GwCnq8)
1. Go to "General" > "VPN & Network" > "DNS"
1. "Automatic" is selected by default, so select "DNSecure"
## How to use (macOS)
## How to use (macOS 13+)
1. Select a DNS server you like, or add another one
1. Enable "Use This Server"
1. Open the System Settings
1. Go to Network settings and click "Filters"
1. Enable "DNSecure"
## How to use (macOS 12)
1. Select a DNS server you like, or add another one
1. Enable "Use This Server"