Ariver
2026-06-12 ca6f9be3818641e87d854a82646d2ea2aff6e545
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)
@@ -21,6 +22,8 @@
    private var quickSwitchTriggerService: CGEventTapTriggerService?
    private var isRunningQuickSwitchLifecycle = false
    private var statusItem: NSStatusItem?
    private var verticalWaterfallViewModeMenuItem: NSMenuItem?
    private var horizontalWaterfallViewModeMenuItem: NSMenuItem?
    func applicationDidFinishLaunching(_ notification: Notification) {
        let bundlePathSummary = DevelopmentDiagnostics.pathSummary(Bundle.main.bundlePath)
@@ -91,21 +94,68 @@
        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(.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,
            "hasWaterfallViewModeItems": verticalWaterfallViewModeMenuItem != nil
                && horizontalWaterfallViewModeMenuItem != 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 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)
        verticalWaterfallViewModeMenuItem = verticalItem
        horizontalWaterfallViewModeMenuItem = horizontalItem
        let rootItem = NSMenuItem(title: "Quick Switch View", action: nil, keyEquivalent: "")
        rootItem.submenu = submenu
        return rootItem
    }
    private func updateWaterfallViewModeMenuState() {
        let mode = quickSwitchWaterfallViewMode()
        verticalWaterfallViewModeMenuItem?.state = mode == .verticalColumns ? .on : .off
        horizontalWaterfallViewModeMenuItem?.state = mode == .horizontalMasonry ? .on : .off
    }
    private func showPermissionsIfNeeded() {
@@ -354,8 +404,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)
@@ -428,8 +480,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)
@@ -464,13 +518,19 @@
        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()
        do {
            let data = try JSONSerialization.data(withJSONObject: report, options: [.prettyPrinted, .sortedKeys])
@@ -546,6 +606,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()
@@ -566,6 +639,71 @@
        ])
    }
    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()
        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()
        return [
            "hasAboutItem": statusItem?.menu?.item(withTitle: "About Aligner...") != nil,
            "hasQuickSwitchViewSubmenu": statusItem?.menu?.item(withTitle: "Quick Switch View")?.submenu != nil,
            "hasVerticalColumnsItem": verticalWaterfallViewModeMenuItem != nil,
            "hasHorizontalWaterfallItem": horizontalWaterfallViewModeMenuItem != nil,
            "verticalColumnsState": verticalWaterfallViewModeMenuItem?.state == .on ? "on" : "off",
            "horizontalWaterfallState": horizontalWaterfallViewModeMenuItem?.state == .on ? "on" : "off"
        ]
    }
    @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 useVerticalWaterfallViewMode() {
        setQuickSwitchWaterfallViewMode(.verticalColumns)
    }
    @objc private func useHorizontalWaterfallViewMode() {
        setQuickSwitchWaterfallViewMode(.horizontalMasonry)
    }
    @objc private func showPermissions() {
        DevelopmentDiagnostics.log("app.permissions.show", [
            "accessibility": permissionStatusString(accessibilityStatus()),