macos: fix Option+Arrow chords + modifier-state corruption in repeat-task cleanup#441
Open
tyvsmith wants to merge 2 commits into
Open
macos: fix Option+Arrow chords + modifier-state corruption in repeat-task cleanup#441tyvsmith wants to merge 2 commits into
tyvsmith wants to merge 2 commits into
Conversation
Hardware-generated arrow key events on macOS carry the NumericPad and SecondaryFn flags in addition to any user-pressed modifiers. CGEventTap- based hotkey matchers (tiling window managers, accessibility tools, etc.) commonly check those flags to disambiguate navigation arrows from generic chords, and reject events that lack them. Before this change, synthesized Option+Arrow chords were silently swallowed by the focused application instead of being captured by the window manager, because the events arrived with only the Alternate flag set. Hardware Option+Arrow chords on the local keyboard worked because the OS itself set the missing flags. Add NumericPad + SecondaryFn to the flags posted with arrow key events (Mac key codes 0x7B-0x7E) so synthesized arrow chords match hardware chords on the wire. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
spawn_repeat_task() takes a Mac CGKeyCode, but the cleanup block was passing that value to update_modifiers(), which expects a Linux evdev scancode (it calls scancode::Linux::try_from(key)). The two codespaces collide on several values, so cancelling the repeat task could silently clear a still-held modifier: Mac LeftShift = 56 == Linux KeyLeftAlt = 56 -> clears Mod1Mask Mac Down arrow = 125 == Linux KeyLeftMeta = 125 -> clears Mod4Mask Mac Up arrow = 126 == Linux KeyRightMeta = 126 -> clears Mod4Mask Mac Backslash = 42 == Linux KeyLeftShift = 42 -> clears ShiftMask Mac "9" = 29 == Linux KeyLeftCtrl = 29 -> clears ControlMask In practice this broke chords such as Shift+Option+X and Cmd+Down: pressing Shift while holding Option cancels Option's repeat task and runs the buggy cleanup, which then interprets Mac LeftShift's code (56) as Linux KeyLeftAlt and removes Option from the modifier state. The next key arrives with Shift only, so window-manager bindings on the original Option chord never fire. Remove the buggy update_modifiers() call. Modifier state is owned by the main consume() loop, which already calls update_modifiers() with the correct Linux scancode on the real release event. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Fixes two macOS input-emulation issues that affected modifier correctness and global hotkey recognition in CGEventTap-based tools (issue #440), by aligning synthesized events more closely with hardware-generated events and preventing accidental modifier-state mutation during repeat-task cleanup.
Changes:
- Stop calling
update_modifiers()in repeat-task cleanup (it expects Linux evdev scancodes, but the repeat task is keyed by macOSCGKeyCode). - Add detection for arrow-key macOS virtual keycodes and attach
NumericPad+SecondaryFnflags to synthesized arrow-key events.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Owner
|
Thanks for catching this! I've been wondering whats up with the arrow keys ... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #440.
Two related bugs in the macOS input-emulation backend, each addressed in its own commit so they can be reviewed and bisected independently.
Commit 1 —
macos: post NumericPad and SecondaryFn flags for synthesized arrow keysHardware-generated arrow key
CGEvents on macOS carrykCGEventFlagMaskNumericPadandkCGEventFlagMaskSecondaryFnin addition to any user-pressed modifiers. CGEventTap-based hotkey matchers (tiling window managers, accessibility tools) often check for those flags to disambiguate navigation keys from generic chords. Synthesized arrow chords without them fall through to the focused app instead of being captured by the WM.Fix: set both flags on arrow key events (Mac key codes
0x7B–0x7E).Commit 2 —
macos: stop corrupting modifier state in repeat-task cleanupspawn_repeat_task()takes a MacCGKeyCode, but the cleanup block was passing that value toupdate_modifiers(), which expects a Linux evdev scancode (it callsscancode::Linux::try_from(key)). The two codespaces collide on several values, silently clearing still-held modifiers when the repeat task is cancelled by a follow-up key:This made
Shift+Option+Xarrive asShift+XandCmd+Up/Cmd+Downarrive withoutCmd— see issue #440 for the full trace.Fix: remove the buggy
update_modifierscall. The mainconsume()loop already updates modifier state with the correct Linux scancode on the real release event.Verification
cargo build --workspaceandcargo bundle --releaseon macOS 26 (Sequoia, arm64)cargo clippy --workspace --all-targets --all-features: cleancargo fmt --check: cleancargo test --workspace: passes (no unit tests for the macOS backend;AGENTS.mdsays "OS-specific backends: test via GTK/CLI on target OS or document manual verification")Notes / follow-ups deliberately left out of this PR
SecondaryFnon hardware. They aren't part of the reported symptom, but a follow-up could extend the same pattern.NumericPad. Same situation.KeyUpfor the cancelled key even when the user hasn't released it (because a different key was pressed). This is independently questionable behavior but doesn't visibly misbehave once the codespace bug is fixed, so left alone.