Skip to content

feat(v3): macOS global shortcuts land on the labelled key (layout-aware)#5688

Open
taliesin-ai wants to merge 1 commit into
masterfrom
feature/global-shortcuts-mac-layout-aware
Open

feat(v3): macOS global shortcuts land on the labelled key (layout-aware)#5688
taliesin-ai wants to merge 1 commit into
masterfrom
feature/global-shortcuts-mac-layout-aware

Conversation

@taliesin-ai

@taliesin-ai taliesin-ai commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #5686. Makes macOS global shortcuts keyboard-layout aware for letter accelerators.

Problem

Carbon's RegisterEventHotKey binds to a physical virtual key code, not a character. So Register("Cmd+A") bound to the QWERTY position of A regardless of the active layout — the key labelled Q on AZERTY. (#5686 documented this as a known limitation.)

Fix

Resolve letter accelerators against the active keyboard layout using UCKeyTranslate: scan the layout for the key that produces the requested letter unshifted, and bind that key code. Cmd+A now lands on the key labelled A on AZERTY/QWERTZ/Dvorak. If the active layout cannot produce the letter (e.g. Cyrillic/Greek), it falls back to the ANSI/QWERTY position so registration still succeeds.

Only letters are translated. They are produced unshifted on every Latin layout and never appear on the numeric keypad, so the reverse lookup is unambiguous. Digits, punctuation and named keys stay positional — translating digits would mis-bind to the numpad on layouts whose number row is shifted (e.g. AZERTY's main-row 1 is & unshifted, while the numpad 1 is 1).

Verification

Proved the reverse lookup against the real installed layouts:

Layout a q z w y
US QWERTY 0 12 6 13 16
French AZERTY 12 0 13 6 16
German QWERTZ 0 12 16 13 6
Dvorak 0 7 44 43 17

French swaps a↔q and z↔w; German swaps y↔z; Dvorak fully remaps — each matching the physical position of the labelled key. Combined with the end-to-end "registered key code fires" proof from #5686, the round trip holds: an AZERTY user pressing the key labelled A triggers Cmd+A.

Adds unit tests (global_shortcut_darwin_test.go), updates the docs macOS tab and the changelog. gofmt clean; full pkg/application test suite passes on macOS; Windows still cross-compiles.

Scope / follow-up

The binding is resolved at registration time. Switching keyboard layout while the app is running keeps existing letter shortcuts on their original physical key. Re-registering on a live layout change (observing kTISNotifySelectedKeyboardInputSourceChanged) is a deliberate, separable follow-up — kept out of this PR to keep it tight and because it is the harder part to test.

Summary by CodeRabbit

  • Bug Fixes

    • macOS global shortcuts now respect the active keyboard layout for letter keys, so shortcuts like Cmd+A follow the labeled key on layouts such as AZERTY, QWERTZ, and Dvorak.
    • Shortcut handling now keeps the original mapping after registration, even if the keyboard layout changes while the app is running.
  • Documentation

    • Updated the keyboard shortcuts documentation and changelog to reflect the new macOS behavior.

Carbon's RegisterEventHotKey binds to a physical virtual key code, not a
character, so a letter accelerator like "Cmd+A" previously bound to the
QWERTY position regardless of the active layout (the key labelled Q on
AZERTY).

Resolve letter accelerators against the active keyboard layout with
UCKeyTranslate: scan the layout for the key that produces the requested
letter unshifted and bind that key code. "Cmd+A" now lands on the key
labelled A on AZERTY, QWERTZ, Dvorak and similar layouts. If the active
layout cannot produce the letter (for example Cyrillic or Greek) we fall
back to the ANSI/QWERTY position so registration still succeeds.

Only letters are translated: they are produced unshifted on every Latin
layout and never appear on the numeric keypad, so the reverse lookup is
unambiguous. Digits, punctuation and named keys remain positional, because
translating digits would mis-bind to the numpad on layouts whose number row
is shifted.

The binding is resolved at registration time; re-registering on a live
layout change is a possible future enhancement.

Verified the reverse lookup against the real French (AZERTY), German
(QWERTZ) and Dvorak layouts. Adds unit tests and updates the docs and
changelog.
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 29efa8ca-7fa7-452f-8355-94a65bdc031e

📥 Commits

Reviewing files that changed from the base of the PR and between ee0fdbf and 0670f93.

📒 Files selected for processing (4)
  • docs/src/content/docs/features/keyboard/global-shortcuts.mdx
  • v3/UNRELEASED_CHANGELOG.md
  • v3/pkg/application/global_shortcut_darwin.go
  • v3/pkg/application/global_shortcut_darwin_test.go

Walkthrough

macOS global shortcut registration now resolves letter accelerators to virtual key codes using UCKeyTranslate against the active keyboard layout (with an ASCII-layout fallback), so Cmd+A lands on the labeled A key on AZERTY/QWERTZ/Dvorak. Non-letter keys retain fixed positional codes. Tests and docs are updated accordingly.

Changes

Layout-aware macOS global shortcuts

Layer / File(s) Summary
cgo UCKeyTranslate helper and macKeyCodeFor
v3/pkg/application/global_shortcut_darwin.go
Adds C functions using UCKeyTranslate (with ASCII-layout fallback) to resolve a lowercase letter to its virtual key code under the active keyboard layout. Introduces macKeyCodeFor in Go to route single letters through this path and other keys through the existing positional macKeyCodes table; updates the table comment.
Registration wired to macKeyCodeFor
v3/pkg/application/global_shortcut_darwin.go
macosGlobalShortcuts.register now calls macKeyCodeFor and returns its error instead of performing a direct macKeyCodes map lookup.
Tests for macKeyCodeFor
v3/pkg/application/global_shortcut_darwin_test.go
Darwin-only tests verify all 26 letters yield valid codes in [0,127], specific non-letter keys match expected positional codes, and unknown keys return an error.
Docs and changelog
docs/src/content/docs/features/keyboard/global-shortcuts.mdx, v3/UNRELEASED_CHANGELOG.md
Platform Considerations updated to explain layout-aware letter binding at registration time; changelog entry added.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • wailsapp/wails#5686: Introduced the macOS global shortcuts implementation (global_shortcut_darwin.go) that this PR directly extends with layout-aware key code resolution.

Suggested reviewers

  • leaanthony

🐇 A key by any name should ring the same,
AZERTY or Dvorak — the label's the game!
UCKeyTranslate hops through the layout with glee,*
binding each letter to where it should be.
The rabbit approves: press A, get A! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately summarizes the layout-aware macOS global shortcut change.
Description check ✅ Passed The description covers the change, problem, fix, verification, and follow-up issue, with only template checklist fields omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/global-shortcuts-mac-layout-aware

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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