"The modern frameworks can watch the key. Only the old one can take it."
Three Doors, One Job
Flint needs a key chord that fires everywhere — in any app, whether or not Flint is frontmost — and, crucially, gets consumed so it doesn't also type into whatever you're using. macOS gives you three doors to that, and they are not equivalent. The surprise, coming from modern Swift, is that the newest door is the wrong one and the oldest door is the right one.
Observe Is Not Consume
The first door is NSEvent.addGlobalMonitorForEvents. It is modern, easy, and Swift-native — and it is passive. A global NSEvent monitor lets you observe key events happening in other apps, but it cannot intercept or block them. If you used it for a hotkey, your action would fire and the keystroke would also reach the focused app. That's fine for a heads-up display that reacts to activity; it's useless for a command shortcut, which must swallow the keys it claims.
The Firehose and the Scalpel
The second door is a CGEvent tap. It is powerful: it can intercept and consume events system-wide. But it's a firehose — it sees every event and you filter for yours — it requires Accessibility permission, and its lifecycle is fragile. For a handful of discrete chords, that's a heavy, error-prone way in. The third door, RegisterEventHotKey, is a scalpel: you register one specific chord with the OS, your handler fires when it's pressed, and the chord is consumed. It's lightweight and needs no Accessibility just to register the hotkey. For Flint's job — a few named chords — the scalpel wins cleanly.
DOOR consumes? needs AX? weight fit for Flint
--------------------------- --------- --------- ------- -------------
NSEvent global monitor no no light no (passive)
CGEvent tap yes yes heavy overkill
RegisterEventHotKey (Carbon) yes no* light yes
* Flint still needs Accessibility to READ and INSERT text —
just not to register the chord itself.
Deprecated Is Not Removed
RegisterEventHotKey lives in Carbon's HIToolbox, an API family Apple marked deprecated a very long time ago. That word scares people off, but deprecated is not the same as removed: it still ships, still works, and Apple never wrote a modern replacement for this exact narrow need. The mature engineering move is to use the stable, purpose-built API even though its label says 'old,' and to wall off that one Carbon dependency behind a small Swift type so the deprecation lives in one file instead of leaking through the app.