The global hotkey engine uses a listen-only CGEventTap, which requires the Input Monitoring permission and, under the App Sandbox, cannot swallow the key — the chord still reaches the app in front. Releases up to 1.8.0 asked for no permission at all; 1.9.0 is the first to require it.
HotkeyMonitor.swift's header rules out the Carbon alternative:
(Carbon RegisterEventHotKey was tried and rejected — it doesn't swallow either, and can't do bare keys / modifier-only chords.)
I measured both claims. Neither holds.
Does RegisterEventHotKey swallow?
A test binary registered ⌥J globally at .accessory policy (never frontmost), with TextEdit in front. ⌥J was chosen because it inserts a character when it is not swallowed, so the document answers the question by itself.
| Step |
Result |
| ⌥J, hotkey registered |
handler fired — TextEdit document stayed empty |
| Plain "A", hotkey registered |
"A" inserted — control: the app does receive keystrokes |
| ⌥J, after unregistering |
"Ï" inserted |
It swallows. The control rules out the alternative explanation that ⌥J simply types nothing on this layout (French/AZERTY).
What does it actually refuse?
RegisterEventHotKey return status, one call per form:
| Form |
Status |
| F13, no modifier |
0 — accepted |
| A letter key, no modifier |
0 — accepted |
| ⌘⇧A |
0 — accepted |
| ⌥⇧⌘Space |
0 — accepted |
| Modifier-only chord (⌘⌃ held) |
not expressible — the API takes a key code, and 0 is a real key |
Bare keys are accepted, so that half of the claim is wrong too. A bare letter would be swallowed system-wide and become untypable everywhere, which is a good reason to keep refusing it in the recorder — but F13–F20 is exactly the recommended binding, and it works.
Push-to-talk is covered as well: registering F13 bare and listening for both kEventHotKeyPressed and kEventHotKeyReleased produced F13 PRESSED then F13 RELEASED.
Proposal
Route by the shape of the binding:
- anything with a key code → Carbon. No permission, and the chord is genuinely swallowed.
- modifier-only chord → the existing tap, permission and all.
Consequences worth weighing:
Not yet measured
- Behaviour under secure input (password fields neutralise Carbon hotkeys).
- Migration of bindings already stored in preferences.
- Whether a Carbon hotkey and the SwiftUI menu key equivalent can coexist on the same chord without one shadowing the other.
The global hotkey engine uses a listen-only
CGEventTap, which requires the Input Monitoring permission and, under the App Sandbox, cannot swallow the key — the chord still reaches the app in front. Releases up to 1.8.0 asked for no permission at all; 1.9.0 is the first to require it.HotkeyMonitor.swift's header rules out the Carbon alternative:I measured both claims. Neither holds.
Does
RegisterEventHotKeyswallow?A test binary registered ⌥J globally at
.accessorypolicy (never frontmost), with TextEdit in front. ⌥J was chosen because it inserts a character when it is not swallowed, so the document answers the question by itself.It swallows. The control rules out the alternative explanation that ⌥J simply types nothing on this layout (French/AZERTY).
What does it actually refuse?
RegisterEventHotKeyreturn status, one call per form:0— accepted0— accepted0— accepted0— acceptedBare keys are accepted, so that half of the claim is wrong too. A bare letter would be swallowed system-wide and become untypable everywhere, which is a good reason to keep refusing it in the recorder — but F13–F20 is exactly the recommended binding, and it works.
Push-to-talk is covered as well: registering F13 bare and listening for both
kEventHotKeyPressedandkEventHotKeyReleasedproducedF13 PRESSEDthenF13 RELEASED.Proposal
Route by the shape of the binding:
Consequences worth weighing:
mainMenuOwns/safeMenuKeyEquivalent/ the double-fire guard have nothing left to arbitrate for those bindings. Global mic-toggle hotkey: work while ttaccessible is focused, and use one chord everywhere #38 is still correct for the engine as it stands today and should land on its own merits — this would simplify it afterwards, not block it.Not yet measured