Repository navigation
Held-key check reads HID state and names a key it waits on #1335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| # A key nobody was pressing left remapping off with no explanation | ||
|
|
||
| ## Symptom (mini, 2026-10-20) | ||
|
|
||
| KeyPath started without its engine three times in one day (11:01, 14:09, | ||
| 14:18). Each start logged: | ||
|
|
||
| ``` | ||
| Managed Caps activation refused: physicalKeysHeld | ||
| Wake recovery gate: held-keys elapsedMs=0 | ||
| ``` | ||
|
|
||
| and nothing after that. Port 37001 refused connections for hours, and the only | ||
| visible message was "Remapping will resume when your keyboard session is ready." | ||
|
|
||
| ## Cause | ||
|
|
||
| `SessionCapsRuntimeSupport.requirePhysicalAllUp()` read | ||
| `CGEventSource.keyState(.combinedSessionState, …)`. It reported Space | ||
| (keycode 49) held while HID state did not: | ||
|
|
||
| ``` | ||
| combined: [49] hid: [] | ||
| ``` | ||
|
|
||
| No keyboard was holding Space. Pressing and releasing Space on the cl80 and | ||
| unplugging the fixture and the X2A changed nothing. One Space key-up posted at | ||
| the session tap (`kCGSessionEventTap`, from a combined-state source) cleared it | ||
| immediately. | ||
|
|
||
| The wake recovery loop polls the same check every 250 ms and logs only when the | ||
| blocker changes, so it waited silently for as long as the stale state lasted. | ||
|
|
||
| How the state diverged is not established. Posted events reach both records: a | ||
| key-down posted at the session tap from a private source set HID state as well, | ||
| and a key-up from a different source cleared both. That experiment did not | ||
| reproduce a combined-only press, and which program posted the original press is | ||
| unknown. | ||
|
|
||
| ## Fix | ||
|
|
||
| - The check reads `.hidSystemState`, which was clear throughout this incident. | ||
| It exists so a Caps Lock hidutil mapping never changes mid-press, and hidutil | ||
| acts on HID events. | ||
| - Because HID state can also be held by a posted press, the wake recovery loop | ||
| no longer waits silently: after 5 seconds on `held-keys` it logs the key codes | ||
| and shows "Remapping is waiting because macOS reports the “space” key as held | ||
| down. Press and release it to resume." | ||
|
|
||
| ## Clearing it by hand | ||
|
|
||
| Post one key-up for the stuck key code at the session tap, for example from | ||
| Python with `ctypes`: `CGEventSourceCreate(0)`, then | ||
| `CGEventCreateKeyboardEvent(src, 49, False)` and `CGEventPost(1, event)`. A | ||
| key-up on its own types nothing. Logging out also clears it. |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a key remains held for five seconds, this updates
lastWarningthroughonWarningbut never callsonStateChanged. The UI is event-driven—StatePublisherServiceemits a new snapshot only whennotifyStateChanged()is called—and the lifecycle warning callback inRuntimeCoordinatoronly assigns the value. Consequently, during the indefinite held-key wait the user continues seeing the generic warning, and a successful resume clears the named warning before the next state publication. TriggeronStateChangedafter replacing the warning so the new actionable message is actually shown.Useful? React with 👍 / 👎.