diff --git a/Sources/Shared/FileManager+Extension.swift b/Sources/Shared/FileManager+Extension.swift index 1b246fc..a865b81 100644 --- a/Sources/Shared/FileManager+Extension.swift +++ b/Sources/Shared/FileManager+Extension.swift @@ -35,6 +35,10 @@ extension FileManager { return sharedFolderURL?.appendingPathComponent("last-error.txt") } + static var loginHelperTimestampURL: URL? { + return sharedFolderURL?.appendingPathComponent("login-helper-timestamp.bin") + } + static func deleteFile(at url: URL) -> Bool { do { try FileManager.default.removeItem(at: url) diff --git a/Sources/WireGuardApp/UI/macOS/AppDelegate.swift b/Sources/WireGuardApp/UI/macOS/AppDelegate.swift index c56091e..f1ed4cd 100644 --- a/Sources/WireGuardApp/UI/macOS/AppDelegate.swift +++ b/Sources/WireGuardApp/UI/macOS/AppDelegate.swift @@ -65,19 +65,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - 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 confirmAndQuit() { let alert = NSAlert() alert.messageText = tr("macConfirmAndQuitAlertMessage") diff --git a/Sources/WireGuardApp/UI/macOS/LaunchedAtLoginDetector.swift b/Sources/WireGuardApp/UI/macOS/LaunchedAtLoginDetector.swift index b3c6995..57003e5 100644 --- a/Sources/WireGuardApp/UI/macOS/LaunchedAtLoginDetector.swift +++ b/Sources/WireGuardApp/UI/macOS/LaunchedAtLoginDetector.swift @@ -4,25 +4,16 @@ 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 + let now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW) + guard openAppleEvent.eventClass == kCoreEventClass && openAppleEvent.eventID == kAEOpenApplication else { return false } + guard let url = FileManager.loginHelperTimestampURL else { return false } + guard let data = try? Data(contentsOf: url) else { return false } + _ = FileManager.deleteFile(at: url) + guard data.count == 8 else { return false } + let then = data.withUnsafeBytes { ptr in + ptr.load(as: UInt64.self) + } + return now - then <= 5000000000 } } - -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 -} diff --git a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/Info.plist b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/Info.plist index 7334878..516eafd 100644 --- a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/Info.plist +++ b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/Info.plist @@ -32,5 +32,7 @@ com.wireguard.macos.app_id $(APP_ID_MACOS) + com.wireguard.macos.app_group_id + $(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS) diff --git a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements index 852fa1a..557cb22 100644 --- a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements +++ b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements @@ -4,5 +4,9 @@ com.apple.security.app-sandbox + com.apple.security.application-groups + + $(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS) + diff --git a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/main.m b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/main.m index 8d074d9..1c37daf 100644 --- a/Sources/WireGuardApp/UI/macOS/LoginItemHelper/main.m +++ b/Sources/WireGuardApp/UI/macOS/LoginItemHelper/main.m @@ -5,13 +5,32 @@ int main(int argc, char *argv[]) { - NSString *appIdInfoDictionaryKey = @"com.wireguard.macos.app_id"; - NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:appIdInfoDictionaryKey]; - - NSString *launchCode = @"LaunchedByWireGuardLoginItemHelper"; - NSAppleEventDescriptor *paramDescriptor = [NSAppleEventDescriptor descriptorWithString:launchCode]; - - [NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation - additionalEventParamDescriptor:paramDescriptor launchIdentifier:NULL]; + NSString *appId = [NSBundle.mainBundle objectForInfoDictionaryKey:@"com.wireguard.macos.app_id"]; + NSString *appGroupId = [NSBundle.mainBundle objectForInfoDictionaryKey:@"com.wireguard.macos.app_group_id"]; + if (!appId || !appGroupId) + return 1; + NSURL *containerUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupId]; + if (!containerUrl) + return 2; + uint64_t now = clock_gettime_nsec_np(CLOCK_UPTIME_RAW); + if (![[NSData dataWithBytes:&now length:sizeof(now)] writeToURL:[containerUrl URLByAppendingPathComponent:@"login-helper-timestamp.bin"] atomically:YES]) + return 3; + if (@available(macOS 10.15, *)) { + NSCondition *condition = [[NSCondition alloc] init]; + NSURL *appURL = [NSWorkspace.sharedWorkspace URLForApplicationWithBundleIdentifier:appId]; + if (!appURL) + return 4; + NSWorkspaceOpenConfiguration *openConfiguration = [NSWorkspaceOpenConfiguration configuration]; + openConfiguration.activates = NO; + openConfiguration.addsToRecentItems = NO; + openConfiguration.hides = YES; + [NSWorkspace.sharedWorkspace openApplicationAtURL:appURL configuration:openConfiguration completionHandler:^(NSRunningApplication * _Nullable app, NSError * _Nullable error) { + [condition signal]; + }]; + [condition wait]; + } else { + [NSWorkspace.sharedWorkspace launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchWithoutActivation + additionalEventParamDescriptor:NULL launchIdentifier:NULL]; + } return 0; }