Skip to content

[fix] Recover the Cocoa full grab tap after macOS disables it - #226

Open
NimbleAINinja wants to merge 1 commit into
omacom:mainfrom
NimbleAINinja:cocoa-grab-tap-recovery
Open

NimbleAINinja wants to merge 1 commit into
omacom:mainfrom
NimbleAINinja:cocoa-grab-tap-recovery

Conversation

@NimbleAINinja

Copy link
Copy Markdown

Problem

Command-Space intermittently reached macOS instead of the guest, opening Spotlight or Siri while the Omarchy window was focused. Once it started, it kept happening until the VM was restarted, while ordinary Super bindings kept working throughout.

Cause

macOS owns a CGEventTap's lifetime. A callback that returns too slowly gets the tap disabled with kCGEventTapDisabledByTimeout, and a tap can also be switched off with kCGEventTapDisabledByUserInput; recovery requires calling CGEventTapEnable again. QEMU 11.1.1 never does. handleTapEvent does not look at the event type, and neither notification is a key event, so +[NSEvent eventWithCGEvent:] returns nil and the event is reported as unhandled. The shipped binary imports CGEventTapCreate and no other CGEventTap symbol, so the tap simply stays dead for the session.

That explains the asymmetry: the full-grab-focus patch routes ordinary Command chords through key-window focus rather than the tap, so those keep working; only the system shortcuts that need the HID tap to pre-empt macOS depend on it.

Fix

A new Cocoa patch (qemu-cocoa-full-grab-reenable.patch) handles both notifications before the nil-returning conversion and re-enables the tap. This is a recovery path, not a way to defeat the timeout: if the callback is still too slow the window server disables the tap again.

Validation

  • make test passes.
  • Contract tests cover the guard, the re-enable call, the ordering against +[NSEvent eventWithCGEvent:], and the patch order in the runtime build (this patch edits handleTapEvent after the full-grab patch rewrites it).
  • Runtime built with make runtime; the resulting QEMU imports CGEventTapEnable, which the shipped 11.1.1 build does not. Verified on a live VM: Command-Space reaches the guest again. No pinned URL, archive, or checksum changed.

🤖 Generated with Claude Code

Command-Space intermittently reached macOS instead of the guest, opening
Spotlight or Siri while the Omarchy window was focused. Once it started, it
kept happening until the VM was restarted, and ordinary Super bindings kept
working throughout.

macOS owns a CGEventTap's lifetime. A callback that returns too slowly gets
the tap disabled with kCGEventTapDisabledByTimeout, and a tap can also be
switched off with kCGEventTapDisabledByUserInput; recovery requires calling
CGEventTapEnable again. QEMU 11.1.1 never does. handleTapEvent does not look
at the event type, and neither notification is a key event, so
+[NSEvent eventWithCGEvent:] returns nil and the event is reported as
unhandled. The shipped binary imports CGEventTapCreate and no other
CGEventTap symbol, so the tap simply stays dead for the session.

That explains the asymmetry. Our full-grab-focus patch routes ordinary
Command chords through key-window focus rather than the tap, so those keep
working; only the system shortcuts that need the HID tap to pre-empt macOS
depend on it, which is why Command-Space alone appeared to break.

Handle both notifications before the nil-returning conversion and re-enable
the tap. This is a recovery path, not a way to defeat the timeout: if the
callback is still too slow the window server disables the tap again.

The contract tests cover the guard, the re-enable call, the ordering against
+[NSEvent eventWithCGEvent:], and the patch order in the runtime build, since
this patch edits handleTapEvent after the full-grab patch rewrites it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@NimbleAINinja NimbleAINinja changed the title Recover the Cocoa full grab tap after macOS disables it [fix] Recover the Cocoa full grab tap after macOS disables it Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant