Ariver
2026-06-13 6983a4ed2137bf1a8377e5d1cf82183e02e617f8
C1.source/Sources/Aligner/AlignerApplicationDelegate.swift
@@ -3,10 +3,11 @@
import AlignerCore
@MainActor
final class AlignerApplicationDelegate: NSObject, NSApplicationDelegate {
final class AlignerApplicationDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
    private static let quickSwitchConfirmBeforeCloseDefaultsKey = "QuickSwitchConfirmBeforeClose"
    private let permissionChecker = SystemPermissionChecker()
    private let preferenceStore = UserDefaultsPreferenceStore()
    private let launchOptions = Round0DebugLaunchOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1PoCOptions = Round1PerformancePoCOptions.parse(arguments: ProcessInfo.processInfo.arguments)
    private let round1TriggerProbeOptions = Round1TriggerProbeOptions.parse(arguments: ProcessInfo.processInfo.arguments)
@@ -19,8 +20,11 @@
    private var round1TriggerProbe: CGEventTapTriggerProbe?
    private var quickSwitchSessionController: QuickSwitchSessionController?
    private var quickSwitchTriggerService: CGEventTapTriggerService?
    private var settingsWindowController: SettingsWindowController?
    private var isRunningQuickSwitchLifecycle = false
    private var statusItem: NSStatusItem?
    private var verticalWaterfallViewModeMenuItems: [NSMenuItem] = []
    private var horizontalWaterfallViewModeMenuItems: [NSMenuItem] = []
    func applicationDidFinishLaunching(_ notification: Notification) {
        let bundlePathSummary = DevelopmentDiagnostics.pathSummary(Bundle.main.bundlePath)
@@ -44,6 +48,7 @@
            return
        }
        configureMainMenu()
        configureStatusItem()
        if !launchOptions.skipPermissions {
            showPermissionsIfNeeded()
@@ -91,21 +96,139 @@
        item.button?.title = "A"
        let menu = NSMenu()
        menu.addItem(NSMenuItem(title: "Check Permissions...", action: #selector(showPermissions), keyEquivalent: ","))
        menu.delegate = self
        menu.addItem(menuItem(title: "About Aligner...", action: #selector(showAbout), keyEquivalent: ""))
        menu.addItem(menuItem(title: "Settings...", action: #selector(showSettingsFromMenu), keyEquivalent: ""))
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(NSMenuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(menuItem(title: "Check Permissions...", action: #selector(showPermissions), keyEquivalent: ""))
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Show Round0 Debug Overlay", action: #selector(showRound0DebugOverlay), keyEquivalent: "d"))
        menu.addItem(NSMenuItem(title: "Hide Round0 Debug Overlay", action: #selector(hideRound0DebugOverlay), keyEquivalent: ""))
        menu.addItem(menuItem(title: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(menuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: ""))
        menu.addItem(quickSwitchViewModeMenuItem())
        menu.addItem(.separator())
        menu.addItem(NSMenuItem(title: "Quit Aligner", action: #selector(quit), keyEquivalent: "q"))
        menu.addItem(menuItem(title: "Show Round0 Debug Overlay", action: #selector(showRound0DebugOverlay), keyEquivalent: "d"))
        menu.addItem(menuItem(title: "Hide Round0 Debug Overlay", action: #selector(hideRound0DebugOverlay), keyEquivalent: ""))
        menu.addItem(.separator())
        menu.addItem(menuItem(title: "Quit Aligner", action: #selector(quit), keyEquivalent: "q"))
        item.menu = menu
        statusItem = item
        updateWaterfallViewModeMenuState()
        DevelopmentDiagnostics.log("app.statusItem.configure.end", [
            "hasButton": item.button != nil
            "hasButton": item.button != nil,
            "hasAboutItem": true,
            "hasSettingsItem": true,
            "hasWaterfallViewModeItems": !verticalWaterfallViewModeMenuItems.isEmpty
                && !horizontalWaterfallViewModeMenuItems.isEmpty
        ])
    }
    private func configureMainMenu() {
        DevelopmentDiagnostics.log("app.mainMenu.configure.start")
        let mainMenu = NSMenu(title: "Main Menu")
        let appMenuItem = NSMenuItem(title: "Aligner", action: nil, keyEquivalent: "")
        let appMenu = NSMenu(title: "Aligner")
        appMenu.delegate = self
        appMenuItem.submenu = appMenu
        appMenu.addItem(menuItem(title: "About Aligner...", action: #selector(showAbout), keyEquivalent: ""))
        appMenu.addItem(.separator())
        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: "Show Quick Switch", action: #selector(showQuickSwitchFromMenu), keyEquivalent: ""))
        appMenu.addItem(menuItem(title: "Hide Quick Switch", action: #selector(hideQuickSwitchFromMenu), keyEquivalent: ""))
        appMenu.addItem(quickSwitchViewModeMenuItem())
        appMenu.addItem(.separator())
        appMenu.addItem(menuItem(title: "Quit Aligner", action: #selector(quit), keyEquivalent: "q"))
        mainMenu.addItem(appMenuItem)
        let editMenuItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "")
        let editMenu = NSMenu(title: "Edit")
        editMenuItem.submenu = editMenu
        editMenu.addItem(responderMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"))
        editMenu.addItem(responderMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"))
        editMenu.addItem(.separator())
        editMenu.addItem(responderMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"))
        editMenu.addItem(responderMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"))
        editMenu.addItem(responderMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
        editMenu.addItem(responderMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
        mainMenu.addItem(editMenuItem)
        let viewMenuItem = NSMenuItem(title: "View", action: nil, keyEquivalent: "")
        let viewMenu = NSMenu(title: "View")
        viewMenu.delegate = self
        viewMenuItem.submenu = viewMenu
        viewMenu.addItem(quickSwitchViewModeMenuItem())
        mainMenu.addItem(viewMenuItem)
        let windowMenuItem = NSMenuItem(title: "Window", action: nil, keyEquivalent: "")
        let windowMenu = NSMenu(title: "Window")
        windowMenuItem.submenu = windowMenu
        windowMenu.addItem(responderMenuItem(title: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m"))
        windowMenu.addItem(responderMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: ""))
        windowMenu.addItem(.separator())
        windowMenu.addItem(responderMenuItem(title: "Bring All to Front", action: #selector(NSApplication.arrangeInFront(_:)), keyEquivalent: ""))
        mainMenu.addItem(windowMenuItem)
        NSApp.windowsMenu = windowMenu
        let helpMenuItem = NSMenuItem(title: "Help", action: nil, keyEquivalent: "")
        helpMenuItem.submenu = NSMenu(title: "Help")
        mainMenu.addItem(helpMenuItem)
        NSApp.mainMenu = mainMenu
        updateWaterfallViewModeMenuState()
        DevelopmentDiagnostics.log("app.mainMenu.configure.end", [
            "hasSettingsItem": appMenu.item(withTitle: "Settings...") != nil,
            "hasQuickSwitchViewSubmenu": appMenu.item(withTitle: "Quick Switch View")?.submenu != nil
        ])
    }
    func menuWillOpen(_ menu: NSMenu) {
        updateWaterfallViewModeMenuState()
    }
    private func menuItem(title: String, action: Selector?, keyEquivalent: String) -> NSMenuItem {
        let item = NSMenuItem(title: title, action: action, keyEquivalent: keyEquivalent)
        item.target = self
        return item
    }
    private func responderMenuItem(title: String, action: Selector?, keyEquivalent: String) -> NSMenuItem {
        NSMenuItem(title: title, action: action, keyEquivalent: keyEquivalent)
    }
    private func quickSwitchViewModeMenuItem() -> NSMenuItem {
        let submenu = NSMenu(title: "Quick Switch View")
        let verticalItem = menuItem(
            title: "Vertical Columns",
            action: #selector(useVerticalWaterfallViewMode),
            keyEquivalent: ""
        )
        let horizontalItem = menuItem(
            title: "Horizontal Waterfall",
            action: #selector(useHorizontalWaterfallViewMode),
            keyEquivalent: ""
        )
        submenu.addItem(verticalItem)
        submenu.addItem(horizontalItem)
        verticalWaterfallViewModeMenuItems.append(verticalItem)
        horizontalWaterfallViewModeMenuItems.append(horizontalItem)
        let rootItem = NSMenuItem(title: "Quick Switch View", action: nil, keyEquivalent: "")
        rootItem.submenu = submenu
        return rootItem
    }
    private func updateWaterfallViewModeMenuState() {
        let mode = quickSwitchWaterfallViewMode()
        for item in verticalWaterfallViewModeMenuItems {
            item.state = mode == .verticalColumns ? .on : .off
        }
        for item in horizontalWaterfallViewModeMenuItems {
            item.state = mode == .horizontalMasonry ? .on : .off
        }
    }
    private func showPermissionsIfNeeded() {
@@ -354,8 +477,10 @@
            let controller = QuickSwitchSessionController(
                snapshotLoader: quickSwitchSnapshotLoader(),
                windowActivationService: quickSwitchWindowActivationService(),
                spaceActivationService: quickSwitchSpaceActivationService(),
                windowCloseService: quickSwitchWindowCloseService(),
                disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh,
                waterfallViewMode: quickSwitchWaterfallViewMode(),
                closeConfirmationRequired: quickSwitchConfirmBeforeClose,
                onCloseConfirmationDisabled: { [weak self] in
                    self?.setQuickSwitchConfirmBeforeClose(false)
@@ -368,6 +493,9 @@
            )
            controller.onSnapshotUpdated = { [weak self] in
                self?.writeQuickSwitchReportIfNeeded()
            }
            controller.onOpenSettings = { [weak self] in
                self?.showSettingsFromQuickSwitch()
            }
            quickSwitchSessionController = controller
        }
@@ -428,8 +556,10 @@
            let controller = QuickSwitchSessionController(
                snapshotLoader: quickSwitchSnapshotLoader(),
                windowActivationService: quickSwitchWindowActivationService(),
                spaceActivationService: quickSwitchSpaceActivationService(),
                windowCloseService: quickSwitchWindowCloseService(),
                disableScreenshotRefresh: round1QuickSwitchOptions.disableScreenshotRefresh,
                waterfallViewMode: quickSwitchWaterfallViewMode(),
                closeConfirmationRequired: quickSwitchConfirmBeforeClose,
                onCloseConfirmationDisabled: { [weak self] in
                    self?.setQuickSwitchConfirmBeforeClose(false)
@@ -442,6 +572,9 @@
            )
            controller.onSnapshotUpdated = { [weak self] in
                self?.writeQuickSwitchReportIfNeeded()
            }
            controller.onOpenSettings = { [weak self] in
                self?.showSettingsFromQuickSwitch()
            }
            quickSwitchSessionController = controller
        }
@@ -464,13 +597,21 @@
        guard !isRunningQuickSwitchLifecycle else { return }
        let reportPathSummary = DevelopmentDiagnostics.pathSummary(reportPath)
        let report = quickSwitchSessionController?.reportDictionary() ?? [
        var report: [String: Any] = quickSwitchSessionController?.reportDictionary() ?? [
            "snapshotLoaded": false,
            "quickSwitchVisible": false,
            "permissionStatus": "\(accessibilityStatus())",
            "diagnosticLogLocationKind": DevelopmentDiagnostics.pathSummary(DevelopmentDiagnostics.logPath)["locationKind"] ?? "unknown",
            "diagnosticLogBasename": DevelopmentDiagnostics.pathSummary(DevelopmentDiagnostics.logPath)["basename"] ?? "aligner-dev.log"
        ]
        let versionBuild = appVersionBuild()
        report["preferenceWaterfallViewMode"] = preferenceStore.read().quickSwitchWaterfallViewMode.rawValue
        report["resolvedWaterfallViewMode"] = quickSwitchWaterfallViewMode().rawValue
        report["aboutVersion"] = versionBuild.version
        report["aboutBuild"] = versionBuild.build
        report["statusMenu"] = statusMenuReportDictionary()
        report["mainMenu"] = mainMenuReportDictionary()
        report["settingsWindow"] = settingsWindowReportDictionary()
        do {
            let data = try JSONSerialization.data(withJSONObject: report, options: [.prettyPrinted, .sortedKeys])
@@ -517,6 +658,18 @@
            return FixtureQuickSwitchSnapshotLoader(appCount: 1, mode: .candidateFiltering)
        }
        if round1QuickSwitchOptions.fixtureSpaceFilter {
            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,
@@ -542,6 +695,19 @@
        return CGWindowAXWindowService(spaceIDsByWindowIDProvider: { _ in [:] })
    }
    private func quickSwitchWaterfallViewMode() -> QuickSwitchWaterfallViewMode {
        round1QuickSwitchOptions.waterfallViewMode
            ?? preferenceStore.read().quickSwitchWaterfallViewMode
    }
    private func quickSwitchSpaceActivationService() -> any SpaceActivationServiceProtocol {
        if round1QuickSwitchOptions.debugWindowActivation {
            return DebugSpaceActivationService()
        }
        return PrivateSpaceActivationService()
    }
    private func quickSwitchWindowCloseService() -> any WindowCloseServiceProtocol {
        if round1QuickSwitchOptions.debugWindowClose {
            return DebugWindowCloseService()
@@ -562,6 +728,140 @@
        ])
    }
    private func setQuickSwitchWaterfallViewMode(_ mode: QuickSwitchWaterfallViewMode) {
        let preferences = preferenceStore.read()
        let updatedPreferences = AlignerPreferences(
            showMinimizedWindows: preferences.showMinimizedWindows,
            showFullscreenWindows: preferences.showFullscreenWindows,
            quickSwitchWaterfallViewMode: mode,
            theme: preferences.theme,
            language: preferences.language,
            spaceDisplayStrategy: preferences.spaceDisplayStrategy
        )
        preferenceStore.write(updatedPreferences)
        quickSwitchSessionController?.setWaterfallViewMode(mode)
        updateWaterfallViewModeMenuState()
        settingsWindowController?.refreshWaterfallViewMode()
        writeQuickSwitchReportIfNeeded()
        DevelopmentDiagnostics.log("app.quickSwitch.waterfallViewModePreferenceChanged", [
            "mode": mode.rawValue,
            "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false
        ])
    }
    private func appVersionBuild() -> (version: String, build: String) {
        let info = Bundle.main.infoDictionary ?? [:]
        let version = info["CFBundleShortVersionString"] as? String ?? "Unknown"
        let build = info["CFBundleVersion"] as? String ?? "Unknown"
        return (version, build)
    }
    private func statusMenuReportDictionary() -> [String: Any] {
        updateWaterfallViewModeMenuState()
        let verticalItem = verticalWaterfallViewModeMenuItems.first
        let horizontalItem = horizontalWaterfallViewModeMenuItems.first
        return [
            "hasAboutItem": statusItem?.menu?.item(withTitle: "About Aligner...") != nil,
            "hasSettingsItem": statusItem?.menu?.item(withTitle: "Settings...") != nil,
            "hasQuickSwitchViewSubmenu": statusItem?.menu?.item(withTitle: "Quick Switch View")?.submenu != nil,
            "hasVerticalColumnsItem": verticalItem != nil,
            "hasHorizontalWaterfallItem": horizontalItem != nil,
            "verticalColumnsState": verticalItem?.state == .on ? "on" : "off",
            "horizontalWaterfallState": horizontalItem?.state == .on ? "on" : "off"
        ]
    }
    private func mainMenuReportDictionary() -> [String: Any] {
        updateWaterfallViewModeMenuState()
        let appMenu = NSApp.mainMenu?.items.first?.submenu
        let viewMenu = NSApp.mainMenu?.item(withTitle: "View")?.submenu
        return [
            "hasMenu": NSApp.mainMenu != nil,
            "hasAppMenu": appMenu != nil,
            "hasAboutItem": appMenu?.item(withTitle: "About Aligner...") != nil,
            "hasSettingsItem": appMenu?.item(withTitle: "Settings...") != 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
        ]
    }
    private func settingsWindowReportDictionary() -> [String: Any] {
        settingsWindowController?.reportDictionary() ?? [
            "exists": false,
            "visible": false,
            "title": "",
            "selectedTabLabel": "",
            "selectedTabIdentifier": "",
            "waterfallViewMode": quickSwitchWaterfallViewMode().rawValue,
            "hasViewTab": false,
            "usesSwiftUI": false
        ]
    }
    @objc private func showAbout() {
        let versionBuild = appVersionBuild()
        DevelopmentDiagnostics.log("app.menu.showAbout", [
            "version": versionBuild.version,
            "build": versionBuild.build
        ])
        NSApp.setActivationPolicy(.regular)
        let alert = NSAlert()
        alert.messageText = "Aligner"
        alert.informativeText = "Version / Build: \(versionBuild.version) / \(versionBuild.build)"
        alert.alertStyle = .informational
        alert.addButton(withTitle: "OK")
        alert.icon = NSApp.applicationIconImage
        NSApp.activate(ignoringOtherApps: true)
        alert.runModal()
    }
    @objc private func showSettingsFromMenu() {
        openSettingsWindow(source: "menu")
    }
    private func showSettingsFromQuickSwitch() {
        openSettingsWindow(source: "quickSwitch")
    }
    private func openSettingsWindow(source: String) {
        DevelopmentDiagnostics.log("app.settings.show", [
            "source": source,
            "quickSwitchVisible": quickSwitchSessionController?.isVisible ?? false,
            "mode": quickSwitchWaterfallViewMode().rawValue
        ])
        hideQuickSwitchSession(reason: .userClosed)
        NSApp.setActivationPolicy(.regular)
        if settingsWindowController == nil {
            settingsWindowController = SettingsWindowController(
                modeProvider: { [weak self] in
                    self?.quickSwitchWaterfallViewMode() ?? .verticalColumns
                },
                onWaterfallViewModeChanged: { [weak self] mode in
                    self?.setQuickSwitchWaterfallViewMode(mode)
                }
            )
        }
        settingsWindowController?.showWindow(nil)
        NSApp.activate(ignoringOtherApps: true)
        writeQuickSwitchReportIfNeeded()
        DevelopmentDiagnostics.log("app.settings.windowShown", [
            "windowVisible": settingsWindowController?.window?.isVisible ?? false,
            "activationPolicy": NSApp.activationPolicy().rawValue
        ])
    }
    @objc private func useVerticalWaterfallViewMode() {
        setQuickSwitchWaterfallViewMode(.verticalColumns)
    }
    @objc private func useHorizontalWaterfallViewMode() {
        setQuickSwitchWaterfallViewMode(.horizontalMasonry)
    }
    @objc private func showPermissions() {
        DevelopmentDiagnostics.log("app.permissions.show", [
            "accessibility": permissionStatusString(accessibilityStatus()),