Skip to content

Global hotkeys: Carbon RegisterEventHotKey does swallow the key, and needs no permission — measured #41

Description

@math65

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions