From 01bab5422712299b49f656e6b4c42a3bf314f519 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Thu, 02 Jul 2026 11:02:42 +0800
Subject: [PATCH] Release 8.3.5 free tag order persistence

---
 src/Apptag/ApptagApp.swift |  156 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 138 insertions(+), 18 deletions(-)

diff --git a/src/Apptag/ApptagApp.swift b/src/Apptag/ApptagApp.swift
index 53dfb40..2ef8916 100644
--- a/src/Apptag/ApptagApp.swift
+++ b/src/Apptag/ApptagApp.swift
@@ -28,7 +28,14 @@
 
 // MARK: - App Delegate (menubar + overlay window + hotkey)
 
+private enum HotkeyRegistrationAttempt {
+    case success(EventHotKeyRef)
+    case failure(OSStatus)
+}
+
 final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
+    static private(set) weak var shared: AppDelegate?
+
     private static let showDockIconKey = "showDockIcon"
     private static let statusItemAutosaveName = AppIdentity.statusItemAutosaveName
     private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton")
@@ -58,6 +65,7 @@
     private var quickSearchHotkeyRef: EventHotKeyRef?
     private var overlaySpaceHotkeyRef: EventHotKeyRef?
     private var hotkeyEventHandlerInstalled = false
+    private var configuredHotkeysSuspendedForRecording = false
     private var isQuickSearchOpen = false
     private var quickSearchShouldHideOverlayOnClose = false
     private var quickSearchOnlyOverlaySession = false
@@ -72,6 +80,11 @@
     private var overlayOpenedByQuickSearchOnly = false
     private var didFinishLaunching = false
     private var pendingShowOverlayInvocation = false
+
+    override init() {
+        super.init()
+        Self.shared = self
+    }
 
     private lazy var overlayController = OverlayWindowController(
         dependencies: OverlayWindowController.Dependencies(
@@ -220,6 +233,7 @@
         observeApplicationMenuChanges()
         observeChromeSettings()
         observeLanguageChanges()
+        observeProEntitlementChanges()
         setupLaunchAtLogin()
         suppressReopenUntil = Date().addingTimeInterval(1.0)
         didFinishLaunching = true
@@ -889,7 +903,7 @@
         if LauncherHotkeyRegistrationStore.state(for: .main) == .failed {
             return tr("menu.showShortcutUnavailable")
         }
-        return "\(tr("menu.showAppList"))  \(LauncherHotkey.main.displayString)"
+        return "\(tr("menu.showAppList"))  \(LauncherHotkeySettings.effectiveHotkey(for: .main).displayString)"
     }
 
     private func observeApplicationMenuChanges() {
@@ -1267,14 +1281,106 @@
     // MARK: - Global Hotkeys
 
     private func registerConfiguredHotkeys() {
+        guard !configuredHotkeysSuspendedForRecording else { return }
         installHotkeyEventHandlerIfNeeded()
-        registerFixedHotkey(.main)
-        registerFixedHotkey(.quickSearch)
+        registerEffectiveHotkey(.main)
+        registerEffectiveHotkey(.quickSearch)
     }
 
-    private func registerFixedHotkey(_ kind: LauncherHotkeyKind) {
+    func suspendConfiguredHotkeysForRecording() {
+        guard !configuredHotkeysSuspendedForRecording else { return }
+        configuredHotkeysSuspendedForRecording = true
+        unregisterHotkey(for: .main)
+        unregisterHotkey(for: .quickSearch)
+    }
+
+    func resumeConfiguredHotkeysAfterRecording() {
+        guard configuredHotkeysSuspendedForRecording else { return }
+        configuredHotkeysSuspendedForRecording = false
+        registerConfiguredHotkeys()
+    }
+
+    private func registerEffectiveHotkey(_ kind: LauncherHotkeyKind) {
         unregisterHotkey(for: kind)
-        let hotkey = kind.hotkey
+        let hotkey = LauncherHotkeySettings.effectiveHotkey(for: kind)
+        registerAndStoreHotkey(hotkey, for: kind, markFailure: true)
+    }
+
+    @discardableResult
+    func applyCustomHotkey(_ hotkey: LauncherHotkey, for kind: LauncherHotkeyKind) -> LauncherHotkeyCustomizationResult {
+        guard ProEntitlementPolicy.isUnlocked(.customHotkeys) else {
+            return .locked
+        }
+        if let error = LauncherHotkeySettings.validationError(for: hotkey, kind: kind) {
+            return .invalid(error)
+        }
+        return activateHotkey(hotkey, for: kind, persistCustom: true)
+    }
+
+    @discardableResult
+    func restoreDefaultHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkeyCustomizationResult {
+        guard ProEntitlementPolicy.isUnlocked(.customHotkeys) else {
+            return .locked
+        }
+        return activateHotkey(LauncherHotkeySettings.defaultHotkey(for: kind), for: kind, persistCustom: false)
+    }
+
+    private func activateHotkey(
+        _ hotkey: LauncherHotkey,
+        for kind: LauncherHotkeyKind,
+        persistCustom: Bool
+    ) -> LauncherHotkeyCustomizationResult {
+        installHotkeyEventHandlerIfNeeded()
+
+        if LauncherHotkeySettings.effectiveHotkey(for: kind) == hotkey,
+           hotkeyRef(for: kind) != nil {
+            if persistCustom {
+                LauncherHotkeySettings.save(hotkey, for: kind)
+                LauncherHotkeyRegistrationStore.setActive(for: kind)
+                return .saved
+            }
+            LauncherHotkeySettings.clearCustomHotkey(for: kind)
+            LauncherHotkeyRegistrationStore.setActive(for: kind)
+            return .restored
+        }
+
+        switch tryRegisterHotkey(hotkey, for: kind, markFailure: false) {
+        case .success(let newRef):
+            if let oldRef = hotkeyRef(for: kind) {
+                UnregisterEventHotKey(oldRef)
+            }
+            setHotkeyRef(newRef, for: kind)
+            if persistCustom {
+                LauncherHotkeySettings.save(hotkey, for: kind)
+                LauncherHotkeyRegistrationStore.setActive(for: kind)
+                return .saved
+            }
+            LauncherHotkeySettings.clearCustomHotkey(for: kind)
+            LauncherHotkeyRegistrationStore.setActive(for: kind)
+            return .restored
+        case .failure(let status):
+            return .registrationFailed(status)
+        }
+    }
+
+    private func registerAndStoreHotkey(
+        _ hotkey: LauncherHotkey,
+        for kind: LauncherHotkeyKind,
+        markFailure: Bool
+    ) {
+        switch tryRegisterHotkey(hotkey, for: kind, markFailure: markFailure) {
+        case .success(let newRef):
+            setHotkeyRef(newRef, for: kind)
+        case .failure:
+            setHotkeyRef(nil, for: kind)
+        }
+    }
+
+    private func tryRegisterHotkey(
+        _ hotkey: LauncherHotkey,
+        for kind: LauncherHotkeyKind,
+        markFailure: Bool
+    ) -> HotkeyRegistrationAttempt {
 
         var hotkeyID = EventHotKeyID()
         hotkeyID.signature = OSType(0x41505447) // 'APTG'
@@ -1291,12 +1397,16 @@
         )
 
         if status == noErr, let newRef {
-            setHotkeyRef(newRef, for: kind)
-            LauncherHotkeyRegistrationStore.setActive(for: kind)
+            if markFailure {
+                LauncherHotkeyRegistrationStore.setActive(for: kind)
+            }
+            return .success(newRef)
         } else {
-            setHotkeyRef(nil, for: kind)
-            LauncherHotkeyRegistrationStore.setFailed(status, for: kind)
+            if markFailure {
+                LauncherHotkeyRegistrationStore.setFailed(status, for: kind)
+            }
             print("[TagLauncher] Fixed hotkey registration failed for \(kind.rawValue): \(status)")
+            return .failure(status)
         }
     }
 
@@ -1306,6 +1416,18 @@
             object: nil,
             queue: .main
         ) { [weak self] _ in
+            self?.setupMenuBar()
+            self?.configureApplicationMenuWhenAvailable(retries: 2)
+        }
+    }
+
+    private func observeProEntitlementChanges() {
+        NotificationCenter.default.addObserver(
+            forName: .tagLauncherProEntitlementChanged,
+            object: nil,
+            queue: .main
+        ) { [weak self] _ in
+            self?.registerConfiguredHotkeys()
             self?.setupMenuBar()
             self?.configureApplicationMenuWhenAvailable(retries: 2)
         }
@@ -1818,12 +1940,7 @@
         ) { [weak self] event in
             guard let self, self.isQuickSearchOpen else { return event }
             if event.window === self.overlayWindow {
-                NotificationCenter.default.post(
-                    name: .tagLauncherQuickSearchDismissRequested,
-                    object: nil,
-                    userInfo: ["source": QuickSearchDismissSource.mouse]
-                )
-                return nil
+                return event
             }
             return event
         }
@@ -1886,7 +2003,7 @@
     }
 
     @objc private func openProStatusFromStatusMenu(_ sender: NSMenuItem) {
-        openPreferences(targetTab: SettingsTabTarget.about)
+        openPreferences(targetTab: SettingsTabTarget.pro)
     }
 
     private func openPreferences(targetTab: String?) {
@@ -2052,12 +2169,15 @@
         // NSHostingView.hitTest may return a SwiftUI-internal wrapper — the
         // actual AppKit subview may be nested deeper.
         if let container = findTextFieldContainer(in: hit) {
-            container.mouseDown(with: event)
+            container.focusTextField()
             return
         }
         if let tf = findNSTextField(in: hit) {
+            let shouldSelectAll = tf.currentEditor() == nil
             tf.window?.makeFirstResponder(tf)
-            tf.mouseDown(with: event)
+            if shouldSelectAll {
+                tf.selectText(nil)
+            }
             return
         }
         super.mouseDown(with: event)

--
Gitblit v1.9.3