Compare commits

...

45 Commits

Author SHA1 Message Date
d20daa345a Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:30:06 +02:00
168ba2da8a NetworkExtension: bump sockets on path change
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:29:29 +02:00
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
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
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
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
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
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
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
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
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
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
88c80d6694 iOS: Refactor showing of the tunnel detail
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
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
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
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
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
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
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
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
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
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
fc1fdbbcdb macOS: Login item: Fix Info.plist path
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
300268daa0 macOS: Show Manage Tunnels window on startup
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
9bf304a9ac macOS: Minor refactor of StatusMenuWindowDelegate
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
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
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
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
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
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
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
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
404fa741e8 macOS: swiftlint: Suppress incorrect warnings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
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
6d8965e97d macOS: Remove custom key event handling
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
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
69b33c0fad macOS: Edit view: Save on Cmd+S
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 15:45:26 +05:30
167e4f0bf2 macOS: Edit view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:23:52 +05:30
6e3b28852a macOS: Log view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:21:42 +05:30
5914e868ab iOS: Log view: Improve the look
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-28 14:29:22 +05:30
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
b954e9a4fd Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-23 06:49:16 +02:00
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
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
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
44 changed files with 778 additions and 216 deletions

View File

@ -52,6 +52,10 @@
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 */; };
@ -66,6 +70,7 @@
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 */; };
@ -298,6 +303,10 @@
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>"; };
@ -316,6 +325,7 @@
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>"; };
@ -562,6 +572,8 @@
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
6F29A9422278518D00DC6A6B /* RecentTunnelsTracker.swift */,
6F29A94622787B1600DC6A6B /* QuickActionItem.swift */,
6FF4AC23211EC472002C96EB /* Info.plist */,
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
);
@ -629,9 +641,12 @@
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 */,
@ -1286,8 +1301,10 @@
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 */,
@ -1304,6 +1321,7 @@
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 */,
@ -1343,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 */,
@ -1383,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 */,
@ -1513,7 +1533,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
@ -1526,7 +1546,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
INFOPLIST_FILE = WireGuard/UI/macOS/LoginItemHelper/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
@ -1687,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;
@ -1752,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;

View File

@ -291,19 +291,42 @@
"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…";
"macMenuViewLog" = "View log";
"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";
"macMenuQuitManagingTunnels" = "Quit Tunnel Manager";
"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
@ -405,3 +428,9 @@
"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.";

View File

@ -1,2 +1,2 @@
VERSION_NAME = 0.0.20190409
VERSION_ID = 7
VERSION_NAME = 0.0.20190531
VERSION_ID = 9

View File

@ -33,7 +33,7 @@ class TunnelsManager {
startObservingTunnelConfigurations()
}
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,7 +46,11 @@ class TunnelsManager {
var tunnelManagers = managers ?? []
var refs: Set<Data> = []
var tunnelNames: Set<String> = []
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
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 }
@ -66,6 +70,9 @@ class TunnelsManager {
}
}
Keychain.deleteReferences(except: refs)
#if os(iOS)
RecentTunnelsTracker.cleanupTunnels(except: tunnelNames)
#endif
completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
}
#endif
@ -104,7 +111,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))
@ -167,9 +174,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)
}
}
}
@ -182,7 +195,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)
@ -214,6 +228,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)!)
@ -260,6 +277,10 @@ class TunnelsManager {
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
}
completionHandler(nil)
#if os(iOS)
RecentTunnelsTracker.handleTunnelRemoved(tunnelName: tunnel.name)
#endif
}
}
@ -292,6 +313,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)
}
@ -337,6 +362,10 @@ class TunnelsManager {
#else
tunnel.startActivation(activationDelegate: activationDelegate)
#endif
#if os(iOS)
RecentTunnelsTracker.handleTunnelActivated(tunnelName: tunnel.name)
#endif
}
func startDeactivation(of tunnel: TunnelContainer) {

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()

View File

@ -9,10 +9,17 @@ 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
self.window = window
@ -37,6 +44,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
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 +67,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 +80,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

View File

@ -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) }
}
}

View File

@ -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
}
}

View File

@ -20,6 +20,15 @@ class LogViewController: UIViewController {
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?
@ -68,9 +77,20 @@ class LogViewController: UIViewController {
}
guard !fetchedLogEntries.isEmpty else { return }
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
let text = fetchedLogEntries.reduce("") { $0 + $1.text() + "\n" }
let font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let richText = NSAttributedString(string: text, attributes: [.font: font])
let richText = NSMutableAttributedString()
let bodyFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let captionFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.caption1)
let lightGrayColor = UIColor(white: 0.88, alpha: 1.0)
for logEntry in fetchedLogEntries {
let bgColor = self.isNextLineHighlighted ? lightGrayColor : UIColor.white
let timestampText = NSAttributedString(string: logEntry.timestamp + "\n", attributes: [.font: captionFont, .backgroundColor: bgColor, .paragraphStyle: self.paragraphStyle])
let messageText = NSAttributedString(string: logEntry.message + "\n", attributes: [.font: bodyFont, .backgroundColor: bgColor, .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)

View File

@ -42,22 +42,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 +88,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)
}
}
}

View File

@ -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)
}

View File

@ -251,6 +251,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 +282,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 +327,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) {
@ -386,3 +400,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)
}
}
}
}

View File

@ -13,36 +13,71 @@ 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!
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
}
}
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
}
@objc func quit() {
if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
NSApp.activate(ignoringOtherApps: true)
@ -68,12 +103,106 @@ class AppDelegate: NSObject, NSApplicationDelegate {
NSApp.terminate(nil)
}
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
if UserDefaults.standard.bool(forKey: "shouldSuppressAppStoreUpdateDetection") {
wg_log(.debug, staticMessage: "App Store update detection is suppressed")
return .terminateNow
}
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")
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
.applicationVersion: appVersionString,
.version: ""
])
}
}
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))
@ -81,7 +210,10 @@ extension AppDelegate: StatusMenuWindowDelegate {
manageTunnelsWindowObject = window
tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
}
return manageTunnelsWindowObject!
setDockIconAndMainMenuVisibility(isVisible: true) { [weak manageTunnelsWindowObject] in
manageTunnelsWindowObject?.makeKeyAndOrderFront(self)
completion?(manageTunnelsWindowObject)
}
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(VERSION_NAME)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(VERSION_ID)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
@ -30,5 +30,7 @@
<string>NSApplication</string>
<key>LSBackgroundOnly</key>
<true/>
<key>com.wireguard.macos.app_id</key>
<string>$(APP_ID_MACOS)</string>
</dict>
</plist>

View File

@ -5,12 +5,13 @@
int main(int argc, char *argv[])
{
NSURL *bundleURL = [NSBundle.mainBundle bundleURL];
NSString *appIdInfoDictionaryKey = @"com.wireguard.macos.app_id";
NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:appIdInfoDictionaryKey];
// From <path>/WireGuard.app/Contents/Library/LoginItems/WireGuardLoginItemHelper.app, derive <path>/WireGuard.app
for (int i = 0; i < 4; ++i)
bundleURL = [bundleURL URLByDeletingLastPathComponent];
NSString *launchCode = @"LaunchedByWireGuardLoginItemHelper";
NSAppleEventDescriptor *paramDescriptor = [NSAppleEventDescriptor descriptorWithString:launchCode];
[NSWorkspace.sharedWorkspace launchApplication:[bundleURL path]];
[NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation
additionalEventParamDescriptor:paramDescriptor launchIdentifier:NULL];
return 0;
}

View File

@ -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
}
}

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("macMenuQuitManagingTunnels"),
action: #selector(NSWindow.performClose(_:)), 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)
}

View File

@ -4,7 +4,7 @@
import Cocoa
protocol StatusMenuWindowDelegate: class {
func manageTunnelsWindow() -> NSWindow
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?)
}
class StatusMenu: NSMenu {
@ -127,8 +127,8 @@ 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
@ -151,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: ""
])
}
}

View File

@ -220,7 +220,8 @@ class LogViewController: NSViewController {
return
}
DispatchQueue.main.async { [weak self] in
self?.dismiss(self)
guard let self = self else { return }
self.presentingViewController?.dismiss(self)
}
}
@ -228,7 +229,7 @@ class LogViewController: NSViewController {
}
@objc func closeClicked() {
dismiss(self)
presentingViewController?.dismiss(self)
}
@objc func copy(_ sender: Any?) {
@ -261,3 +262,9 @@ extension LogViewController: NSTableViewDelegate {
}
}
}
extension LogViewController {
override func cancelOperation(_ sender: Any?) {
closeClicked()
}
}

View File

@ -117,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)
}
}
}

View File

@ -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
}

View File

@ -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
}()
@ -235,32 +237,34 @@ 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
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
self?.setUserInteractionEnabled(true)
if let error = result.error {
guard let self = self else { return }
self.setUserInteractionEnabled(true)
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
return
case .success(let tunnel):
self.delegate?.tunnelSaved(tunnel: tunnel)
self.presentingViewController?.dismiss(self)
}
let tunnel: TunnelContainer = result.value!
self?.dismiss(self)
self?.delegate?.tunnelSaved(tunnel: tunnel)
}
}
}
@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()
}
}

View File

@ -309,6 +309,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)

View File

@ -5,3 +5,8 @@
#include "ringlogger.h"
#include "highlighter.h"
#include "key.h"
#import "TargetConditionals.h"
#if TARGET_OS_OSX
#include <libproc.h>
#endif

View File

@ -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"))
}
}
}

View File

@ -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 {

View File

@ -11,6 +11,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private var handle: Int32?
private var networkMonitor: NWPathMonitor?
private var ifname: String?
private var lastSeenInterfaces: [String] = []
private var packetTunnelSettingsGenerator: PacketTunnelSettingsGenerator?
deinit {
@ -142,18 +143,19 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func pathUpdate(path: Network.NWPath) {
guard let handle = handle else { return }
guard let ifname = ifname else { return }
wg_log(.debug, message: "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
guard path.status == .satisfied else { return }
#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))
let interfaces = path.availableInterfaces.filter { $0.name != ifname }.compactMap { $0.name }
if !interfaces.elementsEqual(lastSeenInterfaces) {
lastSeenInterfaces = interfaces
wgBumpSockets(handle)
}
}
}

View File

@ -165,22 +165,14 @@ func wgGetConfig(tunnelHandle int32) *C.char {
writer.Flush()
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

View File

@ -3,6 +3,6 @@ module golang.zx2c4.com/wireguard/ios
go 1.12
require (
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c
)

View File

@ -2,12 +2,22 @@ github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5 h1:f005F/Jl5JLP036x7QIvUVhNTqxvSYwFIiyOh2q12iU=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265 h1:ujM5BaP4MD/2MJZ1n7pmw6IIsyyS7SyPl0fDefnx/2o=
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265/go.mod h1:u0Cl3X+pyWdXaax3S583DQrnGDuTASO0QdlKFrs8r/8=
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c h1:nUKiRKxUjK8Zghs+JBZZYXo0g51BlXVqkr6mDfm22ow=
golang.zx2c4.com/wireguard v0.0.20190518-0.20190530131616-d9f995209c3c/go.mod h1:8X7vp4RrsvM83bde6vQ94DsL4ZpjUViVUym8aa8zGhs=

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