Skip to content

Add a session event tap as a fallback for cmd-cmd chord detection#9

Merged
peterp merged 1 commit intomainfrom
feat/chord-event-tap
Apr 28, 2026
Merged

Add a session event tap as a fallback for cmd-cmd chord detection#9
peterp merged 1 commit intomainfrom
feat/chord-event-tap

Conversation

@peterp
Copy link
Copy Markdown
Owner

@peterp peterp commented Apr 28, 2026

`NSEvent` global/local monitors can miss flag-change events under various conditions (other apps consuming modifiers, accessibility permission just-granted, key-remapping software). Add a `CGEventTap` on `.cgSessionEventTap` in passive `.listenOnly` mode as a second source for chord detection.

  • Runs alongside the existing NSEvent monitors, not replacing them
  • `.listenOnly` so it can never consume or block events
  • Dispatches to main `async` (never `sync`) — no deadlock risk
  • Both sources feed the same `handleFlags` state machine; the `fired` flag dedupes when they race
  • Tap is gracefully skipped (logged "unavailable") if Accessibility permission isn't granted

Scope reduced from #1: the in-overlay key event tap from the original PR is not included here. The overlay window already becomes key and the existing NSResponder path captures keys correctly. That tap was needed only for the minimal-mode panel which isn't part of this slice.

Credit to @plyght — adapted from #1.

Test plan

  • `./build-app.sh && open cmdcmd.app`
  • ⌘⌘ chord still triggers the overlay
  • Pressing left ⌘ alone, then any letter, then right ⌘ does not trigger (contamination still works)
  • Releasing both ⌘ resets state — repeat chord works
  • Console: no "cmd-cmd event tap unavailable" once Accessibility is granted
  • Revoke Accessibility temporarily → app logs "unavailable" and falls back to NSEvent monitors

🤖 Generated with Claude Code

NSEvent global/local monitors can miss flag-change events when other
apps consume modifiers, when accessibility permission was just granted,
or under certain key-remapping setups. Add a CGEventTap on the session
in passive .listenOnly mode (alongside the existing NSEvent monitors)
so the chord state machine sees flag changes from a second source.

The tap dispatches to main async, never consumes events, and re-uses
the same handleFlags state machine. The 'fired' flag dedupes when both
sources race the same chord. Falls back silently if the tap can't be
created (no Accessibility permission yet).

Skips the in-overlay key tap from the original PR — the overlay window
becomes key and the existing NSResponder path works there.

Co-Authored-By: plyght <plyght@peril.lol>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@peterp peterp merged commit bd291a7 into main Apr 28, 2026
1 check passed
@peterp peterp mentioned this pull request Apr 28, 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