| | |
| | | require(settings, "modifiers == UInt32(kEventKeyModifierFnMask)", "Fn-only must be restricted explicitly") |
| | | require(settings, "hotkey.keyCode == UInt32(kVK_Space)", "Fn-only must only be valid for Space") |
| | | require(settings, "isReservedBySystem", "system-reserved shortcuts must be rejected") |
| | | require(settings, "modifiers == UInt32(shiftKey)", "Shift-only must remain rejected") |
| | | if "modifiers == UInt32(shiftKey) || modifiers == UInt32(optionKey)" in settings: |
| | | fail("Option-only shortcuts such as Option+Space must be allowed") |
| | | require(settings, "private static let functionKeyCodes: Set<Int>", "function keys must use an explicit Carbon key-code set") |
| | | require(settings, "functionKeyCodes.contains(key)", "supported-key validation must check the explicit function key set") |
| | | if "kVK_F1...kVK_F12" in settings: |
| | | fail("Carbon function key codes are not contiguous; kVK_F1...kVK_F12 crashes at runtime") |
| | | |
| | | effective_body = function_body(settings, "static func effectiveHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey") |
| | | if "validationError(for:" in effective_body: |
| | |
| | | require(activate_body, "return .registrationFailed(status)", "registration failure must not save the candidate") |
| | | |
| | | require(app, "observeProEntitlementChanges()", "AppDelegate must observe Pro entitlement changes") |
| | | require(app, "static private(set) weak var shared: AppDelegate?", "Preferences must have a stable AppDelegate bridge for custom hotkey writes") |
| | | require(app, "Self.shared = self", "AppDelegate must publish its stable shared bridge during initialization") |
| | | require(app, "private var configuredHotkeysSuspendedForRecording = false", "AppDelegate must track hotkey recording suspension") |
| | | require(app, "registerConfiguredHotkeys()", "Pro entitlement changes must re-register effective hotkeys") |
| | | require(app, "LauncherHotkeySettings.effectiveHotkey(for: .main).displayString", "menu title must display the effective App Grid shortcut") |
| | | require(app, "LauncherHotkeySettings.effectiveHotkey(for: kind)", "hotkey registration must read effective shortcuts") |
| | | require(app, "RegisterEventHotKey", "Carbon RegisterEventHotKey must remain the global hotkey backend") |
| | | require(app, "UnregisterEventHotKey(oldRef)", "old hotkey ref must be released after successful replacement") |
| | | |
| | | register_configured_body = function_body(app, "private func registerConfiguredHotkeys()") |
| | | require(register_configured_body, "guard !configuredHotkeysSuspendedForRecording else { return }", "configured hotkeys must stay unregistered while recording") |
| | | |
| | | suspend_body = function_body(app, "func suspendConfiguredHotkeysForRecording()") |
| | | require(suspend_body, "guard !configuredHotkeysSuspendedForRecording else { return }", "recording suspension must be idempotent") |
| | | require(suspend_body, "configuredHotkeysSuspendedForRecording = true", "recording suspension must set the guard flag") |
| | | require(suspend_body, "unregisterHotkey(for: .main)", "recording must suspend the App Grid global hotkey") |
| | | require(suspend_body, "unregisterHotkey(for: .quickSearch)", "recording must suspend the Quick Search global hotkey") |
| | | if "LauncherHotkeySettings.save" in suspend_body or "clearCustomHotkey" in suspend_body: |
| | | fail("recording suspension must not write custom hotkey storage") |
| | | |
| | | resume_body = function_body(app, "func resumeConfiguredHotkeysAfterRecording()") |
| | | require(resume_body, "guard configuredHotkeysSuspendedForRecording else { return }", "recording resume must be idempotent") |
| | | require(resume_body, "configuredHotkeysSuspendedForRecording = false", "recording resume must clear the guard flag") |
| | | require(resume_body, "registerConfiguredHotkeys()", "recording resume must restore effective hotkeys") |
| | | |
| | | for forbidden in ["CGEventTapCreate", "AXIsProcessTrusted", "IOHIDManagerCreate", "addGlobalMonitorForEvents(matching: .keyDown"]: |
| | | if forbidden in app + preferences + settings + quick_search: |
| | |
| | | require(preferences, "NSEvent.addLocalMonitorForEvents(matching: .keyDown)", "recording must use local key-down capture in Settings") |
| | | require(preferences, "Int(event.keyCode) == kVK_Escape", "Esc must cancel recording") |
| | | require(preferences, "LauncherHotkeySettings.candidate(from: event)", "recording must normalize NSEvent into LauncherHotkey") |
| | | require(preferences, "AppDelegate.shared?", "recording must use the stable AppDelegate bridge instead of NSApp.delegate casting") |
| | | require(preferences, "appDelegate.suspendConfiguredHotkeysForRecording()", "recording must suspend existing global hotkeys before capture") |
| | | require(preferences, "AppDelegate.shared?.resumeConfiguredHotkeysAfterRecording()", "recording cleanup must restore effective global hotkeys") |
| | | require(preferences, ".applyCustomHotkey(candidate, for: kind)", "recording must call the AppDelegate transactional save path") |
| | | require(preferences, "CustomizableHotkeyInfoRow", "Settings hotkey rows must expose custom controls") |
| | | require(preferences, "StaticHotkeyInfoRow(", "internal App Grid Space shortcut must remain static") |
| | | require(preferences, "LauncherHotkeySettings.effectiveHotkey(for: .main).displayString", "Settings must display effective App Grid shortcut") |
| | | require(preferences, "LauncherHotkeySettings.effectiveHotkey(for: .quickSearch).displayString", "Settings must display effective Quick Search shortcut") |
| | | require(preferences, "HotkeyPrimaryActionButton", "custom hotkey rows must use the shared action button") |
| | | require(preferences, "ProStatusPill(text: title, style: .locked, compact: true)", "locked custom hotkey action must use the global Pro pill") |
| | | require(preferences, 'customizeTitle: tr("hotkeys.customize")', "locked custom hotkey actions must still show the action label") |
| | | require(preferences, 'proBadgeTitle: tr("pro.card.badge")', "locked custom hotkey actions must compose the global Pro badge beside the action label") |
| | | require(preferences, "ProStatusPill(text: proBadgeTitle, style: .locked, compact: true)", "locked custom hotkey action must use the global Pro pill") |
| | | require(preferences, "if canRestore {", "restore-default action must only appear when a custom shortcut exists") |
| | | |
| | | start_recording_body = function_body(preferences, "private func startHotkeyRecording(for kind: LauncherHotkeyKind)") |
| | | suspend_index = start_recording_body.find("appDelegate.suspendConfiguredHotkeysForRecording()") |
| | | monitor_index = start_recording_body.find("NSEvent.addLocalMonitorForEvents(matching: .keyDown)") |
| | | if suspend_index == -1 or monitor_index == -1 or not suspend_index < monitor_index: |
| | | fail("recording must suspend global hotkeys before installing the local monitor") |
| | | |
| | | stop_recording_body = function_body(preferences, "private func stopHotkeyRecording(showCancelledToast: Bool = false)") |
| | | require(stop_recording_body, "let wasRecording", "recording cleanup must only resume when a recording was active") |
| | | require(stop_recording_body, "NSEvent.removeMonitor(hotkeyRecorderMonitor)", "recording cleanup must remove the local monitor") |
| | | require(stop_recording_body, "recordingHotkeyKind = nil", "recording cleanup must clear recording state") |
| | | require(stop_recording_body, "AppDelegate.shared?.resumeConfiguredHotkeysAfterRecording()", "recording cleanup must restore global hotkeys") |
| | | |
| | | capture_recording_body = function_body(preferences, "private func captureHotkeyEvent(_ event: NSEvent)") |
| | | stop_index = capture_recording_body.find("stopHotkeyRecording(showCancelledToast: false)") |
| | | handle_index = capture_recording_body.find("handleHotkeyCustomizationResult(result)") |
| | | if stop_index == -1 or handle_index == -1 or not stop_index < handle_index: |
| | | fail("recording capture must restore global hotkeys before showing save/failure feedback") |
| | | |
| | | require(preferences, "if newTab != .hotkeys", "leaving the Hotkeys tab must end recording") |
| | | require(preferences, "stopHotkeyRecording(showCancelledToast: false)", "recording must clean up on tab change, Pro changes, and view disappearance") |
| | | |
| | | custom_row_start = preferences.find("private struct CustomizableHotkeyInfoRow") |
| | | custom_row_end = preferences.find("private struct HotkeyStatusPill") |
| | |
| | | if ".buttonStyle(.borderedProminent)" in custom_row: |
| | | fail("custom hotkey rows must not use the default blue borderedProminent Pro button") |
| | | |
| | | hotkey_button_start = preferences.find("private struct HotkeyPrimaryActionButton") |
| | | hotkey_button_end = preferences.find("private struct HotkeySecondaryActionButton") |
| | | if hotkey_button_start == -1 or hotkey_button_end == -1 or hotkey_button_end <= hotkey_button_start: |
| | | fail("could not inspect HotkeyPrimaryActionButton layout") |
| | | hotkey_button = preferences[hotkey_button_start:hotkey_button_end] |
| | | require(hotkey_button, ".buttonStyle(.bordered)", "locked custom hotkey action must visually match Data tab Pro-gated buttons") |
| | | |
| | | require(pro, "case customHotkeys", "ProFeature.customHotkeys is missing") |
| | | require(pro, "case .premiumThemes, .layoutImport, .layoutExport, .unlimitedNotes, .persistentAppSorting, .customHotkeys:", "customHotkeys must be unlocked only by the Pro access state") |
| | | require(pro, "case .premiumThemes, .layoutImport, .layoutExport, .customTagColors, .unlimitedNotes, .persistentAppSorting, .customHotkeys:", "customHotkeys must be unlocked only by the Pro access state") |
| | | require(pro, "NotificationCenter.default.post(name: .tagLauncherProEntitlementChanged", "entitlement changes must notify hotkey registration") |
| | | require(access_views, "case .customHotkeys:", "Pro prompt routing must include custom hotkeys") |
| | | |
| | |
| | | if "%status%" not in data["hotkeys.error.registrationFailed"]: |
| | | fail(f"{path.name} hotkeys.error.registrationFailed must keep %status% placeholder") |
| | | |
| | | print("PASS Pro custom hotkeys QA: Pro gating, transactional Carbon registration, duplicate-safe effective shortcuts, local recording, Pro-consistent row UI, and 29-language copy are wired") |
| | | print("PASS Pro custom hotkeys static QA: Pro gating, recording suspension, transactional save path, duplicate-safe effective shortcuts, local recording, Pro-consistent row UI, and 29-language copy are wired") |
| | | PY |
| | | |
| | | swift - <<'SWIFT' |
| | | import Carbon |
| | | import Darwin |
| | | |
| | | func fail(_ message: String) -> Never { |
| | | fputs("FAIL: \(message)\n", stderr) |
| | | exit(1) |
| | | } |
| | | |
| | | @discardableResult |
| | | func register( |
| | | label: String, |
| | | keyCode: Int, |
| | | modifiers: UInt32, |
| | | id: UInt32, |
| | | expectSuccess: Bool |
| | | ) -> EventHotKeyRef? { |
| | | let hotkeyID = EventHotKeyID(signature: OSType(0x54474C51), id: id) // 'TGLQ' |
| | | var ref: EventHotKeyRef? |
| | | let status = RegisterEventHotKey( |
| | | UInt32(keyCode), |
| | | modifiers, |
| | | hotkeyID, |
| | | GetApplicationEventTarget(), |
| | | 0, |
| | | &ref |
| | | ) |
| | | if expectSuccess { |
| | | guard status == noErr, let ref else { |
| | | fail("\(label) should register successfully, got status \(status)") |
| | | } |
| | | return ref |
| | | } |
| | | if status == noErr { |
| | | if let ref { UnregisterEventHotKey(ref) } |
| | | fail("\(label) should not register successfully") |
| | | } |
| | | return nil |
| | | } |
| | | |
| | | let modifiers = UInt32(controlKey | optionKey | shiftKey) |
| | | let mainRef = register( |
| | | label: "main representative shortcut", |
| | | keyCode: kVK_ANSI_9, |
| | | modifiers: modifiers, |
| | | id: 1, |
| | | expectSuccess: true |
| | | ) |
| | | let quickSearchRef = register( |
| | | label: "quick search representative shortcut", |
| | | keyCode: kVK_ANSI_8, |
| | | modifiers: modifiers, |
| | | id: 2, |
| | | expectSuccess: true |
| | | ) |
| | | let replacementRef = register( |
| | | label: "same-id replacement shortcut", |
| | | keyCode: kVK_ANSI_7, |
| | | modifiers: modifiers, |
| | | id: 1, |
| | | expectSuccess: true |
| | | ) |
| | | let optionSpaceRef = register( |
| | | label: "Option-Space shortcut", |
| | | keyCode: kVK_Space, |
| | | modifiers: UInt32(optionKey), |
| | | id: 4, |
| | | expectSuccess: true |
| | | ) |
| | | _ = register( |
| | | label: "duplicate shortcut conflict", |
| | | keyCode: kVK_ANSI_7, |
| | | modifiers: modifiers, |
| | | id: 3, |
| | | expectSuccess: false |
| | | ) |
| | | |
| | | if let optionSpaceRef { UnregisterEventHotKey(optionSpaceRef) } |
| | | if let replacementRef { UnregisterEventHotKey(replacementRef) } |
| | | if let quickSearchRef { UnregisterEventHotKey(quickSearchRef) } |
| | | if let mainRef { UnregisterEventHotKey(mainRef) } |
| | | |
| | | print("PASS Pro custom hotkeys runtime QA: Carbon accepts representative custom registrations, Option-Space, same-id replacement, and rejects duplicate active shortcuts") |
| | | SWIFT |