Ariver
2026-06-27 369c8f03db2f6d507697ab2296db5e6f81c8cd1e
src/Apptag/ApptagApp.swift
@@ -34,6 +34,7 @@
    private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
    private static let statusItemAccessibilityLabel = AppIdentity.displayName
    private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem")
    private static let proStatusMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherProStatusMenuItem")
    private static let helpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherHelpMenu")
    private static let downloadHelpMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherDownloadHelpMenuItem")
    private static let externalActivationNotification = Notification.Name("TagLauncherExternalActivationRequested")
@@ -193,8 +194,10 @@
    func applicationDidFinishLaunching(_ notification: Notification) {
        AppDefaults.register()
        L10n.setup()
        ProEntitlementCenter.shared.start()
        migrateDefaultGroupName()
        TagDatabase.seedDefaultTags()
        _ = TagDatabase.relocalizeSystemTagsForCurrentLanguage()
        syncChromeSettings(force: true)
        observeHotkeyStatusChanges()
        registerConfiguredHotkeys()
@@ -342,11 +345,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)
@@ -363,7 +408,7 @@
        overlayGeneration expectedOverlayGeneration: Int? = nil
    ) {
        if NSApp.activationPolicy() != .regular {
            NSApp.setActivationPolicy(.regular)
            setLauncherActivationPolicy(.regular)
        }
        if isOverlayVisible && !NSApp.presentationOptions.contains(.hideDock) {
@@ -425,7 +470,7 @@
            ? .regular
            : (showDock ? .regular : .accessory))))
        if NSApp.activationPolicy() != desiredPolicy {
            NSApp.setActivationPolicy(desiredPolicy)
            setLauncherActivationPolicy(desiredPolicy)
        }
        let desiredPresentation: NSApplication.PresentationOptions = isOverlayVisible ? [.hideDock] : []
@@ -617,6 +662,9 @@
        versionItem.isEnabled = false
        menu.addItem(versionItem)
        let proStatusItem = makeProStatusMenuItem()
        menu.addItem(proStatusItem)
        menu.addItem(.separator())
        let prefsItem = NSMenuItem(
            title: tr("menu.preferences"),
@@ -653,6 +701,7 @@
    }
    func menuWillOpen(_ menu: NSMenu) {
        configureProStatusMenuItem(in: menu)
        statusMenuScreenForNextOverlay = overlayController.screenContainingCurrentPointer()
            ?? statusItem?.button?.window?.screen
            ?? NSScreen.main
@@ -722,6 +771,53 @@
        image.isTemplate = true
        image.accessibilityDescription = "TagLauncher"
        return image
    }
    private func makeProStatusMenuItem() -> NSMenuItem {
        let item = NSMenuItem(
            title: "",
            action: #selector(openProStatusFromStatusMenu(_:)),
            keyEquivalent: ""
        )
        item.identifier = Self.proStatusMenuItemIdentifier
        item.target = self
        configureProStatusMenuItem(item)
        return item
    }
    private func configureProStatusMenuItem(in menu: NSMenu) {
        guard let item = menu.items.first(where: { $0.identifier == Self.proStatusMenuItemIdentifier }) else {
            return
        }
        configureProStatusMenuItem(item)
    }
    private func configureProStatusMenuItem(_ item: NSMenuItem) {
        let isUnlocked = ProEntitlementPolicy.accessState().isUnlocked
        item.title = tr(isUnlocked ? "menu.proStatus.unlocked" : "menu.proStatus.free")
        item.action = #selector(openProStatusFromStatusMenu(_:))
        item.target = self
        item.isEnabled = true
        item.image = isUnlocked ? makeProStatusMenuIcon() : nil
    }
    private func makeProStatusMenuIcon() -> NSImage? {
        guard let symbol = NSImage(systemSymbolName: "crown.fill", accessibilityDescription: "Pro") else {
            return nil
        }
        let configuration = NSImage.SymbolConfiguration(pointSize: 13, weight: .semibold)
        let configuredSymbol = symbol.withSymbolConfiguration(configuration) ?? symbol
        let image = NSImage(size: NSSize(width: 16, height: 16))
        image.lockFocus()
        NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
        let iconRect = NSRect(x: 1.5, y: 1.5, width: 13, height: 13)
        configuredSymbol.draw(in: iconRect, from: .zero, operation: .sourceOver, fraction: 1.0)
        NSColor(red: 0.96, green: 0.63, blue: 0.10, alpha: 1.0).set()
        iconRect.fill(using: .sourceAtop)
        image.unlockFocus()
        image.isTemplate = false
        image.accessibilityDescription = "Pro"
        return image
    }
@@ -1625,9 +1721,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() {
@@ -1649,6 +1749,14 @@
    }
    @objc private func openPreferences(_ sender: Any? = nil) {
        openPreferences(targetTab: nil)
    }
    @objc private func openProStatusFromStatusMenu(_ sender: NSMenuItem) {
        openPreferences(targetTab: SettingsTabTarget.about)
    }
    private func openPreferences(targetTab: String?) {
        dismissQuickSearchIfNeeded()
        TagDatabase.flushPendingCategorySchemeBackupBatch()
        if overlayAvoidsSpaceSwitch {
@@ -1664,6 +1772,7 @@
        if let settingsWindow {
            prepareSettingsWindow(settingsWindow)
            requestPreferencesTabSelection(targetTab)
            return
        }
@@ -1674,10 +1783,19 @@
            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() {
@@ -1751,7 +1869,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,
@@ -1786,6 +1910,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,