Compare commits

..

129 Commits

Author SHA1 Message Date
Jason A. Donenfeld edde27a0a0 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-01-27 12:10:53 +01:00
Jason A. Donenfeld cfff596c30 wireguard-go-bridge: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-01-27 12:10:21 +01:00
Jason A. Donenfeld ba1c968cdf Update repo urls
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-12-30 11:54:13 +01:00
Jason A. Donenfeld c48406ac38 wireguard-go-bridge: style
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-20 09:59:21 +01:00
Jason A. Donenfeld 14437477e6 README: specify required version in readme
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 18:59:24 +08:00
Jason A. Donenfeld 68d928192b Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 17:25:24 +08:00
Roopesh Chander 028e76eb3f [REVERT ME SOON] TunnelsManager: Workaround for macOS Catalina deleting tunnels arbitrarily
In macOS Catalina, for some users, the tunnels get deleted arbitrarily
by the OS. It's not clear what triggers that.

As a workaround, in macOS Catalina, when we realize that tunnels have
been deleted outside the app, we reinstate those tunnels using the
information in the keychain.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-11-05 17:25:21 +08:00
Jason A. Donenfeld cb0c965294 wireguard-go-bridge: update to 1.13.4
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 17:20:31 +08:00
Jason A. Donenfeld d7ce621cb2 UI: iOS: more dark mode fixes
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-25 10:59:16 +02:00
Jason A. Donenfeld a1ca4f6eb5 wireguard-go-bridge: work around Go 1.13.3 regression
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-25 10:36:58 +02:00
Jason A. Donenfeld 437f0dc46d Revert "NetworkExtension: don't use exit(0) hack on Catalina"
This reverts commit 3619279a65d9a506fb13d7f24909b38a5202fa8f.

Still broken!

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 16:51:50 +02:00
Jason A. Donenfeld 547eabb4ae Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 14:53:34 +02:00
Jason A. Donenfeld bb16d3ebc8 iOS: UI: Make edit views full screen modal
This might be worse on the iPad. Oh well.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 14:51:04 +02:00
Jason A. Donenfeld 1b6170cbc9 NetworkExtension: don't use exit(0) hack on Catalina
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 11:44:13 +02:00
Diab Neiroukh 4c37a4b7a7 UI: iOS: adjust colors for iOS 13
To be compatible with Dark Mode, we need to change some of our
color references to be "dynamic".

Signed-off-by: Diab Neiroukh <officiallazerl0rd@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 00:01:17 +02:00
Jason A. Donenfeld 226166bdaf Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-12 22:20:37 +02:00
Jason A. Donenfeld 80fa72cabc iOS: UI: abort is optimized out in release builds
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-12 22:20:37 +02:00
Jason A. Donenfeld d976d159d0 Keychain: make verification errors only happen when we're sure it's due to not found
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-11 22:07:18 +02:00
Jason A. Donenfeld 84ca7fcf40 ui: add donation link
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-11 21:44:12 +02:00
Jason A. Donenfeld f120a6aab0 wireguard-go-bridge: reduce version checks and cleanup
We now rely on -trimpath which restricts us to >= 1.13, and the patch
application should fail too. This has the downside that the user will
need to clean their xcode project when they upgrade go, though.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-08 16:59:02 +02:00
Jason A. Donenfeld 0d8108d8da wireguard-go-bridge: update for 1.13
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-04 18:28:53 +02:00
Jason A. Donenfeld e072ebee58 UI: iOS: set CFBundleDisplayName to satisfy new ITMS-90783 error
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 14:42:43 +02:00
Jason A. Donenfeld c6767d9007 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 13:51:32 +02:00
Jason A. Donenfeld bb5760cca4 WgQuickConfig: Swift treats \r\n as a single character
let blah = "hello\nworld\ndoes\nthis\nwork"
print(blah.split(separator: "\n"))
//output: ["hello", "world", "does", "this", "work"]

let blah2 = "hello\r\nworld\r\ndoes\r\nthis\r\nwork"
print(blah2.split(separator: "\n"))
//output: ["hello\r\nworld\r\ndoes\r\nthis\r\nwork"]
//expected: ["hello\r", "world\r", "does\r", "this\r", "work\r"]

In blah2, the string splitting fails because swift considers \r\n to be
its own character.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 12:26:39 +02:00
Jason A. Donenfeld 26b7971ba6 UI: macOS: Show useful error message on .conf import
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 12:07:18 +02:00
Roopesh Chander b286ede3c6 iOS: Importing: If tunnelsManager isn't ready yet, we should wait for it
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-13 23:02:54 +05:30
Roopesh Chander 4ef7afe3ca macOS: Tunnel detail: Handle deletion outside app, again
This was previously done in commit f281b93, but the changes in commit
1507a97 for handling deletion of multiple tunnels undid this capability.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-13 22:21:31 +05:30
Jason A. Donenfeld 377f2f0496 TunnelsManager: store UID on macOS for keychain availability
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-11 02:18:42 +02:00
Jason A. Donenfeld 7ed5893fc6 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-10 18:58:18 +02:00
Jason A. Donenfeld e70c397e54 TunnelProvider: remove all cleverness
This will cause more socket flaps than necessary but hopefully will fix
some bugs.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-10 18:47:39 +02:00
Roopesh Chander 6a6be9edde on-demand: iOS: Fix crash on selecting Any SSID when already selected
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 23:55:44 +05:30
Roopesh Chander 37f8500fe6 on-demand: Don't crash on encountering unexpected on-demand rules
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 23:55:39 +05:30
Roopesh Chander 207f82dd9d macOS: Remove unused strings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 11:39:06 +02:00
Jason A. Donenfeld de8fedf87a Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
Jason A. Donenfeld 98d306da5b macOS: remove store update escape hatch
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
Jason A. Donenfeld c7b7b1247b TunnelProvider: store the entire NWPath
Otherwise [utun0, en0] == [en0, utun0] before WiFi has connected, and we
wind up not rebinding after WiFi does successfully connect, which means
people have trouble when resuming from sleep.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
Jason A. Donenfeld a66f13eb01 README: update repo location
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
Jason A. Donenfeld f50e7ae686 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-06 10:27:39 +02:00
Roopesh Chander 4d6692548c macOS: App menu > Quit shall show a prompt to quit or close window
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-06 10:27:11 +02:00
Roopesh Chander 1dccd39818 macOS: Save/restore the log window's size
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-04 20:34:37 +05:30
Roopesh Chander 4cb775c72f macOS: Log view: Allow resizing horizontally
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-04 15:48:42 +05:30
Jason A. Donenfeld 4cb783c447 go-bridge: bump version
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 19:20:51 +02:00
Jason A. Donenfeld d20daa345a Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:30:06 +02:00
Jason A. Donenfeld 168ba2da8a NetworkExtension: bump sockets on path change
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:29:29 +02:00
Roopesh Chander 714d6a41bd macOS: Dismiss modals correctly
Previously, the presented vc were leaking when discarding edits
or when closing the log view controller.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-31 17:29:29 +02:00
Roopesh Chander 9b92a8f933 macOS: Update app icon
Reduce the size and add a drop shadow

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-31 17:29:29 +02:00
Roopesh Chander 5e9780ef8f iOS: Should be able to re-show tunnel detail
Fixes a bug introduced in the refactoring in
commit 7322fb084087774e8b58e347902f6d7036cbde5c

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 15:36:39 +05:30
Roopesh Chander 9faf814e8b macOS: Tunnel detail: No need to update runtime info on tunnelSaved()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 14:43:41 +05:30
Roopesh Chander 30da10a0e9 macOS: Start refreshing runtime info in viewWillAppear(), not init()
Because when the window is closed and reopened, we should start
refreshing runtime info again.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 14:38:17 +05:30
Roopesh Chander a18614d6b3 macOS: Fix residual menu highlight on reopen
If we close the window with Cmd+W or Cmd+Q and then re-launch the app,
the main menu shows residual highlight from the close action. This
commit fixes that.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-26 00:12:47 +05:30
Jason A. Donenfeld 5100e597aa macOS: do not call out to recent tunnels tracker
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
Jason A. Donenfeld 0340641c4c NetworkExtension: apparently the extension process is scoped properly anyway
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
Jason A. Donenfeld 813dea6902 NetworkExtension: use excludedRoutes instead of binding on iOS
The networking stack there is to flaky and the notifier doesn't always
fire correctly. Hopefully excludedRoutes works well with XLAT; otherwise
we're in trouble.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
Roopesh Chander c30d491edc iOS: Should be able to call showTunnelDetail multiple times
And the detail views should not stack up.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-26 00:12:42 +05:30
Roopesh Chander 88c80d6694 iOS: Refactor showing of the tunnel detail
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
Roopesh Chander 393718dfaf iOS: Show Home screen quick actions for recent tunnels
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
Roopesh Chander f852b6f919 iOS: Keep track of most-recently-activated tunnels
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
Roopesh Chander 8926434682 macOS: Workaround for unresponsive main menu when launched from Xcode
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:21 +05:30
Roopesh Chander 493c7b102e macOS: Ignore bogus reopen because of login item helper
The bogus reopen occurs because the SMLoginItemSetEnabled actually runs
the helper app immediately. The helper app attempts to launch the main
app, causing a reopen Apple event (rapp) to be sent.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:21 +05:30
Roopesh Chander 717bc8a26f macOS: Workaround for unresponsive main menu after reopen
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:12 +05:30
Roopesh Chander e582155a10 macOS: Ensure window is shown on app reopening
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 15:19:57 +05:30
Roopesh Chander 70d19691a7 macOS: Simplify detecting the type of an Apple event
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:28 +05:30
Roopesh Chander 40b1f0bac8 macOS: Don't show manage window when launched at login
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:28 +05:30
Roopesh Chander 52ac9b82c2 macOS: Login item: Get helper app version from xcconfig
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander fc1fdbbcdb macOS: Login item: Fix Info.plist path
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 300268daa0 macOS: Show Manage Tunnels window on startup
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 9bf304a9ac macOS: Minor refactor of StatusMenuWindowDelegate
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 586a592b68 macOS: Disable 'Delete Selected' when nothing is selected
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander c0526d2efb macOS: Some menu item titles are automatically inferred
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander fdbd4f875e macOS: Use title-style capitalization for menu items
As per https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-anatomy/

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 4a037cc706 macOS: Make it clear that status menu Quit quits the app
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 5190fc2249 macOS: Quit in main menu shall just close the window
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 3f25d54dcc macOS: Get back removing tunnel using the Delete key
This now works only when the list view has focus

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander f9880907a2 macOS: Both list and detail main menu items should be always enabled
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 404fa741e8 macOS: swiftlint: Suppress incorrect warnings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 6e1f03e41c macOS: Set a main menu for the app
The main menu would be shown only when the manage tunnels window
is visible.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 6d8965e97d macOS: Remove custom key event handling
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 5e5481b69b macOS: Show app in dock when showing the manage tunnels window
This way, the app can participate in Cmd+Tab

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
Roopesh Chander 69b33c0fad macOS: Edit view: Save on Cmd+S
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 15:45:26 +05:30
Roopesh Chander 167e4f0bf2 macOS: Edit view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:23:52 +05:30
Roopesh Chander 6e3b28852a macOS: Log view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:21:42 +05:30
Roopesh Chander 5914e868ab iOS: Log view: Improve the look
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-28 14:29:22 +05:30
Jason A. Donenfeld 83ea9d6fa7 wireguard-go-bridge: add missing format specifier for error
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-24 13:24:03 +02:00
Jason A. Donenfeld b954e9a4fd Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-23 06:49:16 +02:00
Roopesh Chander 76894fba68 Xcode: Use dwarf for debug and dwarf-with-dsym for release
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 18:22:31 +05:30
Roopesh Chander 89a564ce62 Swift 5 migration: Make use of Result type
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 17:51:42 +05:30
Roopesh Chander 178fe86d36 macOS: Detect when updating from the App Store
And show an alert when tunnels are active during updation -- that
might cause the update to not work correctly.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 15:43:10 +05:30
Jason A. Donenfeld 571349bb3d Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-12 10:32:06 +02:00
Roopesh Chander 98ebd55208 Log view: Don't use a global array to store log entries
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-10 17:57:36 +05:30
Roopesh Chander 83d0d34411 macOS: Log view: Stop updating the log once the log view is dismissed
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-10 15:42:39 +05:30
Jason A. Donenfeld 8c7c4b6792 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 10:49:48 +02:00
Jason A. Donenfeld 5b34f49166 wireguard-go-bridge: bump again for version file placement
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 10:43:24 +02:00
Roopesh Chander cef3957875 Swift 5 migration: Handle changes in Data's pointer interface
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-09 11:25:04 +05:30
Roopesh Chander d9e88c51bd Swift 5 migration: Fix switch warnings
We now get a warning when switching over enums from system
frameworks even when we handle all public cases because
there can be future cases that aren't handled.

When such a future case is introduced, we'll get a warning.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-09 11:25:04 +05:30
Jason A. Donenfeld db876647d6 wireguard-go-bridge: version bump to new tag
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 07:44:50 +02:00
Roopesh Chander 90eb45e287 Xcode: Move to Swift 5.0
No code changes were necessary

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-07 16:42:36 +05:30
Roopesh Chander 9f3d86723a macOS: Minor fix to export panel texts
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 17:59:42 +05:30
Roopesh Chander 11063d0f88 macOS: Tunnels list: Suppress alert buttons when removing tunnels is in progress
Also refactor the deletion alert into a separate helper class

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 17:53:41 +05:30
Roopesh Chander 557ee4390b TunnelsManager: When setting a config, also set isAvailable cache
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 00:26:06 +05:30
Roopesh Chander 9ce42d152d macOS: Tunnels list: Show the confirmation alert till removal completes
Fix tunnel selection during deletion

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 00:08:45 +05:30
Roopesh Chander 4c1b2e1258 TunnelsManager: Fix comparing tunnels with tunnelProviders in reload()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-05 13:43:08 +05:30
Roopesh Chander 3bd611aa7c TunnelsManager: Cache isTunnelConfigurationAvailableInKeychain
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-05 13:29:17 +05:30
Roopesh Chander adbe0b065e macOS: Attempt to remove keychain item only if verified
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-04 15:29:25 +05:30
Roopesh Chander 6015661beb macOS: Simplify reusing of the detail VC when applicable
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-04 12:24:32 +05:30
Roopesh Chander 6e6a6b88fb macOS: Hide other-user tunnels in the status menu
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-03 19:18:53 +05:30
Roopesh Chander 9690365dd4 macOS: Better handling of tunnels created by another user
Previously, the tunnels just got deleted.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-03 19:04:12 +05:30
Roopesh Chander 0299c3929e iOS: Log view: Make log text selectable
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-01 23:29:15 +05:30
Roopesh Chander 0043233872 macOS: Log view: Fix autoscroll to end of log
Looks like the tableview doesn't know how much to scroll to get to the
end when we use usesAutomaticRowHeights. So we wait for the tableview
to realize its frame has changed and then scroll to the bottom of the
frame explicitly.

Also, we keep track of whether the scroll view is scrolled to the end or
not every time scrolling happens, not just when we add log entries to
the table.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-01 23:07:48 +05:30
Roopesh Chander a74dd24578 macOS: Bring app to front before 'exiting with an active tunnel' alert
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-29 00:17:37 +05:30
Roopesh Chander dd9506ecee macOS: If a sheet is being shown, ignore quit and bring window to front
Otherwise, the 'exiting with an active tunnel' alert could get queued up
to be shown after the current sheet is dismissed.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-29 00:17:37 +05:30
Jason A. Donenfeld 0c2eb003a0 wireguard-go-bridge: update deps
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-28 15:55:53 +01:00
Roopesh Chander f83f159f97 macOS: Log view: No need to disable Close button
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 19:32:58 +05:30
Roopesh Chander 6175de0438 iOS: Ability to view the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 19:28:27 +05:30
Roopesh Chander bd61be52e6 iOS: Xcode: Minor project rearrangement
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 14:10:42 +05:30
Roopesh Chander 909f88be70 macOS: Ability to view the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 13:57:06 +05:30
Roopesh Chander b7c3bd0d8c Add LogViewHelper
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-27 17:55:52 +05:30
Roopesh Chander 4237ab4a6f macOS: Syntax highlighter: Free spans array
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 17:53:34 +05:30
Roopesh Chander 0fcaf6debb macOS: Hide exclude private IPs when PrivateKey / PublicKey is missing
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 16:00:45 +05:30
Roopesh Chander dbd5ea1ff0 macOS: Syntax highlighter: Swift can bridge c strings automatically
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 15:31:02 +05:30
Roopesh Chander 9afe230c10 macOS: On Add new, Exclude Private IPs should remain hidden
because there aren't any peers in the bootstrapped config.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 15:15:26 +05:30
Roopesh Chander 4bdfbb518e Xcode: iOS: Remove armv7 as 'Required device capabilities'
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-20 14:23:56 +05:30
Jason A. Donenfeld fbe101eabb macOS: Privacy notice is provided by system dialogs
So it really doesn't make sense to add our own. This causes several
popups when trying to add a tunnel, which is madness.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-20 04:24:23 +01:00
Jason A. Donenfeld cda3170970 macOS: Login item: Add a simple login item
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:15:38 -06:00
Jason A. Donenfeld a5e7c3906b Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:25:38 +01:00
Jason A. Donenfeld b21fdfed67 wireguard-go-bridge: do not use getdirentries64 on macos
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:23:46 +01:00
Roopesh Chander 5f8843e247 iOS: Delete confirmation prompt should be a question
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-19 21:23:46 +01:00
Roopesh Chander 7a3f65fd2f macOS: Add 'Deactivate' status menu item
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-19 21:23:46 +01:00
Jason A. Donenfeld dca0fb29f6 Version: CFBundleVersion must always increase for macOS app store
So we'll just start doing it like that, then.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 06:00:40 +01:00
Jason A. Donenfeld af9c800af8 Swiftlint: variable_name -> identifier_name
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 22:26:13 -06:00
79 changed files with 2268 additions and 512 deletions
+2 -2
View File
@@ -136,5 +136,5 @@ Here's an example WireGuard configuration payload dictionary:
Configurations added via .mobileconfig will not be migrated into keychain until the WireGuard application is opened once.
[wg-quick(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
[wg(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
[wg-quick(8)]: https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
[wg(8)]: https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8
+6 -4
View File
@@ -1,12 +1,14 @@
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
This project contains an application for iOS and for macOS, as well as many components shared between the two of them. You may toggle between the two platforms by selecting the target from within Xcode.
## Building
- Clone this repo recursively:
- Clone this repo:
```
$ git clone --recursive https://git.zx2c4.com/wireguard-ios
$ cd wireguard-ios
$ git clone https://git.zx2c4.com/wireguard-apple
$ cd wireguard-apple
```
- Rename and populate developer team ID file:
@@ -16,7 +18,7 @@ $ cp WireGuard/WireGuard/Config/Developer.xcconfig.template WireGuard/WireGuard/
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
```
- Install swiftlint and go:
- Install swiftlint and go 1.13.4:
```
$ brew install swiftlint go
+1 -1
View File
@@ -20,6 +20,6 @@ opt_in_rules:
- unneeded_parentheses_in_closure_argument
- unused_import
- trailing_closure
variable_name:
identifier_name:
min_length:
warning: 0
+1 -1
View File
@@ -112,6 +112,6 @@ class Keychain {
static func verifyReference(called ref: Data) -> Bool {
return SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword,
kSecValuePersistentRef as String: ref] as CFDictionary,
nil) == errSecSuccess
nil) != errSecItemNotFound
}
}
+2 -2
View File
@@ -107,7 +107,7 @@ err:
return ret;
}
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void(*cb)(const char *, uint64_t))
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*cb)(const char *, uint64_t, void *))
{
struct log *log;
uint32_t l, i = cursor;
@@ -132,7 +132,7 @@ uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, vo
else
break;
}
cb(line->line, line->time_ns);
cb(line->line, line->time_ns, ctx);
cursor = (i + 1) % MAX_LINES;
}
free(log);
+1 -1
View File
@@ -11,7 +11,7 @@
struct log;
void write_msg_to_log(struct log *log, const char *tag, const char *msg);
int write_log_to_file(const char *file_name, const struct log *input_log);
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void(*)(const char *, uint64_t));
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*)(const char *, uint64_t, void *));
struct log *open_log(const char *file_name);
void close_log(struct log *log);
+32 -6
View File
@@ -13,8 +13,8 @@ extension Data {
return nil
}
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX))
out.withUnsafeMutableBytes { outBytes in
self.withUnsafeBytes { inBytes in
out.withUnsafeMutableInt8Bytes { outBytes in
self.withUnsafeUInt8Bytes { inBytes in
key_to_hex(outBytes, inBytes)
}
}
@@ -25,7 +25,7 @@ extension Data {
init?(hexKey hexString: String) {
self.init(repeating: 0, count: Int(WG_KEY_LEN))
if !self.withUnsafeMutableBytes { key_from_hex($0, hexString) } {
if !self.withUnsafeMutableUInt8Bytes { key_from_hex($0, hexString) } {
return nil
}
}
@@ -35,8 +35,8 @@ extension Data {
return nil
}
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64))
out.withUnsafeMutableBytes { outBytes in
self.withUnsafeBytes { inBytes in
out.withUnsafeMutableInt8Bytes { outBytes in
self.withUnsafeUInt8Bytes { inBytes in
key_to_base64(outBytes, inBytes)
}
}
@@ -47,8 +47,34 @@ extension Data {
init?(base64Key base64String: String) {
self.init(repeating: 0, count: Int(WG_KEY_LEN))
if !self.withUnsafeMutableBytes { key_from_base64($0, base64String) } {
if !self.withUnsafeMutableUInt8Bytes { key_from_base64($0, base64String) } {
return nil
}
}
}
extension Data {
func withUnsafeUInt8Bytes<R>(_ body: (UnsafePointer<UInt8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: UInt8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
mutating func withUnsafeMutableUInt8Bytes<R>(_ body: (UnsafeMutablePointer<UInt8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: UInt8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
mutating func withUnsafeMutableInt8Bytes<R>(_ body: (UnsafeMutablePointer<Int8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: Int8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
}
+7 -1
View File
@@ -36,12 +36,14 @@ extension Endpoint {
return "\(address):\(port)"
case .ipv6(let address):
return "[\(address)]:\(port)"
@unknown default:
fatalError()
}
}
init?(from string: String) {
// Separation of host and port is based on 'parse_endpoint' function in
// https://git.zx2c4.com/WireGuard/tree/src/tools/config.c
// https://git.zx2c4.com/wireguard-tools/tree/src/config.c
guard !string.isEmpty else { return nil }
let startOfPort: String.Index
let hostString: String
@@ -78,6 +80,8 @@ extension Endpoint {
return true
case .ipv6:
return true
@unknown default:
fatalError()
}
}
@@ -89,6 +93,8 @@ extension Endpoint {
return nil
case .ipv6:
return nil
@unknown default:
fatalError()
}
}
}
@@ -22,6 +22,9 @@ extension NETunnelProviderProtocol {
if passwordReference == nil {
return nil
}
#if os(macOS)
providerConfiguration = ["UID": getuid()]
#endif
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
if endpoints.count == 1 {
@@ -49,9 +52,9 @@ extension NETunnelProviderProtocol {
Keychain.deleteReference(called: ref)
}
func verifyConfigurationReference() -> Data? {
guard let ref = passwordReference else { return nil }
return Keychain.verifyReference(called: ref) ? ref : nil
func verifyConfigurationReference() -> Bool {
guard let ref = passwordReference else { return false }
return Keychain.verifyReference(called: ref)
}
@discardableResult
@@ -60,11 +63,25 @@ extension NETunnelProviderProtocol {
* in the keychain. But it's still useful to keep the migration
* around so that .mobileconfig files are easier.
*/
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
providerConfiguration = nil
guard passwordReference == nil else { return true }
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
return true
if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String {
#if os(macOS)
providerConfiguration = ["UID": getuid()]
#elseif os(iOS)
providerConfiguration = nil
#else
#error("Unimplemented")
#endif
guard passwordReference == nil else { return true }
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
return true
}
#if os(macOS)
if passwordReference != nil && providerConfiguration?["UID"] == nil && verifyConfigurationReference() {
providerConfiguration = ["UID": getuid()]
return true
}
#endif
return false
}
}
@@ -39,7 +39,7 @@ extension TunnelConfiguration {
var interfaceConfiguration: InterfaceConfiguration?
var peerConfigurations = [PeerConfiguration]()
let lines = wgQuickConfig.split(separator: "\n")
let lines = wgQuickConfig.split { $0.isNewline }
var parserState = ParserState.notInASection
var attributes = [String: String]()
+171 -9
View File
@@ -41,16 +41,21 @@
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
6B6956362211DA80001B618A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B6956352211DA80001B618A /* main.m */; };
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */; };
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97E220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44C8222D55BB00B0FF04 /* TextCell.swift */; };
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */; };
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */; };
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; };
6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */; };
6F3E02E9228000F6001FE7E3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3E02E8228000F6001FE7E3 /* MainMenu.swift */; };
6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */; };
6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F29A94622787B1600DC6A6B /* QuickActionItem.swift */; };
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
@@ -65,11 +70,13 @@
6F628C3D217F09E9003482A3 /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
6F628C3F217F3413003482A3 /* DNSServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3E217F3413003482A3 /* DNSServer.swift */; };
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */; };
6F6483E7229293300075BA15 /* LaunchedAtLoginDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */; };
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */ = {isa = PBXBuildFile; fileRef = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
@@ -84,12 +91,15 @@
6F8F0D7222258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */; };
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7322267AD2000E8335 /* ChevronCell.swift */; };
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */; };
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; };
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; };
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; };
6F919EDB218C65C50023B400 /* wireguard_doc_logo_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */; };
6F919EDC218C65C50023B400 /* wireguard_doc_logo_320x320.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */; };
6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */; };
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */; };
6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1017821C57DE600766195 /* MockTunnels.swift */; };
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */; };
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; };
@@ -155,6 +165,9 @@
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; };
6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */; };
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */; };
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
@@ -194,6 +207,13 @@
remoteGlobalIDString = 6F5D0C19218352EF000F85AD;
remoteInfo = WireGuardNetworkExtension;
};
6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6F70E22822106A2D008BDFB4;
remoteInfo = WireGuardmacOSLoginItemHelper;
};
6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
@@ -229,6 +249,17 @@
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
};
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = Contents/Library/LoginItems;
dstSubfolderSpec = 1;
files = (
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */,
);
name = "Embed Login Item Helper";
runOnlyForDeploymentPostprocessing = 0;
};
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -264,13 +295,18 @@
6B586C52220CBA6D00427C51 /* Data+KeyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+KeyEncoding.swift"; sourceTree = "<group>"; };
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
6B6956352211DA80001B618A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePrivacyNotice.swift; sourceTree = "<group>"; };
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = "<group>"; };
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextCell.swift; sourceTree = "<group>"; };
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTunnelsConfirmationAlert.swift; sourceTree = "<group>"; };
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = "<group>"; };
6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppStoreUpdateDetector.swift; sourceTree = "<group>"; };
6F3E02E8228000F6001FE7E3 /* MainMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = "<group>"; };
6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentTunnelsTracker.swift; sourceTree = "<group>"; };
6F29A94622787B1600DC6A6B /* QuickActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickActionItem.swift; sourceTree = "<group>"; };
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; };
@@ -289,12 +325,16 @@
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewModel.swift; sourceTree = "<group>"; };
6F628C3E217F3413003482A3 /* DNSServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSServer.swift; sourceTree = "<group>"; };
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchedAtLoginDetector.swift; sourceTree = "<group>"; };
6F689999218043390012E523 /* WireGuard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuard-Bridging-Header.h"; sourceTree = "<group>"; };
6F6899A42180447E0012E523 /* x25519.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = x25519.h; sourceTree = "<group>"; };
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
6F70E20D221058DF008BDFB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = WireGuard/Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuardLoginItemHelper.app; sourceTree = BUILT_PRODUCTS_DIR; };
6F70E23222106A31008BDFB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItemHelper.entitlements; sourceTree = "<group>"; };
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
@@ -308,12 +348,14 @@
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandViewModel.swift; sourceTree = "<group>"; };
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronCell.swift; sourceTree = "<group>"; };
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionEditTableViewController.swift; sourceTree = "<group>"; };
6F907C9B224663A2003CED21 /* LogViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewHelper.swift; sourceTree = "<group>"; };
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; };
6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; };
6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_64x64.png; sourceTree = "<group>"; };
6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_320x320.png; sourceTree = "<group>"; };
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionDetailTableViewController.swift; sourceTree = "<group>"; };
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnusableTunnelDetailViewController.swift; sourceTree = "<group>"; };
6FB1017821C57DE600766195 /* MockTunnels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnels.swift; sourceTree = "<group>"; };
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDemandWiFiControls.swift; sourceTree = "<group>"; };
6FB1BD5D21D2607A00A991BF /* WireGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -335,6 +377,9 @@
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPanelPresenter.swift; sourceTree = "<group>"; };
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditViewController.swift; sourceTree = "<group>"; };
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
@@ -432,6 +477,7 @@
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
);
path = ViewController;
sourceTree = "<group>";
@@ -448,6 +494,8 @@
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
6F5EA59A223E58A8002B380A /* ButtonRow.swift */,
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */,
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */,
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */,
);
path = View;
sourceTree = "<group>";
@@ -488,6 +536,16 @@
path = Crypto;
sourceTree = "<group>";
};
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */ = {
isa = PBXGroup;
children = (
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */,
6F70E23222106A31008BDFB4 /* Info.plist */,
6B6956352211DA80001B618A /* main.m */,
);
path = LoginItemHelper;
sourceTree = "<group>";
};
6F7774DD217181B1006A79B3 /* UI */ = {
isa = PBXGroup;
children = (
@@ -498,6 +556,7 @@
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
6F907C9B224663A2003CED21 /* LogViewHelper.swift */,
);
path = UI;
sourceTree = "<group>";
@@ -507,12 +566,14 @@
children = (
6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
5F4541AC21C4720B00994C13 /* ViewController */,
5F4541A721C44F5B00994C13 /* View */,
5F4541AC21C4720B00994C13 /* ViewController */,
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */,
6F29A94622787B1600DC6A6B /* QuickActionItem.swift */,
6FF4AC23211EC472002C96EB /* Info.plist */,
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
);
@@ -573,22 +634,25 @@
6FB1BD5E21D2607A00A991BF /* macOS */ = {
isa = PBXGroup;
children = (
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */,
6F4DD16921DA556600690EAE /* View */,
6FBA104421D7EA750051C35F /* ViewController */,
6FBA101321D613F30051C35F /* Application.swift */,
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */,
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */,
6FBA101621D655340051C35F /* StatusMenu.swift */,
6F3E02E8228000F6001FE7E3 /* MainMenu.swift */,
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */,
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */,
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */,
6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */,
6F6483E6229293300075BA15 /* LaunchedAtLoginDetector.swift */,
6FB1BD6121D2607E00A991BF /* Assets.xcassets */,
6FB1BD6621D2607E00A991BF /* Info.plist */,
6FB1BD6721D2607E00A991BF /* WireGuard.entitlements */,
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */,
5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */,
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */,
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */,
);
path = macOS;
sourceTree = "<group>";
@@ -601,6 +665,8 @@
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */,
6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */,
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */,
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */,
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */,
);
path = ViewController;
sourceTree = "<group>";
@@ -667,6 +733,7 @@
6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */,
6FB1BD5D21D2607A00A991BF /* WireGuard.app */,
6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */,
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */,
);
name = Products;
sourceTree = "<group>";
@@ -753,6 +820,21 @@
productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */;
productType = "com.apple.product-type.app-extension";
};
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */;
buildPhases = (
6F70E22522106A2D008BDFB4 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = WireGuardmacOSLoginItemHelper;
productName = WireGuardmacOSLoginItemHelper;
productReference = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */;
productType = "com.apple.product-type.application";
};
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */;
@@ -764,10 +846,12 @@
6FB1BD5A21D2607A00A991BF /* Frameworks */,
6FB1BD5B21D2607A00A991BF /* Resources */,
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */,
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */,
);
buildRules = (
);
dependencies = (
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */,
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */,
);
name = WireGuardmacOS;
@@ -829,10 +913,19 @@
TargetAttributes = {
6F5D0C19218352EF000F85AD = {
CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1000;
LastSwiftMigration = 1020;
};
6F70E22822106A2D008BDFB4 = {
CreatedOnToolsVersion = 10.1;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
};
6FB1BD5C21D2607A00A991BF = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.ApplicationGroups.Mac = {
enabled = 1;
@@ -847,6 +940,7 @@
};
6FB1BD9021D4BFE600A991BF = {
CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.ApplicationGroups.Mac = {
enabled = 1;
@@ -858,7 +952,7 @@
};
6FF4AC13211EC46F002C96EB = {
CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1000;
LastSwiftMigration = 1020;
SystemCapabilities = {
com.apple.AccessWiFi = {
enabled = 1;
@@ -892,6 +986,7 @@
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */,
6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */,
6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */,
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */,
);
};
/* End PBXProject section */
@@ -1151,6 +1246,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
6F70E22522106A2D008BDFB4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6B6956362211DA80001B618A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6FB1BD5921D2607A00A991BF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1167,6 +1270,7 @@
6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */,
6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */,
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */,
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */,
6B5C5E29220A48D30024272E /* Keychain.swift in Sources */,
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */,
6FB1BDD521D50F5300A991BF /* unzip.c in Sources */,
@@ -1187,7 +1291,6 @@
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */,
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */,
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
@@ -1196,12 +1299,17 @@
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */,
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
6F3E02E9228000F6001FE7E3 /* MainMenu.swift in Sources */,
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
6F6483E7229293300075BA15 /* LaunchedAtLoginDetector.swift in Sources */,
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */,
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */,
@@ -1213,7 +1321,9 @@
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */,
6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */,
6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */,
6F2449E8226587B90047B9E9 /* MacAppStoreUpdateDetector.swift in Sources */,
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */,
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */,
@@ -1251,6 +1361,7 @@
buildActionMask = 2147483647;
files = (
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
6F29A94722787B1600DC6A6B /* QuickActionItem.swift in Sources */,
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
@@ -1264,6 +1375,7 @@
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
@@ -1290,6 +1402,7 @@
6F7774EA217229DB006A79B3 /* IPAddressRange.swift in Sources */,
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
6F29A9432278518D00DC6A6B /* RecentTunnelsTracker.swift in Sources */,
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
@@ -1309,6 +1422,7 @@
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */,
5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1320,6 +1434,11 @@
target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */;
targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */;
};
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */;
targetProxy = 6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */;
};
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */;
@@ -1383,7 +1502,7 @@
PRODUCT_NAME = WireGuardNetworkExtension;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -1405,7 +1524,33 @@
PRODUCT_NAME = WireGuardNetworkExtension;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 4.2;
SWIFT_VERSION = 5.0;
};
name = Release;
};
6F70E23422106A31008BDFB4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
6F70E23522106A31008BDFB4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Release;
};
@@ -1425,6 +1570,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
PRODUCT_NAME = WireGuard;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -1444,6 +1590,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
PRODUCT_NAME = WireGuard;
SDKROOT = macosx;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -1465,6 +1612,7 @@
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -1486,6 +1634,7 @@
SDKROOT = macosx;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -1558,6 +1707,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -1623,6 +1773,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -1662,6 +1813,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
PRODUCT_NAME = WireGuard;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -1678,6 +1830,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
PRODUCT_NAME = WireGuard;
SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -1693,6 +1846,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6F70E23422106A31008BDFB4 /* Debug */,
6F70E23522106A31008BDFB4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -37,8 +37,8 @@
"alertBadConfigImportMessage (%@)" = "The file %@ does not contain a valid WireGuard configuration";
"deleteTunnelsConfirmationAlertButtonTitle" = "Delete";
"deleteTunnelConfirmationAlertButtonMessage (%d)" = "Delete %d tunnel";
"deleteTunnelsConfirmationAlertButtonMessage (%d)" = "Delete %d tunnels";
"deleteTunnelConfirmationAlertButtonMessage (%d)" = "Delete %d tunnel?";
"deleteTunnelsConfirmationAlertButtonMessage (%d)" = "Delete %d tunnels?";
// Tunnel detail and edit UI
@@ -209,10 +209,14 @@
"settingsSectionTitleExportConfigurations" = "Export configurations";
"settingsExportZipButtonTitle" = "Export zip archive";
"settingsSectionTitleTunnelLog" = "Tunnel log";
"settingsExportLogFileButtonTitle" = "Export log file";
"settingsSectionTitleTunnelLog" = "Log";
"settingsViewLogButtonTitle" = "View log";
// Settings alerts
// Log view
"logViewTitle" = "Log";
// Log alerts
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
@@ -287,19 +291,41 @@
"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
// Mac status bar menu / pulldown menu
// Mac status bar menu / pulldown menu / main menu
"macMenuNetworks (%@)" = "Networks: %@";
"macMenuNetworksNone" = "Networks: None";
"macMenuTitle" = "WireGuard";
"macMenuManageTunnels" = "Manage tunnels";
"macMenuImportTunnels" = "Import tunnel(s) from file…";
"macMenuAddEmptyTunnel" = "Add empty tunnel…";
"macMenuExportLog" = "Export log to file…";
"macMenuExportTunnels" = "Export tunnels to zip…";
"macMenuManageTunnels" = "Manage Tunnels";
"macMenuImportTunnels" = "Import Tunnel(s) from File…";
"macMenuAddEmptyTunnel" = "Add Empty Tunnel…";
"macMenuViewLog" = "View Log";
"macMenuExportTunnels" = "Export Tunnels to Zip…";
"macMenuAbout" = "About WireGuard";
"macMenuQuit" = "Quit";
"macMenuQuit" = "Quit WireGuard";
"macMenuHideApp" = "Hide WireGuard";
"macMenuHideOtherApps" = "Hide Others";
"macMenuShowAllApps" = "Show All";
"macMenuFile" = "File";
"macMenuCloseWindow" = "Close Window";
"macMenuEdit" = "Edit";
"macMenuCut" = "Cut";
"macMenuCopy" = "Copy";
"macMenuPaste" = "Paste";
"macMenuSelectAll" = "Select All";
"macMenuTunnel" = "Tunnel";
"macMenuToggleStatus" = "Toggle Status";
"macMenuEditTunnel" = "Edit…";
"macMenuDeleteSelected" = "Delete Selected";
"macMenuWindow" = "Window";
"macMenuMinimize" = "Minimize";
"macMenuZoom" = "Zoom";
// Mac manage tunnels window
@@ -310,14 +336,15 @@
"macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action.";
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Deleting…";
"macButtonImportTunnels" = "Import tunnel(s) from file";
"macSheetButtonImport" = "Import";
"macNameFieldExportLog" = "Export log to";
"macNameFieldExportLog" = "Save log to:";
"macSheetButtonExportLog" = "Save";
"macNameFieldExportZip" = "Export tunnels to";
"macNameFieldExportZip" = "Export tunnels to:";
"macSheetButtonExportZip" = "Save";
"macButtonDeleteTunnels (%d)" = "Delete %d tunnels";
@@ -378,12 +405,39 @@
// Mac alert
"macConfirmAndQuitAlertMessage" = "Do you want to close the tunnels manager or quit WireGuard entirely?";
"macConfirmAndQuitAlertInfo" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.";
"macConfirmAndQuitInfoWithActiveTunnel (%@)" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.\n\nNote that if you quit WireGuard entirely the currently active tunnel ('%@') will still remain active until you deactivate it from this application or through the Network panel in System Preferences.";
"macConfirmAndQuitAlertQuitWireGuard" = "Quit WireGuard";
"macConfirmAndQuitAlertCloseWindow" = "Close Tunnels Manager";
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
"macPrivacyNoticeMessage" = "Privacy notice: be sure you trust this configuration file";
"macPrivacyNoticeInfo" = "You will be prompted by the system to allow or disallow adding a VPN configuration. While this application does not send any information to the WireGuard project, information is by design sent to the servers specified inside of the configuration file you have just added, which configures your computer to use those servers as a VPN. Be certain that you trust this configuration before clicking “Allow” in the following dialog.";
// Mac tooltip
"macToolTipEditTunnel" = "Edit tunnel (⌘E)";
"macToolTipToggleStatus" = "Toggle status (⌘T)";
// Mac log view
"macLogColumnTitleTime" = "Time";
"macLogColumnTitleLogMessage" = "Log message";
"macLogButtonTitleClose" = "Close";
"macLogButtonTitleSave" = "Save…";
// Mac unusable tunnel view
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
"macUnusableTunnelInfo" = "In case this tunnel was created by another user, only that user can view, edit, or activate this tunnel.";
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
// Mac App Store updating alert
"macAppStoreUpdatingAlertMessage" = "App Store would like to update WireGuard";
"macAppStoreUpdatingAlertInfoWithOnDemand (%@)" = "Please disable on-demand for tunnel %@, deactivate it, and then continue updating in App Store.";
"macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)" = "Please deactivate tunnel %@ and then continue updating in App Store.";
// Donation
"donateLink" = "♥ Donate to the WireGuard Project";
+2 -2
View File
@@ -1,2 +1,2 @@
VERSION_NAME = 0.0.20190319
VERSION_ID = 1
VERSION_NAME = 0.0.20200127
VERSION_ID = 17
+3 -3
View File
@@ -9,7 +9,7 @@ struct Curve25519 {
static func generatePrivateKey() -> Data {
var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
privateKey.withUnsafeMutableBytes { bytes in
privateKey.withUnsafeMutableUInt8Bytes { bytes in
curve25519_generate_private_key(bytes)
}
assert(privateKey.count == TunnelConfiguration.keyLength)
@@ -19,8 +19,8 @@ struct Curve25519 {
static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data {
assert(privateKey.count == TunnelConfiguration.keyLength)
var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
privateKey.withUnsafeBytes { privateKeyBytes in
publicKey.withUnsafeMutableBytes { bytes in
privateKey.withUnsafeUInt8Bytes { privateKeyBytes in
publicKey.withUnsafeMutableUInt8Bytes { bytes in
curve25519_derive_public_key(bytes, privateKeyBytes)
}
}
@@ -46,46 +46,59 @@ extension ActivateOnDemandOption {
}
init(from tunnelProviderManager: NETunnelProviderManager) {
let rules = tunnelProviderManager.onDemandRules ?? []
let activateOnDemandOption: ActivateOnDemandOption
if tunnelProviderManager.isOnDemandEnabled, let onDemandRules = tunnelProviderManager.onDemandRules {
self = ActivateOnDemandOption.create(from: onDemandRules)
} else {
self = .off
}
}
private static func create(from rules: [NEOnDemandRule]) -> ActivateOnDemandOption {
switch rules.count {
case 0:
activateOnDemandOption = .off
return .off
case 1:
let rule = rules[0]
precondition(rule.action == .connect)
activateOnDemandOption = .anyInterface(.anySSID)
guard rule.action == .connect else { return .off }
return .anyInterface(.anySSID)
case 2:
let connectRule = rules.first(where: { $0.action == .connect })!
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
guard let connectRule = rules.first(where: { $0.action == .connect }) else {
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no connect rule.")
return .off
}
guard let disconnectRule = rules.first(where: { $0.action == .disconnect }) else {
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no disconnect rule.")
return .off
}
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
activateOnDemandOption = .wiFiInterfaceOnly(.anySSID)
return .wiFiInterfaceOnly(.anySSID)
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
activateOnDemandOption = .nonWiFiInterfaceOnly
return .nonWiFiInterfaceOnly
} else {
fatalError("Unexpected onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but interface types are inconsistent.")
return .off
}
case 3:
let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil })!
let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType })!
guard let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil }) else { return .off }
guard let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType }) else { return .off }
let ssids = ssidRule.ssidMatch!
switch (ssidRule.action, nonWiFiRule.action) {
case (.connect, .connect):
activateOnDemandOption = .anyInterface(.onlySpecificSSIDs(ssids))
return .anyInterface(.onlySpecificSSIDs(ssids))
case (.connect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
return .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
case (.disconnect, .connect):
activateOnDemandOption = .anyInterface(.exceptSpecificSSIDs(ssids))
return .anyInterface(.exceptSpecificSSIDs(ssids))
case (.disconnect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
return .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
default:
fatalError("Unexpected SSID onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found")
return .off
}
default:
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected number of onDemandRules set on tunnel provider manager: \(rules.count) rules found")
return .off
}
self = activateOnDemandOption
}
}
@@ -27,6 +27,8 @@ import NetworkExtension
self = .reasserting
case .invalid:
self = .inactive
@unknown default:
fatalError()
}
}
}
@@ -54,6 +56,8 @@ extension NEVPNStatus: CustomDebugStringConvertible {
case .disconnecting: return "disconnecting"
case .reasserting: return "reasserting"
case .invalid: return "invalid"
@unknown default:
fatalError()
}
}
}
+234 -15
View File
@@ -20,20 +20,26 @@ protocol TunnelsManagerActivationDelegate: class {
}
class TunnelsManager {
private var tunnels: [TunnelContainer]
fileprivate var tunnels: [TunnelContainer]
weak var tunnelsListDelegate: TunnelsManagerListDelegate?
weak var activationDelegate: TunnelsManagerActivationDelegate?
private var statusObservationToken: AnyObject?
private var waiteeObservationToken: AnyObject?
private var configurationsObservationToken: AnyObject?
private var catalinaWorkaround: Any?
init(tunnelProviders: [NETunnelProviderManager]) {
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
startObservingTunnelStatuses()
startObservingTunnelConfigurations()
#if os(macOS)
if #available(macOS 10.15, *) {
self.catalinaWorkaround = CatalinaWorkaround(tunnelsManager: self)
}
#endif
}
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
static func create(completionHandler: @escaping (Result<TunnelsManager, TunnelsManagerError>) -> Void) {
#if targetEnvironment(simulator)
completionHandler(.success(TunnelsManager(tunnelProviders: MockTunnels.createMockTunnels())))
#else
@@ -46,19 +52,47 @@ class TunnelsManager {
var tunnelManagers = managers ?? []
var refs: Set<Data> = []
var tunnelNames: Set<String> = []
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol
if proto?.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") ?? false {
if let tunnelName = tunnelManager.localizedDescription {
tunnelNames.insert(tunnelName)
}
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
tunnelManager.saveToPreferences { _ in }
}
if let ref = proto?.verifyConfigurationReference() {
#if os(iOS)
let passwordRef = proto.verifyConfigurationReference() ? proto.passwordReference : nil
#elseif os(macOS)
let passwordRef: Data?
if proto.providerConfiguration?["UID"] as? uid_t == getuid() {
passwordRef = proto.verifyConfigurationReference() ? proto.passwordReference : nil
} else {
passwordRef = proto.passwordReference // To handle multiple users in macOS, we skip verifying
}
#else
#error("Unimplemented")
#endif
if let ref = passwordRef {
refs.insert(ref)
} else {
wg_log(.info, message: "Removing orphaned tunnel with non-verifying keychain entry: \(tunnelManager.localizedDescription ?? "<unknown>")")
tunnelManager.removeFromPreferences { _ in }
tunnelManagers.remove(at: index)
}
}
#if os(macOS)
if #available(macOS 10.15, *) {
// Don't delete orphaned keychain refs. We need them to restore tunnels as a workaround.
} else {
Keychain.deleteReferences(except: refs)
}
#else
Keychain.deleteReferences(except: refs)
#endif
#if os(iOS)
RecentTunnelsTracker.cleanupTunnels(except: tunnelNames)
#endif
completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
}
#endif
@@ -71,14 +105,14 @@ class TunnelsManager {
let loadedTunnelProviders = managers ?? []
for (index, currentTunnel) in self.tunnels.enumerated().reversed() {
if !loadedTunnelProviders.contains(where: { $0.tunnelConfiguration == currentTunnel.tunnelConfiguration }) {
if !loadedTunnelProviders.contains(where: { $0.isEquivalentTo(currentTunnel) }) {
// Tunnel was deleted outside the app
self.tunnels.remove(at: index)
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel)
}
}
for loadedTunnelProvider in loadedTunnelProviders {
if let matchingTunnel = self.tunnels.first(where: { $0.tunnelConfiguration == loadedTunnelProvider.tunnelConfiguration }) {
if let matchingTunnel = self.tunnels.first(where: { loadedTunnelProvider.isEquivalentTo($0) }) {
matchingTunnel.tunnelProvider = loadedTunnelProvider
matchingTunnel.refreshStatus()
} else {
@@ -97,7 +131,7 @@ class TunnelsManager {
}
}
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (Result<TunnelContainer, TunnelsManagerError>) -> Void) {
let tunnelName = tunnelConfiguration.name ?? ""
if tunnelName.isEmpty {
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
@@ -160,9 +194,15 @@ class TunnelsManager {
let tail = tunnelConfigurations.dropFirst()
add(tunnelConfiguration: head) { [weak self, tail] result in
DispatchQueue.main.async {
let numberSuccessful = numberSuccessful + (result.isSuccess ? 1 : 0)
let lastError = lastError ?? (result.error as? TunnelsManagerError)
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful, lastError: lastError, completionHandler: completionHandler)
var numberSuccessfulCount = numberSuccessful
var lastError: TunnelsManagerError?
switch result {
case .failure(let error):
lastError = error
case .success:
numberSuccessfulCount = numberSuccessful + 1
}
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessfulCount, lastError: lastError, completionHandler: completionHandler)
}
}
}
@@ -175,7 +215,8 @@ class TunnelsManager {
}
let tunnelProviderManager = tunnel.tunnelProvider
let isNameChanged = tunnelName != tunnelProviderManager.localizedDescription
let oldName = tunnelProviderManager.localizedDescription ?? ""
let isNameChanged = tunnelName != oldName
if isNameChanged {
guard !tunnels.contains(where: { $0.name == tunnelName }) else {
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
@@ -207,6 +248,9 @@ class TunnelsManager {
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
let newIndex = self.tunnels.firstIndex(of: tunnel)!
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
#if os(iOS)
RecentTunnelsTracker.handleTunnelRenamed(oldName: oldName, newName: tunnelName)
#endif
}
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
@@ -238,8 +282,15 @@ class TunnelsManager {
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
let tunnelProviderManager = tunnel.tunnelProvider
#if os(macOS)
if tunnel.isTunnelAvailableToUser {
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
}
#elseif os(iOS)
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
#else
#error("Unimplemented")
#endif
tunnelProviderManager.removeFromPreferences { [weak self] error in
guard error == nil else {
wg_log(.error, message: "Remove: Saving configuration failed: \(error!)")
@@ -251,6 +302,10 @@ class TunnelsManager {
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
}
completionHandler(nil)
#if os(iOS)
RecentTunnelsTracker.handleTunnelRemoved(tunnelName: tunnel.name)
#endif
}
}
@@ -283,6 +338,10 @@ class TunnelsManager {
return tunnels[index]
}
func mapTunnels<T>(transform: (TunnelContainer) throws -> T) rethrows -> [T] {
return try tunnels.map(transform)
}
func index(of tunnel: TunnelContainer) -> Int? {
return tunnels.firstIndex(of: tunnel)
}
@@ -328,6 +387,10 @@ class TunnelsManager {
#else
tunnel.startActivation(activationDelegate: activationDelegate)
#endif
#if os(iOS)
RecentTunnelsTracker.handleTunnelActivated(tunnelName: tunnel.name)
#endif
}
func startDeactivation(of tunnel: TunnelContainer) {
@@ -459,6 +522,12 @@ class TunnelContainer: NSObject {
return ActivateOnDemandOption(from: tunnelProvider)
}
#if os(macOS)
var isTunnelAvailableToUser: Bool {
return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration?["UID"] as? uid_t == getuid()
}
#endif
init(tunnel: NETunnelProviderManager) {
name = tunnel.localizedDescription ?? "Unnamed"
let status = TunnelStatus(from: tunnel.connection.status)
@@ -473,7 +542,7 @@ class TunnelContainer: NSObject {
completionHandler(tunnelConfiguration)
return
}
guard nil != (try? session.sendProviderMessage(Data(bytes: [ 0 ]), responseHandler: {
guard nil != (try? session.sendProviderMessage(Data([ UInt8(0) ]), responseHandler: {
guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else {
completionHandler(self.tunnelConfiguration)
return
@@ -567,7 +636,8 @@ class TunnelContainer: NSObject {
}
extension NETunnelProviderManager {
private static var cachedConfigKey: UInt8 = 0
fileprivate static var cachedConfigKey: UInt8 = 0
var tunnelConfiguration: TunnelConfiguration? {
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
return cached
@@ -584,4 +654,153 @@ extension NETunnelProviderManager {
localizedDescription = tunnelConfiguration.name
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func isEquivalentTo(_ tunnel: TunnelContainer) -> Bool {
return localizedDescription == tunnel.name && tunnelConfiguration == tunnel.tunnelConfiguration
}
}
#if os(macOS)
@available(macOS 10.15, *)
class CatalinaWorkaround {
// In macOS Catalina, for some users, the tunnels get deleted arbitrarily
// by the OS. It's not clear what triggers that.
// As a workaround, in macOS Catalina, when we realize that tunnels have been
// deleted outside the app, we reinstate those tunnels using the information
// in the keychain.
unowned let tunnelsManager: TunnelsManager
private var configChangeSubscriber: Any?
struct ReinstationData {
let tunnelConfiguration: TunnelConfiguration
let keychainPasswordRef: Data
}
init(tunnelsManager: TunnelsManager) {
self.tunnelsManager = tunnelsManager
// Attempt reinstation when there's a change in tunnel configurations,
// which indicates that tunnels may have been deleted outside the app.
// We use debounce to wait for all change notifications to arrive
// before attempting to reinstate, so that we don't have saveToPreferences
// being called while another saveToPreferences is in progress.
self.configChangeSubscriber = NotificationCenter.default
.publisher(for: .NEVPNConfigurationChange, object: nil)
.debounce(for: .seconds(1), scheduler: RunLoop.main)
.subscribe(on: RunLoop.main)
.sink { [weak self] _ in
self?.reinstateTunnelsDeletedOutsideApp()
}
// Attempt reinstation on app launch
reinstateTunnelsDeletedOutsideApp()
}
func reinstateTunnelsDeletedOutsideApp() {
let rd = reinstationDataForTunnelsDeletedOutsideApp()
reinstateTunnels(ArraySlice(rd), completionHandler: nil)
}
private func reinstateTunnels(_ rdArray: ArraySlice<ReinstationData>, completionHandler: (() -> Void)?) {
guard let head = rdArray.first else {
completionHandler?()
return
}
let tail = rdArray.dropFirst()
self.tunnelsManager.reinstateTunnel(reinstationData: head) { _ in
DispatchQueue.main.async {
self.reinstateTunnels(tail, completionHandler: completionHandler)
}
}
}
private func reinstationDataForTunnelsDeletedOutsideApp() -> [ReinstationData] {
let knownRefs: [Data] = self.tunnelsManager.tunnels
.compactMap { $0.tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol }
.compactMap { $0.passwordReference }
let knownRefsSet: Set<Data> = Set(knownRefs)
var result: CFTypeRef?
let ret = SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Bundle.main.bundleIdentifier as Any,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: true,
kSecReturnPersistentRef as String: true] as CFDictionary,
&result)
guard ret == errSecSuccess, let resultDicts = result as? [[String: Any]] else { return [] }
let labelPrefix = "WireGuard Tunnel: "
var reinstationData: [ReinstationData] = []
for resultDict in resultDicts {
guard let ref = resultDict[kSecValuePersistentRef as String] as? Data else { continue }
guard let label = resultDict[kSecAttrLabel as String] as? String else { continue }
guard label.hasPrefix(labelPrefix) else { continue }
if !knownRefsSet.contains(ref) {
let tunnelName = String(label.dropFirst(labelPrefix.count))
if let configStr = Keychain.openReference(called: ref),
let config = try? TunnelConfiguration(fromWgQuickConfig: configStr, called: tunnelName) {
reinstationData.append(ReinstationData(tunnelConfiguration: config, keychainPasswordRef: ref))
}
}
}
return reinstationData
}
}
#endif
#if os(macOS)
@available(macOS 10.15, *)
extension TunnelsManager {
fileprivate func reinstateTunnel(reinstationData: CatalinaWorkaround.ReinstationData, completionHandler: @escaping (Bool) -> Void) {
let tunnelName = reinstationData.tunnelConfiguration.name ?? ""
if tunnelName.isEmpty {
completionHandler(false)
return
}
if tunnels.contains(where: { $0.name == tunnelName }) {
completionHandler(false)
return
}
let tunnelProviderProtocol = NETunnelProviderProtocol()
guard let appId = Bundle.main.bundleIdentifier else { fatalError() }
tunnelProviderProtocol.providerBundleIdentifier = "\(appId).network-extension"
tunnelProviderProtocol.passwordReference = reinstationData.keychainPasswordRef
tunnelProviderProtocol.providerConfiguration = ["UID": getuid()]
tunnelProviderProtocol.serverAddress = {
let endpoints = reinstationData.tunnelConfiguration.peers.compactMap { $0.endpoint }
if endpoints.count == 1 {
return endpoints[0].stringRepresentation
} else if endpoints.isEmpty {
return "Unspecified"
} else {
return "Multiple endpoints"
}
}()
let tunnelProvider = NETunnelProviderManager()
tunnelProvider.localizedDescription = tunnelName
tunnelProvider.protocolConfiguration = tunnelProviderProtocol
objc_setAssociatedObject(tunnelProvider, &NETunnelProviderManager.cachedConfigKey, reinstationData.tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
tunnelProvider.isEnabled = true
tunnelProvider.saveToPreferences { [weak self] error in
guard error == nil else {
wg_log(.error, message: "Reinstate: Saving configuration failed: \(error!)")
completionHandler(false)
return
}
guard let self = self else { return }
let tunnel = TunnelContainer(tunnel: tunnelProvider)
self.tunnels.append(tunnel)
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
completionHandler(true)
}
}
}
#endif
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Foundation
public class LogViewHelper {
var log: OpaquePointer
var cursor: UInt32 = UINT32_MAX
static let formatOptions: ISO8601DateFormatter.Options = [
.withYear, .withMonth, .withDay, .withTime,
.withDashSeparatorInDate, .withColonSeparatorInTime, .withSpaceBetweenDateAndTime,
.withFractionalSeconds
]
struct LogEntry {
let timestamp: String
let message: String
func text() -> String {
return timestamp + " " + message
}
}
class LogEntries {
var entries: [LogEntry] = []
}
init?(logFilePath: String?) {
guard let logFilePath = logFilePath else { return nil }
guard let log = open_log(logFilePath) else { return nil }
self.log = log
}
deinit {
close_log(self.log)
}
func fetchLogEntriesSinceLastFetch(completion: @escaping ([LogViewHelper.LogEntry]) -> Void) {
var logEntries = LogEntries()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
let newCursor = view_lines_from_cursor(self.log, self.cursor, &logEntries) { cStr, timestamp, ctx in
let message = cStr != nil ? String(cString: cStr!) : ""
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000000000)
let dateString = ISO8601DateFormatter.string(from: date, timeZone: TimeZone.current, formatOptions: LogViewHelper.formatOptions)
if let logEntries = ctx?.bindMemory(to: LogEntries.self, capacity: 1) {
logEntries.pointee.entries.append(LogEntry(timestamp: dateString, message: message))
}
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.cursor = newCursor
completion(logEntries.entries)
}
}
}
}
+16 -6
View File
@@ -16,10 +16,10 @@ class TunnelImporter {
if url.pathExtension.lowercased() == "zip" {
dispatchGroup.enter()
ZipImporter.importConfigFiles(from: url) { result in
if let error = result.error {
switch result {
case .failure(let error):
lastFileImportErrorText = error.alertText
}
if let configsInZip = result.value {
case .success(let configsInZip):
configs.append(contentsOf: configsInZip)
}
dispatchGroup.leave()
@@ -44,10 +44,20 @@ class TunnelImporter {
}
return
}
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
var parseError: Error?
var tunnelConfiguration: TunnelConfiguration?
do {
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
} catch let error {
parseError = error
}
DispatchQueue.main.async {
if tunnelConfiguration == nil {
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
if parseError != nil {
if let parseError = parseError as? WireGuardAppError {
lastFileImportErrorText = parseError.alertText
} else {
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
}
}
configs.append(tunnelConfiguration)
dispatchGroup.leave()
+30 -7
View File
@@ -9,12 +9,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainVC: MainViewController?
var isLaunchedForSpecificAction = false
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
if let launchOptions = launchOptions {
if launchOptions[.url] != nil || launchOptions[.shortcutItem] != nil {
isLaunchedForSpecificAction = true
}
}
let window = UIWindow(frame: UIScreen.main.bounds)
window.backgroundColor = .white
if #available(iOS 13.0, *) {
window.backgroundColor = .systemBackground
} else {
window.backgroundColor = .white
}
self.window = window
let mainVC = MainViewController()
@@ -27,16 +38,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
_ = FileManager.deleteFile(at: url)
}
mainVC?.importFromDisposableFile(url: url)
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
mainVC?.refreshTunnelConnectionStatuses()
}
func applicationWillResignActive(_ application: UIApplication) {
guard let allTunnelNames = mainVC?.allTunnelNames() else { return }
application.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames)
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
guard shortcutItem.type == QuickActionItem.type else {
completionHandler(false)
return
}
let tunnelName = shortcutItem.localizedTitle
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: true)
completionHandler(true)
}
}
extension AppDelegate {
@@ -45,7 +68,7 @@ extension AppDelegate {
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
return !self.isLaunchedForSpecificAction
}
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
@@ -58,7 +81,7 @@ extension AppDelegate {
}
} else {
// Show it when tunnelsManager is available
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false)
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false)
}
}
return nil
+2 -1
View File
@@ -64,6 +64,8 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
@@ -82,7 +84,6 @@
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class QuickActionItem: UIApplicationShortcutItem {
static let type = "WireGuardTunnelActivateAndShow"
init(tunnelName: String) {
super.init(type: QuickActionItem.type, localizedTitle: tunnelName, localizedSubtitle: nil, icon: nil, userInfo: nil)
}
static func createItems(allTunnelNames: [String]) -> [QuickActionItem] {
let numberOfItems = 10
// Currently, only 4 items shown by iOS, but that can increase in the future.
// iOS will discard additional items we give it.
var tunnelNames = RecentTunnelsTracker.recentlyActivatedTunnelNames(limit: numberOfItems)
let numberOfSlotsRemaining = numberOfItems - tunnelNames.count
if numberOfSlotsRemaining > 0 {
let moreTunnels = allTunnelNames.filter { !tunnelNames.contains($0) }.prefix(numberOfSlotsRemaining)
tunnelNames.append(contentsOf: moreTunnels)
}
return tunnelNames.map { QuickActionItem(tunnelName: $0) }
}
}
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Foundation
class RecentTunnelsTracker {
private static let keyRecentlyActivatedTunnelNames = "recentlyActivatedTunnelNames"
private static let maxNumberOfTunnels = 10
private static var userDefaults: UserDefaults? {
guard let appGroupId = FileManager.appGroupId else {
wg_log(.error, staticMessage: "Cannot obtain app group ID from bundle for tracking recently used tunnels")
return nil
}
guard let userDefaults = UserDefaults(suiteName: appGroupId) else {
wg_log(.error, staticMessage: "Cannot obtain shared user defaults for tracking recently used tunnels")
return nil
}
return userDefaults
}
static func handleTunnelActivated(tunnelName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
recentTunnels.remove(at: existingIndex)
}
recentTunnels.insert(tunnelName, at: 0)
if recentTunnels.count > maxNumberOfTunnels {
recentTunnels.removeLast(recentTunnels.count - maxNumberOfTunnels)
}
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
static func handleTunnelRemoved(tunnelName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
recentTunnels.remove(at: existingIndex)
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func handleTunnelRenamed(oldName: String, newName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: oldName) {
recentTunnels[existingIndex] = newName
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func cleanupTunnels(except tunnelNamesToKeep: Set<String>) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
let oldCount = recentTunnels.count
recentTunnels.removeAll { !tunnelNamesToKeep.contains($0) }
if oldCount != recentTunnels.count {
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func recentlyActivatedTunnelNames(limit: Int) -> [String] {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return [] }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if limit < recentTunnels.count {
recentTunnels.removeLast(recentTunnels.count - limit)
}
return recentTunnels
}
}
@@ -9,8 +9,8 @@ class ButtonCell: UITableViewCell {
set(value) { button.setTitle(value, for: .normal) }
}
var hasDestructiveAction: Bool {
get { return button.tintColor == .red }
set(value) { button.tintColor = value ? .red : buttonStandardTintColor }
get { return button.tintColor == .systemRed }
set(value) { button.tintColor = value ? .systemRed : buttonStandardTintColor }
}
var onTapped: (() -> Void)?
@@ -9,7 +9,11 @@ class KeyValueCell: UITableViewCell {
let keyLabel = UILabel()
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
keyLabel.adjustsFontForContentSizeCategory = true
keyLabel.textColor = .black
if #available(iOS 13.0, *) {
keyLabel.textColor = .label
} else {
keyLabel.textColor = .black
}
keyLabel.textAlignment = .left
return keyLabel
}()
@@ -31,7 +35,11 @@ class KeyValueCell: UITableViewCell {
valueTextField.autocapitalizationType = .none
valueTextField.autocorrectionType = .no
valueTextField.spellCheckingType = .no
valueTextField.textColor = .gray
if #available(iOS 13.0, *) {
valueTextField.textColor = .secondaryLabel
} else {
valueTextField.textColor = .gray
}
return valueTextField
}()
@@ -56,10 +64,18 @@ class KeyValueCell: UITableViewCell {
var isValueValid = true {
didSet {
if isValueValid {
keyLabel.textColor = .black
if #available(iOS 13.0, *) {
if isValueValid {
keyLabel.textColor = .label
} else {
keyLabel.textColor = .systemRed
}
} else {
keyLabel.textColor = .red
if isValueValid {
keyLabel.textColor = .black
} else {
keyLabel.textColor = .red
}
}
}
}
@@ -16,7 +16,11 @@ class SwitchCell: UITableViewCell {
get { return switchView.isEnabled }
set(value) {
switchView.isEnabled = value
textLabel?.textColor = value ? .black : .gray
if #available(iOS 13.0, *) {
textLabel?.textColor = value ? .label : .secondaryLabel
} else {
textLabel?.textColor = value ? .black : .gray
}
}
}
@@ -28,7 +28,11 @@ class TextCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
message = ""
setTextColor(.black)
if #available(iOS 13.0, *) {
setTextColor(.label)
} else {
setTextColor(.black)
}
setTextAlignment(.left)
}
}
@@ -30,7 +30,11 @@ class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
super.init(style: style, reuseIdentifier: reuseIdentifier)
copyableGesture = false
valueTextField.textColor = .black
if #available(iOS 13.0, *) {
valueTextField.textColor = .label
} else {
valueTextField.textColor = .black
}
valueTextField.isEnabled = true
valueLabelScrollView.isScrollEnabled = false
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true
@@ -29,9 +29,15 @@ class TunnelListCell: UITableViewCell {
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
if #available(iOS 13.0, *) {
let busyIndicator = UIActivityIndicatorView(style: .medium)
busyIndicator.hidesWhenStopped = true
return busyIndicator
} else {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
}
}()
let statusSwitch = UISwitch()
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class LogViewController: UIViewController {
let textView: UITextView = {
let textView = UITextView()
textView.isEditable = false
textView.isSelectable = true
textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
textView.adjustsFontForContentSizeCategory = true
return textView
}()
let busyIndicator: UIActivityIndicatorView = {
if #available(iOS 13.0, *) {
let busyIndicator = UIActivityIndicatorView(style: .medium)
busyIndicator.hidesWhenStopped = true
return busyIndicator
} else {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
}
}()
let paragraphStyle: NSParagraphStyle = {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.setParagraphStyle(NSParagraphStyle.default)
paragraphStyle.lineHeightMultiple = 1.2
return paragraphStyle
}()
var isNextLineHighlighted = false
var logViewHelper: LogViewHelper?
var isFetchingLogEntries = false
private var updateLogEntriesTimer: Timer?
override func loadView() {
view = UIView()
if #available(iOS 13.0, *) {
view.backgroundColor = .systemBackground
} else {
view.backgroundColor = .white
}
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
view.addSubview(busyIndicator)
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
busyIndicator.startAnimating()
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
startUpdatingLogEntries()
}
override func viewDidLoad() {
title = tr("logViewTitle")
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped(sender:)))
}
func updateLogEntries() {
guard !isFetchingLogEntries else { return }
isFetchingLogEntries = true
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
guard let self = self else { return }
defer {
self.isFetchingLogEntries = false
}
if self.busyIndicator.isAnimating {
self.busyIndicator.stopAnimating()
}
guard !fetchedLogEntries.isEmpty else { return }
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
let richText = NSMutableAttributedString()
let bodyFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let captionFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.caption1)
for logEntry in fetchedLogEntries {
var bgColor: UIColor
var fgColor: UIColor
if #available(iOS 13.0, *) {
bgColor = self.isNextLineHighlighted ? .systemGray3 : .systemBackground
fgColor = .label
} else {
bgColor = self.isNextLineHighlighted ? UIColor(white: 0.88, alpha: 1.0) : UIColor.white
fgColor = .black
}
let timestampText = NSAttributedString(string: logEntry.timestamp + "\n", attributes: [.font: captionFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
let messageText = NSAttributedString(string: logEntry.message + "\n", attributes: [.font: bodyFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
richText.append(timestampText)
richText.append(messageText)
self.isNextLineHighlighted.toggle()
}
self.textView.textStorage.append(richText)
if isScrolledToEnd {
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
self.textView.scrollRangeToVisible(endOfCurrentText)
}
}
}
func startUpdatingLogEntries() {
updateLogEntries()
updateLogEntriesTimer?.invalidate()
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
self?.updateLogEntries()
}
updateLogEntriesTimer = timer
RunLoop.main.add(timer, forMode: .common)
}
@objc func saveTapped(sender: AnyObject) {
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
DispatchQueue.global(qos: .userInitiated).async {
if FileManager.default.fileExists(atPath: destinationURL.path) {
let isDeleted = FileManager.deleteFile(at: destinationURL)
if !isDeleted {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
return
}
}
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
DispatchQueue.main.async {
guard isWritten else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
return
}
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
if let sender = sender as? UIBarButtonItem {
activityVC.popoverPresentationController?.barButtonItem = sender
}
activityVC.completionWithItemsHandler = { _, _, _, _ in
// Remove the exported log file after the activity has completed
_ = FileManager.deleteFile(at: destinationURL)
}
self.present(activityVC, animated: true)
}
}
}
}
@@ -11,7 +11,11 @@ class MainViewController: UISplitViewController {
init() {
let detailVC = UIViewController()
detailVC.view.backgroundColor = .white
if #available(iOS 13.0, *) {
detailVC.view.backgroundColor = .systemBackground
} else {
detailVC.view.backgroundColor = .white
}
let detailNC = UINavigationController(rootViewController: detailVC)
let masterVC = TunnelsListTableViewController()
@@ -42,22 +46,25 @@ class MainViewController: UISplitViewController {
TunnelsManager.create { [weak self] result in
guard let self = self else { return }
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
return
case .success(let tunnelsManager):
self.tunnelsManager = tunnelsManager
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
tunnelsManager.activationDelegate = self
self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil
}
let tunnelsManager: TunnelsManager = result.value!
self.tunnelsManager = tunnelsManager
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
tunnelsManager.activationDelegate = self
self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil
}
}
func allTunnelNames() -> [String]? {
guard let tunnelsManager = self.tunnelsManager else { return nil }
return tunnelsManager.mapTunnels { $0.name }
}
}
extension MainViewController: TunnelsManagerActivationDelegate {
@@ -85,19 +92,17 @@ extension MainViewController {
}
}
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool) {
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool, shouldToggleStatus: Bool) {
let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
guard let self = self else { return }
guard let tunnelsListVC = self.tunnelsListVC else { return }
if let tunnel = tunnelsManager.tunnel(named: tunnelName) {
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
if let self = self {
if animated {
self.showDetailViewController(tunnelDetailNC, sender: self)
} else {
UIView.performWithoutAnimation {
self.showDetailViewController(tunnelDetailNC, sender: self)
}
tunnelsListVC.showTunnelDetail(for: tunnel, animated: false)
if shouldToggleStatus {
if tunnel.status == .inactive {
tunnelsManager.startActivation(of: tunnel)
} else if tunnel.status == .active {
tunnelsManager.startDeactivation(of: tunnel)
}
}
}
@@ -108,6 +113,19 @@ extension MainViewController {
onTunnelsManagerReady = showTunnelDetailBlock
}
}
func importFromDisposableFile(url: URL) {
let importFromFileBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self) {
_ = FileManager.deleteFile(at: url)
}
}
if let tunnelsManager = tunnelsManager {
importFromFileBlock(tunnelsManager)
} else {
onTunnelsManagerReady = importFromFileBlock
}
}
}
extension MainViewController: UISplitViewControllerDelegate {
@@ -176,7 +176,11 @@ extension SSIDOptionEditTableViewController {
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = tr("tunnelOnDemandNoSSIDs")
cell.setTextColor(.gray)
if #available(iOS 13.0, *) {
cell.setTextColor(.secondaryLabel)
} else {
cell.setTextColor(.gray)
}
cell.setTextAlignment(.center)
return cell
}
@@ -266,6 +270,10 @@ extension SSIDOptionEditTableViewController {
case .ssidOption:
let previousOption = selectedOption
selectedOption = ssidOptionFields[indexPath.row]
guard previousOption != selectedOption else {
tableView.deselectRow(at: indexPath, animated: true)
return
}
loadSections()
if previousOption == .anySSID {
let indexSet = IndexSet(1 ... 2)
@@ -10,22 +10,24 @@ class SettingsTableViewController: UITableViewController {
case iosAppVersion
case goBackendVersion
case exportZipArchive
case exportLogFile
case viewLog
case donateLink
var localizedUIString: String {
switch self {
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
case .exportLogFile: return tr("settingsExportLogFileButtonTitle")
case .viewLog: return tr("settingsViewLogButtonTitle")
case .donateLink: return tr("donateLink")
}
}
}
let settingsFieldsBySection: [[SettingsFields]] = [
[.iosAppVersion, .goBackendVersion],
[.iosAppVersion, .goBackendVersion, .donateLink],
[.exportZipArchive],
[.exportLogFile]
[.viewLog]
]
let tunnelsManager: TunnelsManager?
@@ -108,41 +110,10 @@ class SettingsTableViewController: UITableViewController {
}
}
func exportLogForLastActivatedTunnel(sourceView: UIView) {
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
func presentLogView() {
let logVC = LogViewController()
navigationController?.pushViewController(logVC, animated: true)
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
DispatchQueue.global(qos: .userInitiated).async {
if FileManager.default.fileExists(atPath: destinationURL.path) {
let isDeleted = FileManager.deleteFile(at: destinationURL)
if !isDeleted {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
return
}
}
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
DispatchQueue.main.async {
guard isWritten else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
return
}
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = sourceView
activityVC.popoverPresentationController?.sourceRect = sourceView.bounds
activityVC.completionWithItemsHandler = { _, _, _, _ in
// Remove the exported log file after the activity has completed
_ = FileManager.deleteFile(at: destinationURL)
}
self.present(activityVC, animated: true)
}
}
}
}
@@ -191,14 +162,23 @@ extension SettingsTableViewController {
self?.exportConfigurationsAsZipFile(sourceView: cell.button)
}
return cell
} else {
assert(field == .exportLogFile)
} else if field == .viewLog {
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
cell.buttonText = field.localizedUIString
cell.onTapped = { [weak self] in
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
self?.presentLogView()
}
return cell
} else if field == .donateLink {
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
cell.buttonText = field.localizedUIString
cell.onTapped = {
if let url = URL(string: "https://www.wireguard.com/donations/"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
return cell
}
fatalError()
}
}
@@ -120,7 +120,7 @@ class TunnelDetailTableViewController: UITableViewController {
let editVC = TunnelEditTableViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
editVC.delegate = self
let editNC = UINavigationController(rootViewController: editVC)
editNC.modalPresentationStyle = .formSheet
editNC.modalPresentationStyle = .fullScreen
self.present(editNC, animated: true)
}
}
@@ -127,10 +127,10 @@ class TunnelEditTableViewController: UITableViewController {
} else {
// We're adding a new tunnel
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
} else {
let tunnel: TunnelContainer = result.value!
case .success(let tunnel):
self?.dismiss(animated: true, completion: nil)
self?.delegate?.tunnelSaved(tunnel: tunnel)
}
@@ -32,9 +32,15 @@ class TunnelsListTableViewController: UIViewController {
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
if #available(iOS 13.0, *) {
let busyIndicator = UIActivityIndicatorView(style: .medium)
busyIndicator.hidesWhenStopped = true
return busyIndicator
} else {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
}
}()
var detailDisplayedTunnel: TunnelContainer?
@@ -46,7 +52,11 @@ class TunnelsListTableViewController: UIViewController {
override func loadView() {
view = UIView()
view.backgroundColor = .white
if #available(iOS 13.0, *) {
view.backgroundColor = .systemBackground
} else {
view.backgroundColor = .white
}
tableView.dataSource = self
tableView.delegate = self
@@ -178,7 +188,7 @@ class TunnelsListTableViewController: UIViewController {
func presentViewControllerForTunnelCreation(tunnelsManager: TunnelsManager) {
let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager)
let editNC = UINavigationController(rootViewController: editVC)
editNC.modalPresentationStyle = .formSheet
editNC.modalPresentationStyle = .fullScreen
present(editNC, animated: true)
}
@@ -251,6 +261,24 @@ class TunnelsListTableViewController: UIViewController {
}
}
}
func showTunnelDetail(for tunnel: TunnelContainer, animated: Bool) {
guard let tunnelsManager = tunnelsManager else { return }
guard let splitViewController = splitViewController else { return }
guard let navController = navigationController else { return }
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
if splitViewController.isCollapsed && navController.viewControllers.count > 1 {
navController.setViewControllers([self, tunnelDetailNC], animated: animated)
} else {
splitViewController.showDetailViewController(tunnelDetailNC, sender: self, animated: animated)
}
detailDisplayedTunnel = tunnel
self.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
@@ -264,9 +292,10 @@ extension TunnelsListTableViewController: QRScanViewControllerDelegate {
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController,
completionHandler: (() -> Void)?) {
tunnelsManager?.add(tunnelConfiguration: tunnelConfiguration) { result in
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: qrScanViewController, onDismissal: completionHandler)
} else {
case .success:
completionHandler?()
}
}
@@ -308,12 +337,7 @@ extension TunnelsListTableViewController: UITableViewDelegate {
}
guard let tunnelsManager = tunnelsManager else { return }
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
showDetailViewController(tunnelDetailNC, sender: self) // Shall get propagated up to the split-vc
detailDisplayedTunnel = tunnel
showTunnelDetail(for: tunnel, animated: true)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
@@ -375,7 +399,11 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
(splitViewController.viewControllers[0] as? UINavigationController)?.popToRootViewController(animated: false)
} else {
let detailVC = UIViewController()
detailVC.view.backgroundColor = .white
if #available(iOS 13.0, *) {
detailVC.view.backgroundColor = .systemBackground
} else {
detailVC.view.backgroundColor = .white
}
let detailNC = UINavigationController(rootViewController: detailVC)
splitViewController.showDetailViewController(detailNC, sender: self)
}
@@ -386,3 +414,15 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
}
}
}
extension UISplitViewController {
func showDetailViewController(_ vc: UIViewController, sender: Any?, animated: Bool) {
if animated {
showDetailViewController(vc, sender: sender)
} else {
UIView.performWithoutAnimation {
showDetailViewController(vc, sender: sender)
}
}
}
}
+195 -20
View File
@@ -2,6 +2,7 @@
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
import ServiceManagement
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@@ -12,36 +13,105 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var manageTunnelsRootVC: ManageTunnelsRootViewController?
var manageTunnelsWindowObject: NSWindow?
var onAppDeactivation: (() -> Void)?
func applicationWillFinishLaunching(_ notification: Notification) {
// To workaround a possible AppKit bug that causes the main menu to become unresponsive sometimes
// (especially when launched through Xcode) if we call setActivationPolicy(.regular) in
// in applicationDidFinishLaunching, we set it to .prohibited here.
// Setting it to .regular would fix that problem too, but at this point, we don't know
// whether the app was launched at login or not, so we're not sure whether we should
// show the app icon in the dock or not.
NSApp.setActivationPolicy(.prohibited)
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
registerLoginItem(shouldLaunchAtLogin: true)
var isLaunchedAtLogin = false
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
isLaunchedAtLogin = LaunchedAtLoginDetector.isLaunchedAtLogin(openAppleEvent: appleEvent)
}
NSApp.mainMenu = MainMenu()
setDockIconAndMainMenuVisibility(isVisible: !isLaunchedAtLogin)
TunnelsManager.create { [weak self] result in
guard let self = self else { return }
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: nil)
return
case .success(let tunnelsManager):
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
statusMenu.windowDelegate = self
let statusItemController = StatusItemController()
statusItemController.statusItem.menu = statusMenu
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
tunnelsTracker.statusMenu = statusMenu
tunnelsTracker.statusItemController = statusItemController
self.tunnelsManager = tunnelsManager
self.tunnelsTracker = tunnelsTracker
self.statusItemController = statusItemController
if !isLaunchedAtLogin {
self.showManageTunnelsWindow(completion: nil)
}
}
}
}
let tunnelsManager: TunnelsManager = result.value!
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool {
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
if LaunchedAtLoginDetector.isReopenedByLoginItemHelper(reopenAppleEvent: appleEvent) {
return false
}
}
if hasVisibleWindows {
return true
}
showManageTunnelsWindow(completion: nil)
return false
}
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
statusMenu.windowDelegate = self
@objc func confirmAndQuit() {
let alert = NSAlert()
alert.messageText = tr("macConfirmAndQuitAlertMessage")
if let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating {
alert.informativeText = tr(format: "macConfirmAndQuitInfoWithActiveTunnel (%@)", currentTunnel.name)
} else {
alert.informativeText = tr("macConfirmAndQuitAlertInfo")
}
alert.addButton(withTitle: tr("macConfirmAndQuitAlertCloseWindow"))
alert.addButton(withTitle: tr("macConfirmAndQuitAlertQuitWireGuard"))
let statusItemController = StatusItemController()
statusItemController.statusItem.menu = statusMenu
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
tunnelsTracker.statusMenu = statusMenu
tunnelsTracker.statusItemController = statusItemController
self.tunnelsManager = tunnelsManager
self.tunnelsTracker = tunnelsTracker
self.statusItemController = statusItemController
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
manageWindow.orderFront(self)
alert.beginSheetModal(for: manageWindow) { response in
switch response {
case .alertFirstButtonReturn:
manageWindow.close()
case .alertSecondButtonReturn:
NSApp.terminate(nil)
default:
break
}
}
}
}
@objc func quit() {
if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
NSApp.activate(ignoringOtherApps: true)
manageWindow.orderFront(self)
return
}
registerLoginItem(shouldLaunchAtLogin: false)
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
NSApp.terminate(nil)
return
@@ -49,8 +119,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let alert = NSAlert()
alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
if let window = manageTunnelsWindowObject {
alert.beginSheetModal(for: window) { _ in
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
manageWindow.orderFront(self)
alert.beginSheetModal(for: manageWindow) { _ in
NSApp.terminate(nil)
}
} else {
@@ -58,12 +130,105 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NSApp.terminate(nil)
}
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
return .terminateNow
}
guard let appleEvent = NSAppleEventManager.shared().currentAppleEvent else {
return .terminateNow
}
guard MacAppStoreUpdateDetector.isUpdatingFromMacAppStore(quitAppleEvent: appleEvent) else {
return .terminateNow
}
let alert = NSAlert()
alert.messageText = tr("macAppStoreUpdatingAlertMessage")
if currentTunnel.isActivateOnDemandEnabled {
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithOnDemand (%@)", currentTunnel.name)
} else {
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)", currentTunnel.name)
}
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
alert.beginSheetModal(for: manageWindow) { _ in }
} else {
alert.runModal()
}
return .terminateCancel
}
func applicationShouldTerminateAfterLastWindowClosed(_ application: NSApplication) -> Bool {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak self] in
self?.setDockIconAndMainMenuVisibility(isVisible: false)
}
return false
}
private func setDockIconAndMainMenuVisibility(isVisible: Bool, completion: (() -> Void)? = nil) {
let currentActivationPolicy = NSApp.activationPolicy()
let newActivationPolicy: NSApplication.ActivationPolicy = isVisible ? .regular : .accessory
guard currentActivationPolicy != newActivationPolicy else {
if newActivationPolicy == .regular {
NSApp.activate(ignoringOtherApps: true)
}
completion?()
return
}
if newActivationPolicy == .regular && NSApp.isActive {
// To workaround a possible AppKit bug that causes the main menu to become unresponsive,
// we should deactivate the app first and then set the activation policy.
// NSApp.deactivate() doesn't always deactivate the app, so we instead use
// setActivationPolicy(.prohibited).
onAppDeactivation = {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
completion?()
}
NSApp.setActivationPolicy(.prohibited)
} else {
NSApp.setActivationPolicy(newActivationPolicy)
if newActivationPolicy == .regular {
NSApp.activate(ignoringOtherApps: true)
}
completion?()
}
}
func applicationDidResignActive(_ notification: Notification) {
onAppDeactivation?()
onAppDeactivation = nil
}
}
extension AppDelegate {
@objc func aboutClicked() {
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
}
let appVersionString = [
tr(format: "macAppVersion (%@)", appVersion),
tr(format: "macGoBackendVersion (%@)", WIREGUARD_GO_VERSION)
].joined(separator: "\n")
let donateString = NSMutableAttributedString(string: tr("donateLink"))
donateString.addAttribute(.link, value: "https://www.wireguard.com/donations/", range: NSRange(location: 0, length: donateString.length))
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
.applicationVersion: appVersionString,
.version: "",
.credits: donateString
])
}
}
extension AppDelegate: StatusMenuWindowDelegate {
func manageTunnelsWindow() -> NSWindow {
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?) {
guard let tunnelsManager = tunnelsManager else {
completion?(nil)
return
}
if manageTunnelsWindowObject == nil {
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager!)
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
let window = NSWindow(contentViewController: manageTunnelsRootVC!)
window.title = tr("macWindowTitleManageTunnels")
window.setContentSize(NSSize(width: 800, height: 480))
@@ -71,6 +236,16 @@ extension AppDelegate: StatusMenuWindowDelegate {
manageTunnelsWindowObject = window
tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
}
return manageTunnelsWindowObject!
setDockIconAndMainMenuVisibility(isVisible: true) { [weak manageTunnelsWindowObject] in
manageTunnelsWindowObject?.makeKeyAndOrderFront(self)
completion?(manageTunnelsWindowObject)
}
}
}
@discardableResult
func registerLoginItem(shouldLaunchAtLogin: Bool) -> Bool {
let appId = Bundle.main.bundleIdentifier!
let helperBundleId = "\(appId).login-item-helper"
return SMLoginItemSetEnabled(helperBundleId as CFString, shouldLaunchAtLogin)
}
@@ -1,29 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class AppStorePrivacyNotice {
// The App Store Review Board does not comprehend the fact that this application
// is not a service and does not have any servers of its own. They therefore require
// us to give a notice regarding collection of user data using our non-existent
// servers. This demand is obviously impossible to fulfill, since it doesn't make sense,
// but we do our best here to show something in that category.
static func show(from sourceVC: NSViewController?, into tunnelsManager: TunnelsManager, _ callback: @escaping () -> Void) {
if tunnelsManager.numberOfTunnels() > 0 {
callback()
return
}
let alert = NSAlert()
alert.messageText = tr("macPrivacyNoticeMessage")
alert.informativeText = tr("macPrivacyNoticeInfo")
alert.alertStyle = NSAlert.Style.warning
if let window = sourceVC?.view.window {
alert.beginSheetModal(for: window) { _ in callback() }
} else {
alert.runModal()
callback()
}
}
}
@@ -5,18 +5,6 @@ import Cocoa
class Application: NSApplication {
private let characterKeyCommands = [
"x": #selector(NSText.cut(_:)),
"c": #selector(NSText.copy(_:)),
"v": #selector(NSText.paste(_:)),
"z": #selector(UndoActionRespondable.undo(_:)),
"a": #selector(NSResponder.selectAll(_:)),
"Z": #selector(UndoActionRespondable.redo(_:)),
"w": #selector(NSWindow.performClose(_:)),
"m": #selector(NSWindow.performMiniaturize(_:)),
"q": #selector(AppDelegate.quit)
]
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
override init() {
@@ -28,21 +16,4 @@ class Application: NSApplication {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func sendEvent(_ event: NSEvent) {
let modifierFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue
if event.type == .keyDown,
(modifierFlags == NSEvent.ModifierFlags.command.rawValue || modifierFlags == NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue),
let selector = characterKeyCommands[event.charactersIgnoringModifiers ?? ""] {
sendAction(selector, to: nil, from: self)
} else {
super.sendEvent(event)
}
}
}
@objc protocol UndoActionRespondable {
func undo(_ sender: AnyObject)
func redo(_ sender: AnyObject)
}
@@ -65,4 +65,4 @@
"version" : 1,
"author" : "xcode"
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

@@ -13,9 +13,7 @@ class ImportPanelPresenter {
openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
guard let tunnelsManager = tunnelsManager else { return }
guard response == .OK else { return }
AppStorePrivacyNotice.show(from: sourceVC, into: tunnelsManager) {
TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
}
TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
}
}
}
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LaunchedAtLoginDetector {
static let launchCode = "LaunchedByWireGuardLoginItemHelper"
static func isLaunchedAtLogin(openAppleEvent: NSAppleEventDescriptor) -> Bool {
guard isOpenEvent(openAppleEvent) else { return false }
guard let propData = openAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
return propData.stringValue == launchCode
}
static func isReopenedByLoginItemHelper(reopenAppleEvent: NSAppleEventDescriptor) -> Bool {
guard isReopenEvent(reopenAppleEvent) else { return false }
guard let propData = reopenAppleEvent.paramDescriptor(forKeyword: keyAEPropData) else { return false }
return propData.stringValue == launchCode
}
}
private func isOpenEvent(_ event: NSAppleEventDescriptor) -> Bool {
return event.eventClass == kCoreEventClass && event.eventID == kAEOpenApplication
}
private func isReopenEvent(_ event: NSAppleEventDescriptor) -> Bool {
return event.eventClass == kCoreEventClass && event.eventID == kAEReopenApplication
}
@@ -0,0 +1,36 @@
<?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>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></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>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(VERSION_NAME)</string>
<key>CFBundleVersion</key>
<string>$(VERSION_ID)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSBackgroundOnly</key>
<true/>
<key>com.wireguard.macos.app_id</key>
<string>$(APP_ID_MACOS)</string>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?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>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSString *appIdInfoDictionaryKey = @"com.wireguard.macos.app_id";
NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:appIdInfoDictionaryKey];
NSString *launchCode = @"LaunchedByWireGuardLoginItemHelper";
NSAppleEventDescriptor *paramDescriptor = [NSAppleEventDescriptor descriptorWithString:launchCode];
[NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation
additionalEventParamDescriptor:paramDescriptor launchIdentifier:NULL];
return 0;
}
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class MacAppStoreUpdateDetector {
static func isUpdatingFromMacAppStore(quitAppleEvent: NSAppleEventDescriptor) -> Bool {
guard isQuitEvent(quitAppleEvent) else { return false }
guard let senderPIDDescriptor = quitAppleEvent.attributeDescriptor(forKeyword: keySenderPIDAttr) else { return false }
let pid = senderPIDDescriptor.int32Value
guard let executablePath = getExecutablePath(from: pid) else { return false }
wg_log(.debug, message: "aevt/quit Apple event received from: \(executablePath)")
if executablePath.hasPrefix("/System/Library/") {
let executableName = URL(fileURLWithPath: executablePath, isDirectory: false).lastPathComponent
return executableName == "com.apple.CommerceKit.StoreAEService"
}
return false
}
}
private func isQuitEvent(_ event: NSAppleEventDescriptor) -> Bool {
return event.eventClass == kCoreEventClass && event.eventID == kAEQuitApplication
}
private func getExecutablePath(from pid: pid_t) -> String? {
let bufferSize = Int(PATH_MAX)
var buffer = Data(capacity: bufferSize)
return buffer.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> String? in
if let basePtr = ptr.baseAddress {
let byteCount = proc_pidpath(pid, basePtr, UInt32(bufferSize))
return byteCount > 0 ? String(cString: basePtr.bindMemory(to: CChar.self, capacity: bufferSize)) : nil
}
return nil
}
}
+130
View File
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
import Cocoa
// swiftlint:disable colon
class MainMenu: NSMenu {
init() {
super.init(title: "")
addSubmenu(createApplicationMenu())
addSubmenu(createFileMenu())
addSubmenu(createEditMenu())
addSubmenu(createTunnelMenu())
addSubmenu(createWindowMenu())
}
required init(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func addSubmenu(_ menu: NSMenu) {
let menuItem = self.addItem(withTitle: "", action: nil, keyEquivalent: "")
self.setSubmenu(menu, for: menuItem)
}
private func createApplicationMenu() -> NSMenu {
let menu = NSMenu()
let aboutMenuItem = menu.addItem(withTitle: tr("macMenuAbout"),
action: #selector(AppDelegate.aboutClicked), keyEquivalent: "")
aboutMenuItem.target = NSApp.delegate
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuViewLog"),
action: #selector(TunnelsListTableViewController.handleViewLogAction), keyEquivalent: "")
menu.addItem(NSMenuItem.separator())
let hideMenuItem = menu.addItem(withTitle: tr("macMenuHideApp"),
action: #selector(NSApplication.hide), keyEquivalent: "h")
hideMenuItem.target = NSApp
let hideOthersMenuItem = menu.addItem(withTitle: tr("macMenuHideOtherApps"),
action: #selector(NSApplication.hideOtherApplications), keyEquivalent: "h")
hideOthersMenuItem.keyEquivalentModifierMask = [.command, .option]
hideOthersMenuItem.target = NSApp
let showAllMenuItem = menu.addItem(withTitle: tr("macMenuShowAllApps"),
action: #selector(NSApplication.unhideAllApplications), keyEquivalent: "")
showAllMenuItem.target = NSApp
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuQuit"),
action: #selector(AppDelegate.confirmAndQuit), keyEquivalent: "q")
return menu
}
private func createFileMenu() -> NSMenu {
let menu = NSMenu(title: tr("macMenuFile"))
menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"),
action: #selector(TunnelsListTableViewController.handleAddEmptyTunnelAction), keyEquivalent: "n")
menu.addItem(withTitle: tr("macMenuImportTunnels"),
action: #selector(TunnelsListTableViewController.handleImportTunnelAction), keyEquivalent: "o")
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuExportTunnels"),
action: #selector(TunnelsListTableViewController.handleExportTunnelsAction), keyEquivalent: "")
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuCloseWindow"), action: #selector(NSWindow.performClose(_:)), keyEquivalent:"w")
return menu
}
private func createEditMenu() -> NSMenu {
let menu = NSMenu(title: tr("macMenuEdit"))
menu.addItem(withTitle: "", action: #selector(UndoActionRespondable.undo(_:)), keyEquivalent:"z")
menu.addItem(withTitle: "", action: #selector(UndoActionRespondable.redo(_:)), keyEquivalent:"Z")
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuCut"), action: #selector(NSText.cut(_:)), keyEquivalent:"x")
menu.addItem(withTitle: tr("macMenuCopy"), action: #selector(NSText.copy(_:)), keyEquivalent:"c")
menu.addItem(withTitle: tr("macMenuPaste"), action: #selector(NSText.paste(_:)), keyEquivalent:"v")
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuSelectAll"), action: #selector(NSText.selectAll(_:)), keyEquivalent:"a")
return menu
}
private func createTunnelMenu() -> NSMenu {
let menu = NSMenu(title: tr("macMenuTunnel"))
menu.addItem(withTitle: tr("macMenuToggleStatus"), action: #selector(TunnelDetailTableViewController.handleToggleActiveStatusAction), keyEquivalent:"t")
menu.addItem(NSMenuItem.separator())
menu.addItem(withTitle: tr("macMenuEditTunnel"), action: #selector(TunnelDetailTableViewController.handleEditTunnelAction), keyEquivalent:"e")
menu.addItem(withTitle: tr("macMenuDeleteSelected"), action: #selector(TunnelsListTableViewController.handleRemoveTunnelAction), keyEquivalent: "")
return menu
}
private func createWindowMenu() -> NSMenu {
let menu = NSMenu(title: tr("macMenuWindow"))
menu.addItem(withTitle: tr("macMenuMinimize"), action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent:"m")
menu.addItem(withTitle: tr("macMenuZoom"), action: #selector(NSWindow.performZoom(_:)), keyEquivalent:"")
menu.addItem(NSMenuItem.separator())
let fullScreenMenuItem = menu.addItem(withTitle: "", action: #selector(NSWindow.toggleFullScreen(_:)), keyEquivalent:"f")
fullScreenMenuItem.keyEquivalentModifierMask = [.command, .control]
return menu
}
}
@objc protocol UndoActionRespondable {
func undo(_ sender: AnyObject)
func redo(_ sender: AnyObject)
}
+25 -26
View File
@@ -4,7 +4,7 @@
import Cocoa
protocol StatusMenuWindowDelegate: class {
func manageTunnelsWindow() -> NSWindow
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?)
}
class StatusMenu: NSMenu {
@@ -13,6 +13,7 @@ class StatusMenu: NSMenu {
var statusMenuItem: NSMenuItem?
var networksMenuItem: NSMenuItem?
var deactivateMenuItem: NSMenuItem?
var firstTunnelMenuItemIndex = 0
var numberOfTunnelMenuItems = 0
@@ -53,16 +54,22 @@ class StatusMenu: NSMenu {
networksMenuItem.isEnabled = false
networksMenuItem.isHidden = true
addItem(networksMenuItem)
let deactivateMenuItem = NSMenuItem(title: tr("macToggleStatusButtonDeactivate"), action: #selector(deactivateClicked), keyEquivalent: "")
deactivateMenuItem.target = self
deactivateMenuItem.isHidden = true
addItem(deactivateMenuItem)
self.statusMenuItem = statusMenuItem
self.networksMenuItem = networksMenuItem
self.deactivateMenuItem = deactivateMenuItem
}
func updateStatusMenuItems(with tunnel: TunnelContainer?) {
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return }
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem, let deactivateMenuItem = deactivateMenuItem else { return }
guard let tunnel = tunnel else {
statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
networksMenuItem.title = ""
networksMenuItem.isHidden = true
deactivateMenuItem.isHidden = true
return
}
var statusText: String
@@ -98,6 +105,7 @@ class StatusMenu: NSMenu {
}
networksMenuItem.isHidden = false
}
deactivateMenuItem.isHidden = tunnel.status != .active
}
func addTunnelMenuItems() -> Bool {
@@ -119,14 +127,20 @@ class StatusMenu: NSMenu {
}
func addApplicationItems() {
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
aboutItem.target = self
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(AppDelegate.aboutClicked), keyEquivalent: "")
aboutItem.target = NSApp.delegate
addItem(aboutItem)
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
quitItem.target = NSApp.delegate
addItem(quitItem)
}
@objc func deactivateClicked() {
if let currentTunnel = currentTunnel {
tunnelsManager.startDeactivation(of: currentTunnel)
}
}
@objc func tunnelClicked(sender: AnyObject) {
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
if tunnelMenuItem.state == .off {
@@ -137,32 +151,16 @@ class StatusMenu: NSMenu {
}
@objc func manageTunnelsClicked() {
NSApp.activate(ignoringOtherApps: true)
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
manageTunnelsWindow.makeKeyAndOrderFront(self)
windowDelegate?.showManageTunnelsWindow(completion: nil)
}
@objc func importTunnelsClicked() {
NSApp.activate(ignoringOtherApps: true)
guard let manageTunnelsWindow = windowDelegate?.manageTunnelsWindow() else { return }
manageTunnelsWindow.makeKeyAndOrderFront(self)
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: manageTunnelsWindow.contentViewController)
}
@objc func aboutClicked() {
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
windowDelegate?.showManageTunnelsWindow { [weak self] manageTunnelsWindow in
guard let self = self else { return }
guard let manageTunnelsWindow = manageTunnelsWindow else { return }
ImportPanelPresenter.presentImportPanel(tunnelsManager: self.tunnelsManager,
sourceVC: manageTunnelsWindow.contentViewController)
}
let appVersionString = [
tr(format: "macAppVersion (%@)", appVersion),
tr(format: "macGoBackendVersion (%@)", WIREGUARD_GO_VERSION)
].joined(separator: "\n")
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
.applicationVersion: appVersionString,
.version: ""
])
}
}
@@ -170,6 +168,7 @@ extension StatusMenu {
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
menuItem.target = self
menuItem.isHidden = !tunnel.isTunnelAvailableToUser
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
if numberOfTunnelMenuItems == 0 {
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
@@ -19,6 +19,7 @@ class ConfTextStorage: NSTextStorage {
private(set) var hasOnePeer: Bool = false
private(set) var lastOnePeerAllowedIPs = [String]()
private(set) var lastOnePeerDNSServers = [String]()
private(set) var lastOnePeerHasPublicKey = false
override init() {
backingStore = NSMutableAttributedString(string: "")
@@ -88,6 +89,7 @@ class ConfTextStorage: NSTextStorage {
hasOnePeer = false
lastOnePeerAllowedIPs = []
lastOnePeerDNSServers = []
lastOnePeerHasPublicKey = false
}
func evaluateExcludePrivateIPs(highlightSpans: UnsafePointer<highlight_span>) {
@@ -125,6 +127,8 @@ class ConfTextStorage: NSTextStorage {
backingStore.attributedSubstring(from: NSRange(location: nextnext.pointee.start, length: nextnext.pointee.len)).string
}
lastOnePeerAllowedIPs.append(substring)
} else if span.type == HighlightPublicKey {
lastOnePeerHasPublicKey = true
}
spans = spans.successor()
}
@@ -143,9 +147,10 @@ class ConfTextStorage: NSTextStorage {
.font: defaultFont
]
backingStore.setAttributes(defaultAttributes, range: fullTextRange)
var spans = highlight_config(backingStore.string.cString(using: String.Encoding.utf8))!
var spans = highlight_config(backingStore.string)!
evaluateExcludePrivateIPs(highlightSpans: spans)
let spansStart = spans
while spans.pointee.type != HighlightEnd {
let span = spans.pointee
@@ -165,6 +170,7 @@ class ConfTextStorage: NSTextStorage {
spans = spans.successor()
}
backingStore.endEditing()
free(spansStart)
beginEditing()
edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
@@ -14,6 +14,7 @@ class ConfTextView: NSTextView {
override var string: String {
didSet {
confTextStorage.highlightSyntax()
updateConfigData()
}
}
@@ -53,6 +54,21 @@ class ConfTextView: NSTextView {
}
}
private func updateConfigData() {
if hasError != confTextStorage.hasError {
hasError = confTextStorage.hasError
}
if privateKeyString != confTextStorage.privateKeyString {
privateKeyString = confTextStorage.privateKeyString
}
let hasSyntaxError = confTextStorage.hasError
let hasSemanticError = confTextStorage.privateKeyString == nil || !confTextStorage.lastOnePeerHasPublicKey
let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !hasSyntaxError && !hasSemanticError ? confTextStorage.lastOnePeerAllowedIPs : nil
if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs {
singlePeerAllowedIPs = updatedSinglePeerAllowedIPs
}
}
func setConfText(_ text: String) {
let fullTextRange = NSRange(location: 0, length: (string as NSString).length)
if shouldChangeText(in: fullTextRange, replacementString: text) {
@@ -66,16 +82,7 @@ extension ConfTextView: NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
confTextStorage.highlightSyntax()
if hasError != confTextStorage.hasError {
hasError = confTextStorage.hasError
}
if privateKeyString != confTextStorage.privateKeyString {
privateKeyString = confTextStorage.privateKeyString
}
let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !confTextStorage.hasError ? confTextStorage.lastOnePeerAllowedIPs : nil
if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs {
singlePeerAllowedIPs = updatedSinglePeerAllowedIPs
}
updateConfigData()
needsDisplay = true
}
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class DeleteTunnelsConfirmationAlert: NSAlert {
var alertDeleteButton: NSButton?
var alertCancelButton: NSButton?
var onDeleteClicked: ((_ completionHandler: @escaping () -> Void) -> Void)?
override init() {
super.init()
let alertDeleteButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
alertDeleteButton.target = self
alertDeleteButton.action = #selector(removeTunnelAlertDeleteClicked)
self.alertDeleteButton = alertDeleteButton
self.alertCancelButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
}
@objc func removeTunnelAlertDeleteClicked() {
alertDeleteButton?.title = tr("macDeleteTunnelConfirmationAlertButtonTitleDeleting")
alertDeleteButton?.isEnabled = false
alertCancelButton?.isEnabled = false
if let onDeleteClicked = onDeleteClicked {
onDeleteClicked { [weak self] in
guard let self = self else { return }
self.window.sheetParent?.endSheet(self.window)
}
}
}
func beginSheetModal(for sheetWindow: NSWindow) {
beginSheetModal(for: sheetWindow) { _ in }
}
}
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LogViewCell: NSTableCellView {
var text: String = "" {
didSet { textField?.stringValue = text }
}
init() {
super.init(frame: .zero)
let textField = NSTextField(wrappingLabelWithString: "")
addSubview(textField)
textField.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: self.leadingAnchor),
textField.trailingAnchor.constraint(equalTo: self.trailingAnchor),
textField.topAnchor.constraint(equalTo: self.topAnchor),
textField.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
self.textField = textField
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
textField?.stringValue = ""
}
}
class LogViewTimestampCell: LogViewCell {
override init() {
super.init()
if let textField = textField {
textField.maximumNumberOfLines = 1
textField.lineBreakMode = .byClipping
textField.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
textField.setContentHuggingPriority(.defaultLow, for: .vertical)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LogViewMessageCell: LogViewCell {
override init() {
super.init()
if let textField = textField {
textField.maximumNumberOfLines = 0
textField.lineBreakMode = .byWordWrapping
textField.setContentCompressionResistancePriority(.required, for: .vertical)
textField.setContentHuggingPriority(.required, for: .vertical)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,274 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LogViewController: NSViewController {
enum LogColumn: String {
case time = "Time"
case logMessage = "LogMessage"
func createColumn() -> NSTableColumn {
return NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue))
}
func isRepresenting(tableColumn: NSTableColumn?) -> Bool {
return tableColumn?.identifier.rawValue == rawValue
}
}
let scrollView: NSScrollView = {
let scrollView = NSScrollView()
scrollView.hasVerticalScroller = true
scrollView.autohidesScrollers = false
scrollView.borderType = .bezelBorder
return scrollView
}()
let tableView: NSTableView = {
let tableView = NSTableView()
let timeColumn = LogColumn.time.createColumn()
timeColumn.title = tr("macLogColumnTitleTime")
timeColumn.width = 160
timeColumn.resizingMask = []
tableView.addTableColumn(timeColumn)
let messageColumn = LogColumn.logMessage.createColumn()
messageColumn.title = tr("macLogColumnTitleLogMessage")
messageColumn.minWidth = 360
messageColumn.resizingMask = .autoresizingMask
tableView.addTableColumn(messageColumn)
tableView.rowSizeStyle = .custom
tableView.rowHeight = 16
tableView.usesAlternatingRowBackgroundColors = true
tableView.usesAutomaticRowHeights = true
tableView.allowsColumnReordering = false
tableView.allowsColumnResizing = true
tableView.allowsMultipleSelection = true
return tableView
}()
let progressIndicator: NSProgressIndicator = {
let progressIndicator = NSProgressIndicator()
progressIndicator.controlSize = .small
progressIndicator.isIndeterminate = true
progressIndicator.style = .spinning
progressIndicator.isDisplayedWhenStopped = false
return progressIndicator
}()
let closeButton: NSButton = {
let button = NSButton()
button.title = tr("macLogButtonTitleClose")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
let saveButton: NSButton = {
let button = NSButton()
button.title = tr("macLogButtonTitleSave")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
let logViewHelper: LogViewHelper?
var logEntries = [LogViewHelper.LogEntry]()
var isFetchingLogEntries = false
var isInScrolledToEndMode = true
private var updateLogEntriesTimer: Timer?
init() {
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
tableView.dataSource = self
tableView.delegate = self
closeButton.target = self
closeButton.action = #selector(closeClicked)
saveButton.target = self
saveButton.action = #selector(saveClicked)
saveButton.isEnabled = false
let clipView = NSClipView()
clipView.documentView = tableView
scrollView.contentView = clipView
_ = NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
let lastVisibleRowIndex = self.tableView.row(at: NSPoint(x: 0, y: self.scrollView.contentView.documentVisibleRect.maxY - 1))
self.isInScrolledToEndMode = lastVisibleRowIndex < 0 || lastVisibleRowIndex == self.logEntries.count - 1
}
_ = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
if self.isInScrolledToEndMode {
DispatchQueue.main.async {
self.tableView.scroll(NSPoint(x: 0, y: self.tableView.frame.maxY - clipView.documentVisibleRect.height))
}
}
}
let margin: CGFloat = 20
let internalSpacing: CGFloat = 10
let buttonRowStackView = NSStackView()
buttonRowStackView.addView(closeButton, in: .leading)
buttonRowStackView.addView(saveButton, in: .trailing)
buttonRowStackView.orientation = .horizontal
buttonRowStackView.spacing = internalSpacing
let containerView = NSView()
[scrollView, progressIndicator, buttonRowStackView].forEach { view in
containerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
scrollView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: margin),
buttonRowStackView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: internalSpacing),
buttonRowStackView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
containerView.rightAnchor.constraint(equalTo: buttonRowStackView.rightAnchor, constant: margin),
containerView.bottomAnchor.constraint(equalTo: buttonRowStackView.bottomAnchor, constant: margin),
progressIndicator.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
progressIndicator.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor)
])
NSLayoutConstraint.activate([
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 640),
containerView.widthAnchor.constraint(lessThanOrEqualToConstant: 1200),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
])
containerView.frame = NSRect(x: 0, y: 0, width: 640, height: 480)
view = containerView
progressIndicator.startAnimation(self)
startUpdatingLogEntries()
}
func updateLogEntries() {
guard !isFetchingLogEntries else { return }
isFetchingLogEntries = true
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
guard let self = self else { return }
defer {
self.isFetchingLogEntries = false
}
if !self.progressIndicator.isHidden {
self.progressIndicator.stopAnimation(self)
self.saveButton.isEnabled = true
}
guard !fetchedLogEntries.isEmpty else { return }
let oldCount = self.logEntries.count
self.logEntries.append(contentsOf: fetchedLogEntries)
self.tableView.insertRows(at: IndexSet(integersIn: oldCount ..< oldCount + fetchedLogEntries.count), withAnimation: .slideDown)
}
}
func startUpdatingLogEntries() {
updateLogEntries()
updateLogEntriesTimer?.invalidate()
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
self?.updateLogEntries()
}
updateLogEntriesTimer = timer
RunLoop.main.add(timer, forMode: .common)
}
func stopUpdatingLogEntries() {
updateLogEntriesTimer?.invalidate()
updateLogEntriesTimer = nil
}
override func viewWillAppear() {
view.window?.setFrameAutosaveName(NSWindow.FrameAutosaveName("LogWindow"))
}
override func viewWillDisappear() {
super.viewWillDisappear()
stopUpdatingLogEntries()
}
@objc func saveClicked() {
let savePanel = NSSavePanel()
savePanel.prompt = tr("macSheetButtonExportLog")
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
savePanel.beginSheetModal(for: self.view.window!) { [weak self] response in
guard response == .OK else { return }
guard let destinationURL = savePanel.url else { return }
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
guard isWritten else {
DispatchQueue.main.async { [weak self] in
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
}
return
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.presentingViewController?.dismiss(self)
}
}
}
}
@objc func closeClicked() {
presentingViewController?.dismiss(self)
}
@objc func copy(_ sender: Any?) {
let text = tableView.selectedRowIndexes.sorted().reduce("") { $0 + self.logEntries[$1].text() + "\n" }
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.writeObjects([text as NSString])
}
}
extension LogViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return logEntries.count
}
}
extension LogViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if LogColumn.time.isRepresenting(tableColumn: tableColumn) {
let cell: LogViewTimestampCell = tableView.dequeueReusableCell()
cell.text = logEntries[row].timestamp
return cell
} else if LogColumn.logMessage.isRepresenting(tableColumn: tableColumn) {
let cell: LogViewMessageCell = tableView.dequeueReusableCell()
cell.text = logEntries[row].message
return cell
} else {
fatalError()
}
}
}
extension LogViewController {
override func cancelOperation(_ sender: Any?) {
closeClicked()
}
}
@@ -81,16 +81,20 @@ extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegat
assert(!tunnelIndices.isEmpty)
if tunnelIndices.count == 1 {
let tunnel = tunnelsManager.tunnel(at: tunnelIndices.first!)
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
setTunnelDetailContentVC(tunnelDetailVC)
self.tunnelDetailVC = tunnelDetailVC
} else if tunnelIndices.count > 1 {
let multiSelectionVC: ButtonedDetailViewController
if let buttonedDetailVC = tunnelDetailContentVC as? ButtonedDetailViewController {
multiSelectionVC = buttonedDetailVC
if tunnel.isTunnelAvailableToUser {
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
setTunnelDetailContentVC(tunnelDetailVC)
self.tunnelDetailVC = tunnelDetailVC
} else {
multiSelectionVC = ButtonedDetailViewController()
let unusableTunnelDetailVC = tunnelDetailContentVC as? UnusableTunnelDetailViewController ?? UnusableTunnelDetailViewController()
unusableTunnelDetailVC.onButtonClicked = { [weak tunnelsListVC] in
tunnelsListVC?.handleRemoveTunnelAction()
}
setTunnelDetailContentVC(unusableTunnelDetailVC)
self.tunnelDetailVC = nil
}
} else if tunnelIndices.count > 1 {
let multiSelectionVC = tunnelDetailContentVC as? ButtonedDetailViewController ?? ButtonedDetailViewController()
multiSelectionVC.setButtonTitle(tr(format: "macButtonDeleteTunnels (%d)", tunnelIndices.count))
multiSelectionVC.onButtonClicked = { [weak tunnelsListVC] in
tunnelsListVC?.handleRemoveTunnelAction()
@@ -113,25 +117,19 @@ extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegat
}
extension ManageTunnelsRootViewController {
override func keyDown(with event: NSEvent) {
let modifierFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue
let isCmdOrCmdShiftDown = (modifierFlags == NSEvent.ModifierFlags.command.rawValue || modifierFlags == NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue)
if event.specialKey == .delete {
tunnelsListVC?.handleRemoveTunnelAction()
} else if isCmdOrCmdShiftDown {
switch event.charactersIgnoringModifiers {
case "n":
tunnelsListVC?.handleAddEmptyTunnelAction()
case "o":
tunnelsListVC?.handleImportTunnelAction()
case "t":
tunnelDetailVC?.handleToggleActiveStatusAction()
case "e":
tunnelDetailVC?.handleEditTunnelAction()
default:
break
}
override func supplementalTarget(forAction action: Selector, sender: Any?) -> Any? {
switch action {
case #selector(TunnelsListTableViewController.handleViewLogAction),
#selector(TunnelsListTableViewController.handleAddEmptyTunnelAction),
#selector(TunnelsListTableViewController.handleImportTunnelAction),
#selector(TunnelsListTableViewController.handleExportTunnelsAction),
#selector(TunnelsListTableViewController.handleRemoveTunnelAction):
return tunnelsListVC
case #selector(TunnelDetailTableViewController.handleToggleActiveStatusAction),
#selector(TunnelDetailTableViewController.handleEditTunnelAction):
return tunnelDetailVC
default:
return super.supplementalTarget(forAction: action, sender: sender)
}
}
}
@@ -101,9 +101,14 @@ class TunnelDetailTableViewController: NSViewController {
super.init(nibName: nil, bundle: nil)
updateTableViewModelRowsBySection()
updateTableViewModelRows()
updateStatus()
statusObservationToken = tunnel.observe(\TunnelContainer.status) { [weak self] _, _ in
self?.updateStatus()
guard let self = self else { return }
if tunnel.status == .active {
self.startUpdatingRuntimeConfiguration()
} else if tunnel.status == .inactive {
self.reloadRuntimeConfiguration()
self.stopUpdatingRuntimeConfiguration()
}
}
}
@@ -200,15 +205,6 @@ class TunnelDetailTableViewController: NSViewController {
tableViewModelRows = tableViewModelRowsBySection.flatMap { $0.filter { $0.isVisible }.map { $0.modelRow } }
}
func updateStatus() {
if tunnel.status == .active {
startUpdatingRuntimeConfiguration()
} else if tunnel.status == .inactive {
reloadRuntimeConfiguration()
stopUpdatingRuntimeConfiguration()
}
}
@objc func handleEditTunnelAction() {
PrivateDataConfirmation.confirmAccess(to: tr("macViewPrivateData")) { [weak self] in
guard let self = self else { return }
@@ -227,6 +223,12 @@ class TunnelDetailTableViewController: NSViewController {
}
}
override func viewWillAppear() {
if tunnel.status == .active {
startUpdatingRuntimeConfiguration()
}
}
override func viewWillDisappear() {
super.viewWillDisappear()
if let tunnelEditVC = tunnelEditVC {
@@ -503,7 +505,6 @@ extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
updateTableViewModelRowsBySection()
updateTableViewModelRows()
updateStatus()
tableView.reloadData()
self.tunnelEditVC = nil
}
@@ -73,6 +73,8 @@ class TunnelEditViewController: NSViewController {
button.title = tr("macEditSave")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
button.keyEquivalent = "s"
button.keyEquivalentModifierMask = [.command]
return button
}()
@@ -117,6 +119,8 @@ class TunnelEditViewController: NSViewController {
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n"
publicKeyRow.value = publicKey.base64Key() ?? ""
textView.string = bootstrappingText
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: nil)
dnsServersAddedToAllowedIPs = nil
}
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
if let privateKeyString = textView.privateKeyString,
@@ -233,26 +237,26 @@ class TunnelEditViewController: NSViewController {
if let tunnel = tunnel {
// We're modifying an existing tunnel
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
self?.setUserInteractionEnabled(true)
guard let self = self else { return }
self.setUserInteractionEnabled(true)
if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self)
return
}
self?.dismiss(self)
self?.delegate?.tunnelSaved(tunnel: tunnel)
self.delegate?.tunnelSaved(tunnel: tunnel)
self.presentingViewController?.dismiss(self)
}
} else {
// We're creating a new tunnel
AppStorePrivacyNotice.show(from: self, into: tunnelsManager) { [weak self] in
self?.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
self?.setUserInteractionEnabled(true)
if let error = result.error {
ErrorPresenter.showErrorAlert(error: error, from: self)
return
}
let tunnel: TunnelContainer = result.value!
self?.dismiss(self)
self?.delegate?.tunnelSaved(tunnel: tunnel)
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
guard let self = self else { return }
self.setUserInteractionEnabled(true)
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
case .success(let tunnel):
self.delegate?.tunnelSaved(tunnel: tunnel)
self.presentingViewController?.dismiss(self)
}
}
}
@@ -260,7 +264,7 @@ class TunnelEditViewController: NSViewController {
@objc func handleDiscardAction() {
delegate?.tunnelEditingCancelled()
dismiss(self)
presentingViewController?.dismiss(self)
}
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
@@ -287,3 +291,9 @@ class TunnelEditViewController: NSViewController {
}
}
}
extension TunnelEditViewController {
override func cancelOperation(_ sender: Any?) {
handleDiscardAction()
}
}
@@ -12,6 +12,7 @@ class TunnelsListTableViewController: NSViewController {
let tunnelsManager: TunnelsManager
weak var delegate: TunnelsListTableViewControllerDelegate?
var isRemovingTunnelsFromWithinTheApp = false
let tableView: NSTableView = {
let tableView = NSTableView()
@@ -53,7 +54,7 @@ class TunnelsListTableViewController: NSViewController {
let menu = NSMenu()
menu.addItem(imageItem)
menu.addItem(withTitle: tr("macMenuExportLog"), action: #selector(handleExportLogAction), keyEquivalent: "")
menu.addItem(withTitle: tr("macMenuViewLog"), action: #selector(handleViewLogAction), keyEquivalent: "")
menu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
menu.autoenablesItems = false
@@ -162,60 +163,43 @@ class TunnelsListTableViewController: NSViewController {
@objc func handleRemoveTunnelAction() {
guard let window = view.window else { return }
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted()
let selectedTunnels = selectedTunnelIndices.compactMap { tunnelIndex in
tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() ? tunnelsManager.tunnel(at: tunnelIndex) : nil
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted().filter { $0 >= 0 && $0 < tunnelsManager.numberOfTunnels() }
guard !selectedTunnelIndices.isEmpty else { return }
var nextSelection = selectedTunnelIndices.last! + 1
if nextSelection >= tunnelsManager.numberOfTunnels() {
nextSelection = max(selectedTunnelIndices.first! - 1, 0)
}
guard !selectedTunnels.isEmpty else { return }
let alert = NSAlert()
if selectedTunnels.count == 1 {
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", selectedTunnels.first!.name)
let alert = DeleteTunnelsConfirmationAlert()
if selectedTunnelIndices.count == 1 {
let firstSelectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndices.first!)
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", firstSelectedTunnel.name)
} else {
alert.messageText = tr(format: "macDeleteMultipleTunnelsConfirmationAlertMessage (%d)", selectedTunnels.count)
alert.messageText = tr(format: "macDeleteMultipleTunnelsConfirmationAlertMessage (%d)", selectedTunnelIndices.count)
}
alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo")
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
alert.beginSheetModal(for: window) { [weak self] response in
guard response == .alertFirstButtonReturn else { return }
self?.removeButton.isEnabled = false
self?.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
alert.onDeleteClicked = { [weak self] completion in
guard let self = self else { return }
self.selectTunnel(at: nextSelection)
let selectedTunnels = selectedTunnelIndices.map { self.tunnelsManager.tunnel(at: $0) }
self.isRemovingTunnelsFromWithinTheApp = true
self.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
guard let self = self else { return }
defer { self.removeButton.isEnabled = true }
self.isRemovingTunnelsFromWithinTheApp = false
defer { completion() }
if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self)
return
}
}
}
}
alert.beginSheetModal(for: window)
}
@objc func handleExportLogAction() {
guard let window = view.window else { return }
let savePanel = NSSavePanel()
savePanel.prompt = tr("macSheetButtonExportLog")
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
savePanel.beginSheetModal(for: window) { response in
guard response == .OK else { return }
guard let destinationURL = savePanel.url else { return }
DispatchQueue.global(qos: .userInitiated).async {
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
guard isWritten else {
DispatchQueue.main.async { [weak self] in
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
}
return
}
}
}
@objc func handleViewLogAction() {
let logVC = LogViewController()
self.presentAsSheet(logVC)
}
@objc func handleExportTunnelsAction() {
@@ -299,14 +283,14 @@ extension TunnelsListTableViewController {
}
func tunnelRemoved(at index: Int) {
let selectedTunnelIndex = tableView.selectedRow
let selectedIndices = tableView.selectedRowIndexes
let isSingleSelectedTunnelBeingRemoved = selectedIndices.contains(index) && selectedIndices.count == 1
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
if tunnelsManager.numberOfTunnels() == 0 {
delegate?.tunnelsListEmpty()
}
let tunnelIndex = min(selectedTunnelIndex, self.tunnelsManager.numberOfTunnels() - 1)
if tunnelIndex >= 0 {
self.selectTunnel(at: tunnelIndex)
} else if !isRemovingTunnelsFromWithinTheApp && isSingleSelectedTunnelBeingRemoved {
let newSelection = min(index, tunnelsManager.numberOfTunnels() - 1)
tableView.selectRowIndexes(IndexSet(integer: newSelection), byExtendingSelection: false)
}
}
}
@@ -332,6 +316,23 @@ extension TunnelsListTableViewController: NSTableViewDelegate {
}
}
extension TunnelsListTableViewController {
override func keyDown(with event: NSEvent) {
if event.specialKey == .delete {
handleRemoveTunnelAction()
}
}
}
extension TunnelsListTableViewController: NSMenuItemValidation {
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
if menuItem.action == #selector(TunnelsListTableViewController.handleRemoveTunnelAction) {
return !tableView.selectedRowIndexes.isEmpty
}
return true
}
}
class FillerButton: NSButton {
override var intrinsicContentSize: NSSize {
return NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class UnusableTunnelDetailViewController: NSViewController {
var onButtonClicked: (() -> Void)?
let messageLabel: NSTextField = {
let text = tr("macUnusableTunnelMessage")
let boldFont = NSFont.boldSystemFont(ofSize: NSFont.systemFontSize)
let boldText = NSAttributedString(string: text, attributes: [.font: boldFont])
let label = NSTextField(labelWithAttributedString: boldText)
return label
}()
let infoLabel: NSTextField = {
let label = NSTextField(wrappingLabelWithString: tr("macUnusableTunnelInfo"))
return label
}()
let button: NSButton = {
let button = NSButton()
button.title = tr("macUnusableTunnelButtonTitleDeleteTunnel")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
button.target = self
button.action = #selector(buttonClicked)
let margin: CGFloat = 20
let internalSpacing: CGFloat = 20
let buttonSpacing: CGFloat = 30
let stackView = NSStackView(views: [messageLabel, infoLabel, button])
stackView.orientation = .vertical
stackView.edgeInsets = NSEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)
stackView.spacing = internalSpacing
stackView.setCustomSpacing(buttonSpacing, after: infoLabel)
let view = NSView()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalToConstant: 360),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 420),
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
])
self.view = view
}
@objc func buttonClicked() {
onButtonClicked?()
}
}
@@ -5,3 +5,8 @@
#include "ringlogger.h"
#include "highlighter.h"
#include "key.h"
#import "TargetConditionals.h"
#if TARGET_OS_OSX
#include <libproc.h>
#endif
@@ -7,6 +7,7 @@ enum ZipArchiveError: WireGuardAppError {
case cantOpenInputZipFile
case cantOpenOutputZipFileForWriting
case badArchive
case noTunnelsInZipArchive
var alertText: AlertText {
switch self {
@@ -16,6 +17,8 @@ enum ZipArchiveError: WireGuardAppError {
return (tr("alertCantOpenOutputZipFileForWritingTitle"), tr("alertCantOpenOutputZipFileForWritingMessage"))
case .badArchive:
return (tr("alertBadArchiveTitle"), tr("alertBadArchiveMessage"))
case .noTunnelsInZipArchive:
return (tr("alertNoTunnelsInImportedZipArchiveTitle"), tr("alertNoTunnelsInImportedZipArchiveMessage"))
}
}
}
@@ -31,7 +34,7 @@ class ZipArchive {
let fileName = input.fileName
let contents = input.contents
zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
contents.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Void in
contents.withUnsafeUInt8Bytes { ptr -> Void in
zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
}
zipCloseFileInZip(zipFile)
@@ -3,16 +3,8 @@
import Foundation
enum ZipImporterError: WireGuardAppError {
case noTunnelsInZipArchive
var alertText: AlertText {
return (tr("alertNoTunnelsInImportedZipArchiveTitle"), tr("alertNoTunnelsInImportedZipArchiveMessage"))
}
}
class ZipImporter {
static func importConfigFiles(from url: URL, completion: @escaping (WireGuardResult<[TunnelConfiguration?]>) -> Void) {
static func importConfigFiles(from url: URL, completion: @escaping (Result<[TunnelConfiguration?], ZipArchiveError>) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
var unarchivedFiles: [(fileBaseName: String, contents: Data)]
do {
@@ -28,9 +20,9 @@ class ZipImporter {
}
if unarchivedFiles.isEmpty {
throw ZipImporterError.noTunnelsInZipArchive
throw ZipArchiveError.noTunnelsInZipArchive
}
} catch let error as WireGuardAppError {
} catch let error as ZipArchiveError {
DispatchQueue.main.async { completion(.failure(error)) }
return
} catch {
@@ -117,6 +117,8 @@ extension Endpoint {
hostname = "\(address)"
case .ipv6(let address):
hostname = "\(address)"
@unknown default:
fatalError()
}
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
@@ -143,18 +143,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func pathUpdate(path: Network.NWPath) {
guard let handle = handle else { return }
wg_log(.debug, message: "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
#if os(iOS)
if let packetTunnelSettingsGenerator = packetTunnelSettingsGenerator {
_ = packetTunnelSettingsGenerator.endpointUapiConfiguration().withGoString { return wgSetConfig(handle, $0) }
}
#endif
var interfaces = path.availableInterfaces
if let ifname = ifname {
interfaces = interfaces.filter { $0.name != ifname }
}
if let ifscope = interfaces.first?.index {
wgBindInterfaceScope(handle, Int32(ifscope))
}
wgBumpSockets(handle)
}
}
+12 -33
View File
@@ -14,58 +14,37 @@ LIPO ?= lipo
DESTDIR ?= $(CONFIGURATION_BUILD_DIR)
BUILDDIR ?= $(CONFIGURATION_TEMP_DIR)/wireguard-go-bridge
UPSTREAM_FILES := $(filter-out %/main.go %/queueconstants.go,$(wildcard ../wireguard-go/*/*.go) $(wildcard ../wireguard-go/*.go)) ../wireguard-go/go.mod ../wireguard-go/go.sum
DOWNSTREAM_FILES := $(wildcard src/*.go) $(wildcard src/*/*.go)
CFLAGS_PREFIX := $(if $(DEPLOYMENT_TARGET_CLANG_FLAG_NAME),-$(DEPLOYMENT_TARGET_CLANG_FLAG_NAME)=$($(DEPLOYMENT_TARGET_CLANG_ENV_NAME)),) -Wno-unused-command-line-argument -isysroot $(SDKROOT) -arch
GOARCH_arm64 := arm64
GOARCH_armv7 := arm
GOARCH_x86_64 := amd64
export GOOS := darwin
export CGO_ENABLED := 1
build: $(DESTDIR)/libwg-go.a
version-header: $(DESTDIR)/wireguard-go-version.h
GOBUILDVERSION_NEEDED := go version go1.12 darwin/amd64
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
export GOROOT := $(BUILDDIR)/goroot
export GOPATH := $(BUILDDIR)/gopath
export PATH := $(GOPATH)/bin:$(PATH)
GOBUILDVERSION_FAKE := $(shell $(GOROOT)/bin/go version 2>/dev/null)
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
$(error This requires $(GOBUILDVERSION_NEEDED))
endif
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_FAKE))
$(shell rm -f $(GOROOT)/.prepared)
endif
$(GOROOT)/.prepared:
[ -n "$(REAL_GOROOT)" ]
mkdir -p "$(GOROOT)"
rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/"
patch -p1 -f -N -r- -d "$(GOROOT)" < goruntime-boottime-over-monotonic.diff
cat goruntime-*.diff | patch -p1 -f -N -r- -d "$(GOROOT)"
touch "$@"
$(shell test "$$(cat "$(BUILDDIR)/.gobuildversion" 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.a")
define libwg-go-a
$(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared
CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
GOARCH="$(GOARCH_$(1))" \
go build -tags ios -ldflags=-w -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive && go version > "$(BUILDDIR)/.gobuildversion"; \
chmod -fR +w "$(GOPATH)/pkg/mod"; \
ret=$$$$?; \
rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \
exit $$$$ret
$(BUILDDIR)/libwg-go-$(1).a: export CGO_ENABLED := 1
$(BUILDDIR)/libwg-go-$(1).a: export CGO_CFLAGS := $(CFLAGS_PREFIX) $(ARCH)
$(BUILDDIR)/libwg-go-$(1).a: export CGO_LDFLAGS := $(CFLAGS_PREFIX) $(ARCH)
$(BUILDDIR)/libwg-go-$(1).a: export GOOS := darwin
$(BUILDDIR)/libwg-go-$(1).a: export GOARCH := $(GOARCH_$(1))
$(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared go.mod
go build -tags ios -ldflags=-w -trimpath -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive
rm -f "$(BUILDDIR)/libwg-go-$(1).h"
endef
$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH))))
$(DESTDIR)/wireguard-go-version.h: go.mod $(GOROOT)/.prepared
go get -d -tags ios; chmod -fR +w "$(GOPATH)/pkg/mod"
wggo="$(GOPATH)/pkg/mod/$$(sed -n 's/.*\(golang\.zx2c4\.com\/wireguard\) \(.*\)$$/\1@\2/p' go.mod)"; \
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$$wggo/device/version.go" > "$@"
$(DESTDIR)/wireguard-go-version.h: $(GOROOT)/.prepared go.mod
go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p' > "$@"
$(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
@mkdir -vp "$(DESTDIR)"
+4 -11
View File
@@ -166,21 +166,14 @@ func wgGetConfig(tunnelHandle int32) *C.char {
return C.CString(settings.String())
}
//export wgBindInterfaceScope
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
//export wgBumpSockets
func wgBumpSockets(tunnelHandle int32) {
device, ok := tunnelHandles[tunnelHandle]
if !ok {
return
}
device.Info.Printf("Binding sockets to interface %d\n", ifscope)
err := device.BindSocketToInterface4(uint32(ifscope))
if err != nil {
device.Error.Printf("Unable to bind v4 socket to interface:", err)
}
err = device.BindSocketToInterface6(uint32(ifscope))
if err != nil {
device.Error.Printf("Unable to bind v6 socket to interface:", err)
}
device.BindUpdate()
device.SendKeepalivesToPeersWithCurrentKeypair()
}
//export wgVersion
+5 -3
View File
@@ -1,8 +1,10 @@
module golang.zx2c4.com/wireguard/ios
go 1.12
go 1.13
require (
golang.org/x/sys v0.0.0-20190312061237-fead79001313
golang.zx2c4.com/wireguard v0.0.0-20190310024754-66fb5caf023e
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad // indirect
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9
golang.zx2c4.com/wireguard v0.0.20200121
)
+21 -10
View File
@@ -1,11 +1,22 @@
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25 h1:jsG6UpNLt9iAsb0S2AGW28DveNzzgmbXR+ENoPjUeIU=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95 h1:fY7Dsw114eJN4boqzVSbpVHO6rTdhq6/GnXeu+PKnzU=
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823 h1:Ypyv6BNJh07T1pUSrehkLemqPKXhus2MkfktJ91kRh4=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.zx2c4.com/wireguard v0.0.0-20190310024754-66fb5caf023e h1:lyp5Vq7gvB3Ei9iCJNhEvzuQx99gteo+GoRZPFOCmRE=
golang.zx2c4.com/wireguard v0.0.0-20190310024754-66fb5caf023e/go.mod h1:Koyxt99ZAZcr8lJuisBNX9vnaqKVNGcITeWOI1Zkmsw=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU=
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
@@ -1,4 +1,4 @@
From 74523c5a12d37fa792e77a252bcc569484c3d41a Mon Sep 17 00:00:00 2001
From 04f5695b83cd221e99e9fa6171b57e45177d5ad3 Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Wed, 27 Feb 2019 05:33:01 +0100
Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin
@@ -18,10 +18,10 @@ Fixes #24595
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
index f34ac88352..416fcb673f 100644
index 376f76dbc5..a0677a83f6 100644
--- a/src/runtime/sys_darwin.go
+++ b/src/runtime/sys_darwin.go
@@ -403,7 +403,7 @@ func closeonexec(fd int32) {
@@ -431,7 +431,7 @@ func setNonblock(fd int32) {
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
@@ -31,10 +31,10 @@ index f34ac88352..416fcb673f 100644
//go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib"
diff --git a/src/runtime/sys_darwin_386.s b/src/runtime/sys_darwin_386.s
index 1bc1a63c28..34a3561350 100644
index e653c54f61..5a43fcbdc1 100644
--- a/src/runtime/sys_darwin_386.s
+++ b/src/runtime/sys_darwin_386.s
@@ -184,7 +184,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
@@ -199,7 +199,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
PUSHL BP
MOVL SP, BP
SUBL $8+(machTimebaseInfo__size+15)/16*16, SP
@@ -44,10 +44,10 @@ index 1bc1a63c28..34a3561350 100644
MOVL AX, 0(CX)
MOVL DX, 4(CX)
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
index f99cb00ab8..8b99316983 100644
index 87c8db8c82..f962f24339 100644
--- a/src/runtime/sys_darwin_amd64.s
+++ b/src/runtime/sys_darwin_amd64.s
@@ -86,7 +86,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
@@ -97,7 +97,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
PUSHQ BP
MOVQ SP, BP
MOVQ DI, BX
@@ -57,10 +57,10 @@ index f99cb00ab8..8b99316983 100644
MOVL timebase<>+machTimebaseInfo_numer(SB), SI
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read
diff --git a/src/runtime/sys_darwin_arm.s b/src/runtime/sys_darwin_arm.s
index 54c7afbf34..a4f06fdb85 100644
index 996f8028a3..5bd34b51be 100644
--- a/src/runtime/sys_darwin_arm.s
+++ b/src/runtime/sys_darwin_arm.s
@@ -118,7 +118,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
@@ -126,7 +126,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
MOVW R0, R8
@@ -70,10 +70,10 @@ index 54c7afbf34..a4f06fdb85 100644
MOVW R1, 4(R8)
MOVW timebase<>+machTimebaseInfo_numer(SB), R6
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
index 29951d8ad7..cdaf0a630e 100644
index ac3ca74f63..5e91540f94 100644
--- a/src/runtime/sys_darwin_arm64.s
+++ b/src/runtime/sys_darwin_arm64.s
@@ -113,7 +113,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
@@ -121,7 +121,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
MOVD R0, R19
@@ -83,5 +83,5 @@ index 29951d8ad7..cdaf0a630e 100644
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
--
2.20.1
2.23.0
+1 -1
View File
@@ -18,7 +18,7 @@ extern int wgTurnOn(gostring_t settings, int32_t tun_fd);
extern void wgTurnOff(int handle);
extern int64_t wgSetConfig(int handle, gostring_t settings);
extern char *wgGetConfig(int handle);
extern void wgBindInterfaceScope(int handle, int32_t ifscope);
extern void wgBumpSockets(int handle);
extern const char *wgVersion();
#endif