ReviewOS

craft-native/craft

Global shortcuts: handlers register, hotkeys never fire — the event monitor is never installed

#47
Closed glennmichael123 opened this 11 days ago ·
11 days ago

window.craft.shortcuts.register() round-trips successfully and native records the registration — but no keystroke will ever arrive. setupGlobalMonitor in bridge_shortcuts.zig:152-175 gives up with a comment ("requires creating an Objective-C block, which is complex in Zig"), discards the event mask, and only stores the bridge in global state. The matcher checkKeyEvent (:179) is reachable solely through handleGlobalKeyEvent (:449), which has no callers anywhere in the tree.

This is the same declared-in-JS/dead-in-native shape as the old menu bug (#29), one layer deeper: an app registers Cmd+Shift+H, gets a success reply, and silently never hears the key. stacksjs/harness has live code depending on this today (shortcuts.register('harness.summon', 'Cmd+Shift+H')).

Two viable fix shapes, no ObjC blocks required:

  1. CGEventTap (CGEventTapCreate with a plain C callback) — pure C API, no blocks, and it's the canonical mechanism for global hotkeys. Needs Accessibility/Input Monitoring permission, which permissions can surface.
  2. Registered-class trampoline — same pattern #29 used for the menu target: objc_allocateClassPair a responder, class_addMethod a plain callconv(.c) function, and use addGlobalMonitorForEventsMatchingMask:handler:'s older non-block sibling, or a Carbon RegisterEventHotKey (still fully supported, no permissions prompt, and exactly scoped to hotkeys).

Carbon RegisterEventHotKey is probably the right call: it's what most Mac apps use for global shortcuts, takes a C callback, and avoids the Accessibility prompt entirely.

Until one of these lands, register() should reply with an honest error rather than success — a registration that can never fire is the worst of the three outcomes.

🤖 Generated with Claude Code

Sign in to comment on this issue.