// SPDX-License-Identifier: MIT // Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. import Cocoa import ServiceManagement @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { var tunnelsManager: TunnelsManager? var tunnelsTracker: TunnelsTracker? var statusItemController: StatusItemController? var manageTunnelsRootVC: ManageTunnelsRootViewController? var manageTunnelsWindowObject: NSWindow? func applicationDidFinishLaunching(_ aNotification: Notification) { Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path) registerLoginItem(shouldLaunchAtLogin: true) NSApp.mainMenu = MainMenu() TunnelsManager.create { [weak self] result in guard let self = self else { return } switch result { case .failure(let error): ErrorPresenter.showErrorAlert(error: error, from: nil) 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 } } } @objc func quit() { if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil { NSApp.activate(ignoringOtherApps: true) manageWindow.orderFront(self) return } registerLoginItem(shouldLaunchAtLogin: false) guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else { NSApp.terminate(nil) return } let alert = NSAlert() alert.messageText = tr("macAppExitingWithActiveTunnelMessage") alert.informativeText = tr("macAppExitingWithActiveTunnelInfo") NSApp.activate(ignoringOtherApps: true) if let manageWindow = manageTunnelsWindowObject { manageWindow.orderFront(self) alert.beginSheetModal(for: manageWindow) { _ in NSApp.terminate(nil) } } else { alert.runModal() 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 { application.setActivationPolicy(.accessory) return false } } 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 { if manageTunnelsWindowObject == nil { manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager!) let window = NSWindow(contentViewController: manageTunnelsRootVC!) window.title = tr("macWindowTitleManageTunnels") window.setContentSize(NSSize(width: 800, height: 480)) window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size manageTunnelsWindowObject = window tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC } return manageTunnelsWindowObject! } } @discardableResult func registerLoginItem(shouldLaunchAtLogin: Bool) -> Bool { let appId = Bundle.main.bundleIdentifier! let helperBundleId = "\(appId).login-item-helper" return SMLoginItemSetEnabled(helperBundleId as CFString, shouldLaunchAtLogin) }