Compare commits
45 Commits
0.0.201904
...
0.0.201905
Author | SHA1 | Date | |
---|---|---|---|
d20daa345a | |||
168ba2da8a | |||
714d6a41bd | |||
9b92a8f933 | |||
5e9780ef8f | |||
9faf814e8b | |||
30da10a0e9 | |||
a18614d6b3 | |||
5100e597aa | |||
0340641c4c | |||
813dea6902 | |||
c30d491edc | |||
88c80d6694 | |||
393718dfaf | |||
f852b6f919 | |||
8926434682 | |||
493c7b102e | |||
717bc8a26f | |||
e582155a10 | |||
70d19691a7 | |||
40b1f0bac8 | |||
52ac9b82c2 | |||
fc1fdbbcdb | |||
300268daa0 | |||
9bf304a9ac | |||
586a592b68 | |||
c0526d2efb | |||
fdbd4f875e | |||
4a037cc706 | |||
5190fc2249 | |||
3f25d54dcc | |||
f9880907a2 | |||
404fa741e8 | |||
6e1f03e41c | |||
6d8965e97d | |||
5e5481b69b | |||
69b33c0fad | |||
167e4f0bf2 | |||
6e3b28852a | |||
5914e868ab | |||
83ea9d6fa7 | |||
b954e9a4fd | |||
76894fba68 | |||
89a564ce62 | |||
178fe86d36 |
@ -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;
|
||||
|
@ -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.";
|
||||
|
@ -1,2 +1,2 @@
|
||||
VERSION_NAME = 0.0.20190409
|
||||
VERSION_ID = 7
|
||||
VERSION_NAME = 0.0.20190531
|
||||
VERSION_ID = 9
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
25
WireGuard/WireGuard/UI/iOS/QuickActionItem.swift
Normal 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) }
|
||||
}
|
||||
}
|
72
WireGuard/WireGuard/UI/iOS/RecentTunnelsTracker.swift
Normal 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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 995 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.9 KiB |
28
WireGuard/WireGuard/UI/macOS/LaunchedAtLoginDetector.swift
Normal 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
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
35
WireGuard/WireGuard/UI/macOS/MacAppStoreUpdateDetector.swift
Normal 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
|
||||
}
|
||||
}
|
130
WireGuard/WireGuard/UI/macOS/MainMenu.swift
Normal 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)
|
||||
}
|
@ -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: ""
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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=
|
||||
|
@ -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
|
||||
|