Ariver
2026-06-25 92d4011efb3d98ecfeb166abed96e276bf13f58b
src/Apptag/ApptagApp.swift
@@ -17,7 +17,7 @@
        Settings {
            PreferencesView()
        }
        .defaultSize(width: 1000, height: 480)
        .defaultSize(width: 1000, height: 640)
        .commands {
            CommandGroup(replacing: .systemServices) { }
            CommandGroup(replacing: .appVisibility) { }
@@ -42,6 +42,7 @@
    private static let launcherOverlayLevel = NSWindow.Level(rawValue: NSWindow.Level.mainMenu.rawValue - 1)
    private static let overlayDefaultLevel = launcherOverlayLevel
    private static let overlayTextInputLevel = launcherOverlayLevel
    private static let settingsContentSize = NSSize(width: 1000, height: 640)
    private var statusItem: NSStatusItem?
    private var overlayKeyMonitor: Any?
@@ -250,12 +251,15 @@
            .combinedSessionState,
            eventType: .leftMouseUp
        )
        let recentMouseClick = min(lastMouseDown, lastMouseUp) < 1.2
        let recentMouseClick = min(lastMouseDown, lastMouseUp) < 0.9
        return recentMouseClick && isPointerNearDockArea()
    }
    private func isPointerNearDockArea() -> Bool {
        let mouse = NSEvent.mouseLocation
        let dockOrientation = UserDefaults(suiteName: "com.apple.dock")?
            .string(forKey: "orientation") ?? "bottom"
        let hiddenDockEdgeTolerance: CGFloat = 96
        return NSScreen.screens.contains { screen in
            let frame = screen.frame
            let visible = screen.visibleFrame
@@ -269,7 +273,16 @@
            let rightDock = visible.maxX < frame.maxX
                && mouse.x <= frame.maxX
                && mouse.x >= visible.maxX - 24
            return bottomDock || leftDock || rightDock
            let hiddenDockFallback: Bool
            switch dockOrientation {
            case "left":
                hiddenDockFallback = mouse.x <= frame.minX + hiddenDockEdgeTolerance
            case "right":
                hiddenDockFallback = mouse.x >= frame.maxX - hiddenDockEdgeTolerance
            default:
                hiddenDockFallback = mouse.y <= frame.minY + hiddenDockEdgeTolerance
            }
            return bottomDock || leftDock || rightDock || hiddenDockFallback
        }
    }
@@ -329,11 +342,53 @@
        }
    }
    private func foregroundWindowForActivationPolicyRestore() -> NSWindow? {
        if let settingsWindow, settingsWindow.isVisible {
            return settingsWindow
        }
        if let overlayWindow, overlayWindow.isVisible {
            return overlayWindow
        }
        return NSApp.keyWindow
    }
    private func setLauncherActivationPolicy(_ desiredPolicy: NSApplication.ActivationPolicy) {
        guard NSApp.activationPolicy() != desiredPolicy else { return }
        let restoreWindow = desiredPolicy == .accessory && NSApp.isActive
            ? foregroundWindowForActivationPolicyRestore()
            : nil
        // Deactivate before switching regular -> accessory; otherwise Dock can
        // keep a stale running tile until Dock itself restarts.
        if desiredPolicy == .accessory && NSApp.isActive {
            NSApp.deactivate()
        }
        NSApp.setActivationPolicy(desiredPolicy)
        guard desiredPolicy == .accessory,
              let restoreWindow,
              restoreWindow.isVisible
        else { return }
        DispatchQueue.main.async { [weak self, weak restoreWindow] in
            guard let self,
                  let restoreWindow,
                  restoreWindow.isVisible,
                  self.requiresForegroundOwnership
            else { return }
            NSApp.activate(ignoringOtherApps: true)
            restoreWindow.makeKeyAndOrderFront(nil)
            restoreWindow.orderFrontRegardless()
        }
    }
    private func beginLauncherForegroundOwnership(activate: Bool = true, keyWindow: NSWindow? = nil) {
        let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
        let desiredPolicy: NSApplication.ActivationPolicy = showDock ? .regular : .accessory
        if NSApp.activationPolicy() != desiredPolicy {
            NSApp.setActivationPolicy(desiredPolicy)
            setLauncherActivationPolicy(desiredPolicy)
        }
        if activate, showDock {
            claimLauncherForeground(keyWindow: keyWindow)
@@ -350,7 +405,7 @@
        overlayGeneration expectedOverlayGeneration: Int? = nil
    ) {
        if NSApp.activationPolicy() != .regular {
            NSApp.setActivationPolicy(.regular)
            setLauncherActivationPolicy(.regular)
        }
        if isOverlayVisible && !NSApp.presentationOptions.contains(.hideDock) {
@@ -412,7 +467,7 @@
            ? .regular
            : (showDock ? .regular : .accessory))))
        if NSApp.activationPolicy() != desiredPolicy {
            NSApp.setActivationPolicy(desiredPolicy)
            setLauncherActivationPolicy(desiredPolicy)
        }
        let desiredPresentation: NSApplication.PresentationOptions = isOverlayVisible ? [.hideDock] : []
@@ -1295,15 +1350,19 @@
    /// Settings must always appear centered over the current overlay view and float above it.
    private func prepareSettingsWindow(_ window: NSWindow) {
        dismissQuickSearchIfNeeded()
        let settingsSize = NSSize(width: 1000, height: 480)
        let settingsSize = Self.settingsContentSize
        let settingsFrameSize = window.frameRect(
            forContentRect: NSRect(origin: .zero, size: settingsSize)
        ).size
        window.identifier = NSUserInterfaceItemIdentifier("TagLauncherPreferencesWindow")
        window.minSize = settingsSize
        window.maxSize = settingsSize
        if abs(window.frame.width - settingsSize.width) > 0.5 || abs(window.frame.height - settingsSize.height) > 0.5 {
            window.setFrame(
                NSRect(origin: window.frame.origin, size: settingsSize),
                display: false
            )
        window.contentMinSize = settingsSize
        window.contentMaxSize = settingsSize
        window.minSize = settingsFrameSize
        window.maxSize = settingsFrameSize
        let currentContentSize = window.contentView?.bounds.size ?? window.contentLayoutRect.size
        if abs(currentContentSize.width - settingsSize.width) > 0.5
            || abs(currentContentSize.height - settingsSize.height) > 0.5 {
            window.setContentSize(settingsSize)
        }
        if let overlayWindow, overlayWindow.isVisible {
@@ -1608,9 +1667,13 @@
            forName: .tagLauncherOpenPreferencesRequested,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.openPreferences()
        ) { [weak self] notification in
            self?.openPreferences(targetTab: Self.preferencesTabTarget(from: notification))
        }
    }
    private static func preferencesTabTarget(from notification: Notification) -> String? {
        notification.userInfo?[SettingsTabTarget.userInfoKey] as? String
    }
    private func observeExternalActivationRequests() {
@@ -1632,6 +1695,10 @@
    }
    @objc private func openPreferences(_ sender: Any? = nil) {
        openPreferences(targetTab: nil)
    }
    private func openPreferences(targetTab: String?) {
        dismissQuickSearchIfNeeded()
        TagDatabase.flushPendingCategorySchemeBackupBatch()
        if overlayAvoidsSpaceSwitch {
@@ -1647,21 +1714,30 @@
        if let settingsWindow {
            prepareSettingsWindow(settingsWindow)
            requestPreferencesTabSelection(targetTab)
            return
        }
        let settingsSize = NSSize(width: 1000, height: 480)
        let window = NSWindow(
            contentRect: NSRect(origin: .zero, size: settingsSize),
            contentRect: NSRect(origin: .zero, size: Self.settingsContentSize),
            styleMask: [.titled, .closable],
            backing: .buffered,
            defer: false
        )
        window.title = tr("menu.preferences").replacingOccurrences(of: "…", with: "")
        window.contentView = NSHostingView(rootView: PreferencesView())
        window.contentView = NSHostingView(rootView: PreferencesView(initialTabRawValue: targetTab))
        window.isReleasedWhenClosed = false
        settingsWindow = window
        prepareSettingsWindow(window)
    }
    private func requestPreferencesTabSelection(_ targetTab: String?) {
        guard let targetTab else { return }
        NotificationCenter.default.post(
            name: .tagLauncherPreferencesTabRequested,
            object: nil,
            userInfo: [SettingsTabTarget.userInfoKey: targetTab]
        )
    }
    private func dismissQuickSearchIfNeeded() {
@@ -1735,7 +1811,13 @@
            super.mouseDown(with: event)
            return
        }
        if routeUsageTipsMouseDownIfNeeded(event) {
            return
        }
        if hit == self {
            if shouldSwallowUsageTipsBackdropClick(at: location) {
                return
            }
            if quickSearchSuppressesBackdropDismiss {
                NotificationCenter.default.post(
                    name: .tagLauncherQuickSearchDismissRequested,
@@ -1770,6 +1852,32 @@
        super.mouseDown(with: event)
    }
    private func routeUsageTipsMouseDownIfNeeded(_ event: NSEvent) -> Bool {
        guard let appGridHost = findAppGridCollectionHost(in: self) else { return false }
        return appGridHost.handleUsageTipsMouseDown(event)
    }
    private func findAppGridCollectionHost(in view: NSView) -> AppGridCollectionHostView? {
        if let host = view as? AppGridCollectionHostView {
            return host
        }
        for subview in view.subviews {
            if let host = findAppGridCollectionHost(in: subview) {
                return host
            }
        }
        return nil
    }
    private func shouldSwallowUsageTipsBackdropClick(at location: NSPoint) -> Bool {
        guard !UserDefaults.standard.bool(forKey: "hideUsageTips") else { return false }
        let height = min(bounds.height, AppGridUsageTipsMetrics.reservedHeight)
        guard height > 0 else { return false }
        let y = isFlipped ? max(0, bounds.height - height) : 0
        let region = NSRect(x: 0, y: y, width: bounds.width, height: height)
        return region.contains(location)
    }
    private func observeBackdropDismissSuppressionChanges() {
        modalInteractionObserver = NotificationCenter.default.addObserver(
            forName: .tagLauncherModalInteractionChanged,