From 71a520840eb3debe46173c1f73bc85bbea0b1aa6 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 28 Jun 2026 23:42:38 +0800
Subject: [PATCH] Implement Pro custom hotkeys for 8.2.0
---
src/Apptag/ApptagApp.swift | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 109 insertions(+), 9 deletions(-)
diff --git a/src/Apptag/ApptagApp.swift b/src/Apptag/ApptagApp.swift
index 53dfb40..8ce54cd 100644
--- a/src/Apptag/ApptagApp.swift
+++ b/src/Apptag/ApptagApp.swift
@@ -28,6 +28,11 @@
// MARK: - App Delegate (menubar + overlay window + hotkey)
+private enum HotkeyRegistrationAttempt {
+ case success(EventHotKeyRef)
+ case failure(OSStatus)
+}
+
final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
private static let showDockIconKey = "showDockIcon"
private static let statusItemAutosaveName = AppIdentity.statusItemAutosaveName
@@ -220,6 +225,7 @@
observeApplicationMenuChanges()
observeChromeSettings()
observeLanguageChanges()
+ observeProEntitlementChanges()
setupLaunchAtLogin()
suppressReopenUntil = Date().addingTimeInterval(1.0)
didFinishLaunching = true
@@ -889,7 +895,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() {
@@ -1268,13 +1274,91 @@
private func registerConfiguredHotkeys() {
installHotkeyEventHandlerIfNeeded()
- registerFixedHotkey(.main)
- registerFixedHotkey(.quickSearch)
+ registerEffectiveHotkey(.main)
+ registerEffectiveHotkey(.quickSearch)
}
- private func registerFixedHotkey(_ kind: LauncherHotkeyKind) {
+ 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 +1375,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)
}
}
@@ -1311,6 +1399,18 @@
}
}
+ private func observeProEntitlementChanges() {
+ NotificationCenter.default.addObserver(
+ forName: .tagLauncherProEntitlementChanged,
+ object: nil,
+ queue: .main
+ ) { [weak self] _ in
+ self?.registerConfiguredHotkeys()
+ self?.setupMenuBar()
+ self?.configureApplicationMenuWhenAvailable(retries: 2)
+ }
+ }
+
private func installHotkeyEventHandlerIfNeeded() {
guard !hotkeyEventHandlerInstalled else { return }
hotkeyEventHandlerInstalled = true
--
Gitblit v1.9.3