| | |
| | | private let round1SnapshotDumpOptions = Round1SnapshotDumpOptions.parse(arguments: ProcessInfo.processInfo.arguments) |
| | | private let round1ScreenshotProbeOptions = Round1ScreenshotProbeOptions.parse(arguments: ProcessInfo.processInfo.arguments) |
| | | private let round1QuickSwitchOptions = Round1QuickSwitchLaunchOptions.parse(arguments: ProcessInfo.processInfo.arguments) |
| | | private let round015EntitlementOptions = Round015EntitlementLaunchOptions.parse(arguments: ProcessInfo.processInfo.arguments) |
| | | private var permissionWindowController: PermissionsWindowController? |
| | | private var debugOverlayController: Round0DebugOverlayController? |
| | | private var round1PerformancePoCController: Round1PerformancePoCController? |
| | |
| | | private var quickSwitchTriggerService: CGEventTapTriggerService? |
| | | private var settingsWindowController: SettingsWindowController? |
| | | private var isRunningQuickSwitchLifecycle = false |
| | | private var lastQuickSwitchOpenRequestSource: String? |
| | | private var statusItem: NSStatusItem? |
| | | private var verticalWaterfallViewModeMenuItems: [NSMenuItem] = [] |
| | | private var horizontalWaterfallViewModeMenuItems: [NSMenuItem] = [] |
| | | private let performanceDiagnosticsExporter = PerformanceDiagnosticsExporter() |
| | | |
| | | func applicationDidFinishLaunching(_ notification: Notification) { |
| | | let bundlePathSummary = DevelopmentDiagnostics.pathSummary(Bundle.main.bundlePath) |
| | |
| | | "hasVisibleWindows": flag, |
| | | "activationPolicy": sender.activationPolicy().rawValue |
| | | ]) |
| | | return true |
| | | openQuickSwitchFromDockReopen(hasVisibleWindows: flag) |
| | | return false |
| | | } |
| | | |
| | | private func configureStatusItem() { |
| | |
| | | menu.addItem(menuItem(title: "Settings...", action: #selector(showSettingsFromMenu), keyEquivalent: "")) |
| | | menu.addItem(.separator()) |
| | | menu.addItem(menuItem(title: "Check Permissions...", action: #selector(showPermissions), keyEquivalent: "")) |
| | | menu.addItem(menuItem(title: "Export Performance Diagnostics...", action: #selector(exportPerformanceDiagnostics), keyEquivalent: "")) |
| | | menu.addItem(.separator()) |
| | | menu.addItem(menuItem(title: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: "")) |
| | | menu.addItem(menuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: "")) |
| | |
| | | appMenu.addItem(menuItem(title: "Settings...", action: #selector(showSettingsFromMenu), keyEquivalent: ",")) |
| | | appMenu.addItem(.separator()) |
| | | appMenu.addItem(menuItem(title: "Check Permissions...", action: #selector(showPermissions), keyEquivalent: "")) |
| | | appMenu.addItem(menuItem(title: "Export Performance Diagnostics...", action: #selector(exportPerformanceDiagnostics), keyEquivalent: "")) |
| | | appMenu.addItem(menuItem(title: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: "")) |
| | | appMenu.addItem(menuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: "")) |
| | | appMenu.addItem(quickSwitchViewModeMenuItem()) |
| | |
| | | } |
| | | |
| | | private func showQuickSwitchIfRequested() { |
| | | guard round1QuickSwitchOptions.openQuickSwitch || round1QuickSwitchOptions.lifecycleCycles != nil else { return } |
| | | guard round1QuickSwitchOptions.openQuickSwitch |
| | | || round1QuickSwitchOptions.lifecycleCycles != nil |
| | | || round1QuickSwitchOptions.simulateDockReopenAfter != nil else { return } |
| | | DevelopmentDiagnostics.log("app.quickSwitch.launchOptionRequested", [ |
| | | "openQuickSwitch": round1QuickSwitchOptions.openQuickSwitch, |
| | | "lifecycleCycles": round1QuickSwitchOptions.lifecycleCycles |
| | | "lifecycleCycles": round1QuickSwitchOptions.lifecycleCycles, |
| | | "simulateDockReopenAfter": round1QuickSwitchOptions.simulateDockReopenAfter |
| | | ]) |
| | | |
| | | if let lifecycleCycles = round1QuickSwitchOptions.lifecycleCycles { |
| | |
| | | return |
| | | } |
| | | |
| | | openQuickSwitchFromTrigger() |
| | | writeQuickSwitchReportIfNeeded() |
| | | if let simulateDockReopenAfter = round1QuickSwitchOptions.simulateDockReopenAfter { |
| | | DispatchQueue.main.asyncAfter(deadline: .now() + simulateDockReopenAfter) { [weak self] in |
| | | guard let self else { return } |
| | | let hasVisibleWindows = NSApp.windows.contains { $0.isVisible } |
| | | self.openQuickSwitchFromDockReopen(hasVisibleWindows: hasVisibleWindows) |
| | | self.writeQuickSwitchReportIfNeeded() |
| | | self.scheduleQuickSwitchAutoHideAndQuitIfNeeded() |
| | | } |
| | | return |
| | | } |
| | | |
| | | openQuickSwitchFromTrigger(source: "launchOption") |
| | | writeQuickSwitchReportIfNeeded() |
| | | scheduleQuickSwitchAutoHideAndQuitIfNeeded() |
| | | } |
| | | |
| | | private func scheduleQuickSwitchAutoHideAndQuitIfNeeded() { |
| | | if let autoHideAfter = round1QuickSwitchOptions.autoHideAfter { |
| | | DispatchQueue.main.asyncAfter(deadline: .now() + autoHideAfter) { [weak self] in |
| | | self?.hideQuickSwitchSession(reason: .timeout) |
| | |
| | | } |
| | | } |
| | | |
| | | private func openQuickSwitchFromTrigger() { |
| | | private func openQuickSwitchFromDockReopen(hasVisibleWindows: Bool) { |
| | | DevelopmentDiagnostics.log("app.quickSwitch.openFromDockReopen", [ |
| | | "hasVisibleWindows": hasVisibleWindows, |
| | | "alreadyVisible": quickSwitchSessionController?.isVisible ?? false |
| | | ]) |
| | | openQuickSwitchFromTrigger(source: "dockReopen") |
| | | } |
| | | |
| | | private func openQuickSwitchFromTrigger(source: String = "trigger") { |
| | | lastQuickSwitchOpenRequestSource = source |
| | | let accessibility = accessibilityStatus() |
| | | DevelopmentDiagnostics.log("app.quickSwitch.openFromTrigger", [ |
| | | "source": source, |
| | | "accessibility": permissionStatusString(accessibility), |
| | | "screenRecording": permissionStatusString(screenRecordingStatus()), |
| | | "alreadyVisible": quickSwitchSessionController?.isVisible ?? false |
| | |
| | | report["resolvedWaterfallViewMode"] = quickSwitchWaterfallViewMode().rawValue |
| | | report["preferenceTheme"] = preferenceStore.read().theme.rawValue |
| | | report["resolvedQuickSwitchTheme"] = quickSwitchThemePreference().rawValue |
| | | report["lastQuickSwitchOpenRequestSource"] = lastQuickSwitchOpenRequestSource |
| | | report["authorization"] = round015EntitlementOptions.authorizationReportDictionary() |
| | | report["aboutVersion"] = versionBuild.version |
| | | report["aboutBuild"] = versionBuild.build |
| | | report["statusMenu"] = statusMenuReportDictionary() |
| | |
| | | return DebugWindowActivationService() |
| | | } |
| | | |
| | | return CGWindowAXWindowService(spaceIDsByWindowIDProvider: { _ in [:] }) |
| | | let context = quickSwitchWindowSpaceContext() |
| | | return CGWindowAXWindowService( |
| | | spaceIDsByWindowIDProvider: context.spaceIDsByWindowIDProvider, |
| | | fullscreenSpaceIDs: context.fullscreenSpaceIDs |
| | | ) |
| | | } |
| | | |
| | | private func quickSwitchWaterfallViewMode() -> QuickSwitchWaterfallViewMode { |
| | |
| | | return DebugWindowCloseService() |
| | | } |
| | | |
| | | return CGWindowAXWindowService(spaceIDsByWindowIDProvider: { _ in [:] }) |
| | | let context = quickSwitchWindowSpaceContext() |
| | | return CGWindowAXWindowService( |
| | | spaceIDsByWindowIDProvider: context.spaceIDsByWindowIDProvider, |
| | | fullscreenSpaceIDs: context.fullscreenSpaceIDs |
| | | ) |
| | | } |
| | | |
| | | private func quickSwitchWindowSpaceContext() -> ( |
| | | spaceIDsByWindowIDProvider: ([UInt32]) -> [UInt32: [UInt64]], |
| | | fullscreenSpaceIDs: Set<UInt64> |
| | | ) { |
| | | do { |
| | | let spaceEnumerator = try SkyLightSpaceEnumerator() |
| | | let displays = try spaceEnumerator.displays() |
| | | let fullscreenSpaceIDs = Set( |
| | | displays |
| | | .flatMap(\.spaces) |
| | | .filter { $0.type == .fullscreen } |
| | | .map(\.id) |
| | | ) |
| | | return ( |
| | | spaceIDsByWindowIDProvider: spaceEnumerator.spaceIDsByWindowID(windowIDs:), |
| | | fullscreenSpaceIDs: fullscreenSpaceIDs |
| | | ) |
| | | } catch { |
| | | DevelopmentDiagnostics.log("app.quickSwitch.windowSpaceContextUnavailable", [ |
| | | "error": DevelopmentDiagnostics.errorSummaryString(error) |
| | | ]) |
| | | return ( |
| | | spaceIDsByWindowIDProvider: { _ in [:] }, |
| | | fullscreenSpaceIDs: [] |
| | | ) |
| | | } |
| | | } |
| | | |
| | | private var quickSwitchConfirmBeforeClose: Bool { |
| | |
| | | return [ |
| | | "hasAboutItem": statusItem?.menu?.item(withTitle: "About Aligner...") != nil, |
| | | "hasSettingsItem": statusItem?.menu?.item(withTitle: "Settings...") != nil, |
| | | "hasExportPerformanceDiagnosticsItem": statusItem?.menu?.item(withTitle: "Export Performance Diagnostics...") != nil, |
| | | "hasQuickSwitchViewSubmenu": statusItem?.menu?.item(withTitle: "Quick Switch View")?.submenu != nil, |
| | | "hasVerticalColumnsItem": verticalItem != nil, |
| | | "hasHorizontalWaterfallItem": horizontalItem != nil, |
| | |
| | | "hasAppMenu": appMenu != nil, |
| | | "hasAboutItem": appMenu?.item(withTitle: "About Aligner...") != nil, |
| | | "hasSettingsItem": appMenu?.item(withTitle: "Settings...") != nil, |
| | | "hasExportPerformanceDiagnosticsItem": appMenu?.item(withTitle: "Export Performance Diagnostics...") != nil, |
| | | "settingsKeyEquivalent": appMenu?.item(withTitle: "Settings...")?.keyEquivalent ?? "", |
| | | "hasQuickSwitchViewSubmenuInAppMenu": appMenu?.item(withTitle: "Quick Switch View")?.submenu != nil, |
| | | "hasQuickSwitchViewSubmenuInViewMenu": viewMenu?.item(withTitle: "Quick Switch View")?.submenu != nil |
| | |
| | | openSettingsWindow(source: "menu") |
| | | } |
| | | |
| | | @objc private func exportPerformanceDiagnostics() { |
| | | let versionBuild = appVersionBuild() |
| | | let preferences = preferenceStore.read() |
| | | let quickSwitch = quickSwitchSessionController?.performanceDiagnosticSummary() ?? [ |
| | | "snapshotLoaded": false, |
| | | "quickSwitchVisible": false, |
| | | "applicationCount": 0, |
| | | "windowCount": 0, |
| | | "applications": [] |
| | | ] |
| | | performanceDiagnosticsExporter.export( |
| | | version: versionBuild.version, |
| | | build: versionBuild.build, |
| | | preferences: preferences, |
| | | accessibility: permissionStatusString(accessibilityStatus()), |
| | | screenRecording: permissionStatusString(screenRecordingStatus()), |
| | | quickSwitch: quickSwitch |
| | | ) |
| | | } |
| | | |
| | | private func showSettingsFromQuickSwitch() { |
| | | openSettingsWindow(source: "quickSwitch") |
| | | } |