| | |
| | | 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] = [] |
| | |
| | | "hasVisibleWindows": flag, |
| | | "activationPolicy": sender.activationPolicy().rawValue |
| | | ]) |
| | | return true |
| | | openQuickSwitchFromDockReopen(hasVisibleWindows: flag) |
| | | return false |
| | | } |
| | | |
| | | private func configureStatusItem() { |
| | |
| | | } |
| | | |
| | | 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) |
| | |
| | | windowCloseService: quickSwitchWindowCloseService(), |
| | | disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh, |
| | | waterfallViewMode: quickSwitchWaterfallViewMode(), |
| | | theme: quickSwitchThemePreference(), |
| | | closeConfirmationRequired: quickSwitchConfirmBeforeClose, |
| | | onCloseConfirmationDisabled: { [weak self] in |
| | | self?.setQuickSwitchConfirmBeforeClose(false) |
| | |
| | | controller.onSnapshotUpdated = { [weak self] in |
| | | self?.writeQuickSwitchReportIfNeeded() |
| | | } |
| | | controller.onOpenSettings = { [weak self] in |
| | | self?.showSettingsFromQuickSwitch() |
| | | controller.onToggleWaterfallViewMode = { [weak self] in |
| | | self?.toggleQuickSwitchWaterfallViewModeFromQuickSwitch() |
| | | } |
| | | controller.onToggleTheme = { [weak self] in |
| | | self?.toggleQuickSwitchThemeFromQuickSwitch() |
| | | } |
| | | quickSwitchSessionController = controller |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | 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 |
| | |
| | | windowCloseService: quickSwitchWindowCloseService(), |
| | | disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh, |
| | | waterfallViewMode: quickSwitchWaterfallViewMode(), |
| | | theme: quickSwitchThemePreference(), |
| | | closeConfirmationRequired: quickSwitchConfirmBeforeClose, |
| | | onCloseConfirmationDisabled: { [weak self] in |
| | | self?.setQuickSwitchConfirmBeforeClose(false) |
| | |
| | | controller.onSnapshotUpdated = { [weak self] in |
| | | self?.writeQuickSwitchReportIfNeeded() |
| | | } |
| | | controller.onOpenSettings = { [weak self] in |
| | | self?.showSettingsFromQuickSwitch() |
| | | controller.onToggleWaterfallViewMode = { [weak self] in |
| | | self?.toggleQuickSwitchWaterfallViewModeFromQuickSwitch() |
| | | } |
| | | controller.onToggleTheme = { [weak self] in |
| | | self?.toggleQuickSwitchThemeFromQuickSwitch() |
| | | } |
| | | quickSwitchSessionController = controller |
| | | } |
| | |
| | | let versionBuild = appVersionBuild() |
| | | report["preferenceWaterfallViewMode"] = preferenceStore.read().quickSwitchWaterfallViewMode.rawValue |
| | | 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 FixtureQuickSwitchSnapshotLoader(appCount: 1, mode: .spaceFilter) |
| | | } |
| | | |
| | | if round1QuickSwitchOptions.fixtureSplitView { |
| | | return FixtureQuickSwitchSnapshotLoader(appCount: 1, mode: .splitView) |
| | | } |
| | | |
| | | if round1QuickSwitchOptions.fixtureMultiPageIdentity { |
| | | return FixtureQuickSwitchSnapshotLoader(appCount: 1, mode: .multiPageIdentity) |
| | | } |
| | | |
| | | if let fixtureAppCount = round1QuickSwitchOptions.fixtureAppCount { |
| | | return FixtureQuickSwitchSnapshotLoader( |
| | | appCount: fixtureAppCount, |
| | |
| | | return DebugWindowActivationService() |
| | | } |
| | | |
| | | return CGWindowAXWindowService(spaceIDsByWindowIDProvider: { _ in [:] }) |
| | | let context = quickSwitchWindowSpaceContext() |
| | | return CGWindowAXWindowService( |
| | | spaceIDsByWindowIDProvider: context.spaceIDsByWindowIDProvider, |
| | | fullscreenSpaceIDs: context.fullscreenSpaceIDs |
| | | ) |
| | | } |
| | | |
| | | private func quickSwitchWaterfallViewMode() -> QuickSwitchWaterfallViewMode { |
| | | round1QuickSwitchOptions.waterfallViewMode |
| | | ?? preferenceStore.read().quickSwitchWaterfallViewMode |
| | | } |
| | | |
| | | private func quickSwitchThemePreference() -> ThemePreference { |
| | | let theme = preferenceStore.read().theme |
| | | return theme == .dark ? .dark : .light |
| | | } |
| | | |
| | | private func quickSwitchSpaceActivationService() -> any SpaceActivationServiceProtocol { |
| | |
| | | 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 { |
| | |
| | | ]) |
| | | } |
| | | |
| | | private func toggleQuickSwitchWaterfallViewModeFromQuickSwitch() { |
| | | let currentMode = quickSwitchWaterfallViewMode() |
| | | let nextMode: QuickSwitchWaterfallViewMode = currentMode == .horizontalMasonry |
| | | ? .verticalColumns |
| | | : .horizontalMasonry |
| | | DevelopmentDiagnostics.log("app.quickSwitch.waterfallViewModeToggle", [ |
| | | "currentMode": currentMode.rawValue, |
| | | "nextMode": nextMode.rawValue, |
| | | "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false |
| | | ]) |
| | | setQuickSwitchWaterfallViewMode(nextMode) |
| | | } |
| | | |
| | | private func setQuickSwitchThemePreference(_ theme: ThemePreference) { |
| | | let normalizedTheme: ThemePreference = theme == .dark ? .dark : .light |
| | | let preferences = preferenceStore.read() |
| | | let updatedPreferences = AlignerPreferences( |
| | | showMinimizedWindows: preferences.showMinimizedWindows, |
| | | showFullscreenWindows: preferences.showFullscreenWindows, |
| | | quickSwitchWaterfallViewMode: preferences.quickSwitchWaterfallViewMode, |
| | | theme: normalizedTheme, |
| | | language: preferences.language, |
| | | spaceDisplayStrategy: preferences.spaceDisplayStrategy |
| | | ) |
| | | preferenceStore.write(updatedPreferences) |
| | | quickSwitchSessionController?.setTheme(normalizedTheme) |
| | | writeQuickSwitchReportIfNeeded() |
| | | DevelopmentDiagnostics.log("app.quickSwitch.themePreferenceChanged", [ |
| | | "theme": normalizedTheme.rawValue, |
| | | "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false |
| | | ]) |
| | | } |
| | | |
| | | private func toggleQuickSwitchThemeFromQuickSwitch() { |
| | | let currentTheme = quickSwitchThemePreference() |
| | | let nextTheme: ThemePreference = currentTheme == .dark ? .light : .dark |
| | | DevelopmentDiagnostics.log("app.quickSwitch.themeToggle", [ |
| | | "currentTheme": currentTheme.rawValue, |
| | | "nextTheme": nextTheme.rawValue, |
| | | "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false |
| | | ]) |
| | | setQuickSwitchThemePreference(nextTheme) |
| | | } |
| | | |
| | | private func appVersionBuild() -> (version: String, build: String) { |
| | | let info = Bundle.main.infoDictionary ?? [:] |
| | | let version = info["CFBundleShortVersionString"] as? String ?? "Unknown" |