diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 3177787..6631380 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ 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 */; }; 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 */; }; @@ -298,6 +299,7 @@ 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextCell.swift; sourceTree = ""; }; 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTunnelsConfirmationAlert.swift; sourceTree = ""; }; 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = ""; }; + 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAppStoreUpdateDetector.swift; sourceTree = ""; }; 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = ""; }; 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = ""; }; 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = ""; }; @@ -632,6 +634,7 @@ 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */, 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */, 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */, + 6F2449E7226587B80047B9E9 /* MacAppStoreUpdateDetector.swift */, 6FB1BD6121D2607E00A991BF /* Assets.xcassets */, 6FB1BD6621D2607E00A991BF /* Info.plist */, 6FB1BD6721D2607E00A991BF /* WireGuard.entitlements */, @@ -1304,6 +1307,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 */, diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings index 8600a63..9cfaef1 100644 --- a/WireGuard/WireGuard/Base.lproj/Localizable.strings +++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings @@ -405,3 +405,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."; diff --git a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift index e80d971..3e98c20 100644 --- a/WireGuard/WireGuard/UI/macOS/AppDelegate.swift +++ b/WireGuard/WireGuard/UI/macOS/AppDelegate.swift @@ -68,6 +68,36 @@ 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 + } } extension AppDelegate: StatusMenuWindowDelegate { diff --git a/WireGuard/WireGuard/UI/macOS/MacAppStoreUpdateDetector.swift b/WireGuard/WireGuard/UI/macOS/MacAppStoreUpdateDetector.swift new file mode 100644 index 0000000..7fbb011 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/MacAppStoreUpdateDetector.swift @@ -0,0 +1,39 @@ +// 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 { + if let eventClassDescriptor = event.attributeDescriptor(forKeyword: keyEventClassAttr), + let eventIdDescriptor = event.attributeDescriptor(forKeyword: keyEventIDAttr) { + return eventClassDescriptor.typeCodeValue == kCoreEventClass && eventIdDescriptor.typeCodeValue == kAEQuitApplication + } + return false +} + +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 + } +} diff --git a/WireGuard/WireGuard/WireGuard-Bridging-Header.h b/WireGuard/WireGuard/WireGuard-Bridging-Header.h index 210b3df..81766ab 100644 --- a/WireGuard/WireGuard/WireGuard-Bridging-Header.h +++ b/WireGuard/WireGuard/WireGuard-Bridging-Header.h @@ -5,3 +5,8 @@ #include "ringlogger.h" #include "highlighter.h" #include "key.h" + +#import "TargetConditionals.h" +#if TARGET_OS_OSX +#include +#endif