Skip to content

Record and send printable keys by character, resolved against the active keyboard layout - #21

Merged
DParent10 merged 2 commits into
mainfrom
fix/layout-aware-keys
Sep 17, 2026
Merged

DParent10 merged 2 commits into
mainfrom
fix/layout-aware-keys

Conversation

@DParent10

Copy link
Copy Markdown
Owner

What does this PR do?

Fixes the keyboard-layout half of #10 ("cut becomes quit on Dvorak").

Mappings stored a physical key code and recording used a QWERTY table keyed by key code, so on any non-QWERTY layout both halves were wrong: pressing the key that types x on Dvorak was recorded as b, and a mapping recorded as Cmd+X on QWERTY sent Cmd+Q on Dvorak.

  • New KeyboardLayout resolves a printable character to the key that produces it on the current input source (TISCopyCurrentKeyboardLayoutInputSource + UCKeyTranslate), including whether Shift is needed (digits on AZERTY). Cached per input source ID, rebuilt on layout change.
  • KeyStroke.canonicalKeyString now records printable keys by the character produced. Special keys (Return, arrows, F-keys, Delete…) still come from the key-code table; their characters are controls or private-use code points.
  • ButtonMapper resolves single-character keys through KeyboardLayout at send time and unions any required Shift into the event flags; falls back to the recorded key code, then the static table. Modifier-only keys and special keys are unaffected.

Existing profiles keep working: a stored "x" with keyCode 7 now resolves by the character first, so it types x on every layout.

Testing

Unit tests added (KeyboardLayoutTests): printable classification, capture preferring the character, and a live resolution on the current layout. Hardware check pending on Dvorak: existing Cmd+X mapping cuts rather than quits; recording a key while on Dvorak stores the right letter.

🤖 Generated with Claude Code

DParent10 and others added 2 commits September 17, 2026 11:48
…e active layout

Mappings stored a physical key code, and recording used a QWERTY table keyed
by key code, so on Dvorak (or any non-QWERTY layout) both halves were wrong:
pressing the key that types "x" was recorded as "b", and a mapping recorded
as Cmd+X on QWERTY sent Cmd+Q. Fixes the layout half of #10.

- KeyboardLayout resolves a printable character to the key (and whether Shift
  is needed) on the current input source via UCKeyTranslate, cached per source.
- canonicalKeyString records printable keys by the character produced; special
  keys (Return, arrows, F-keys) still use the key-code table.
- ButtonMapper resolves single-character keys through KeyboardLayout at send
  time and adds Shift when the layout requires it (digits on AZERTY), falling
  back to the recorded key code or the static table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
primaryKeyNames is built from a dictionary, so "left" vs "left arrow" is
nondeterministic. Assert the identifier resolves back to the key code instead.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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