8000 Fix Ctrl+C / Ctrl+V not working on non-Latin keyboard layouts by Vladislav-EG Β· Pull Request #220 Β· amirlehmam/wmux Β· GitHub
[go: up one dir, main page]

Skip to content

Fix Ctrl+C / Ctrl+V not working on non-Latin keyboard layouts - #220

Merged
amirlehmam merged 1 commit into
amirlehmam:masterfrom
Vladislav-EG:fix/non-latin-layout-copy-paste
Sep 5, 2026
Merged

amirlehmam merged 1 commit into
amirlehmam:masterfrom
Vladislav-EG:fix/non-latin-layout-copy-paste

Conversation

@Vladislav-EG
Copy link
Copy Markdown
Contributor

Hi! πŸ‘‹

First of all β€” thank you for wmux. I have been using it almost since the very first releases, and it is honestly the best thing I have found for Windows. It is open on my machine all day long.

There is one thing I have been waiting for in every single update: Ctrl+C simply does not work while my keyboard layout is set to Russian. I switch layouts dozens of times a day, and every time I want to copy something out of the terminal I first have to switch back to English. After waiting for a while I decided to just sit down and fix it myself.

And it is not only about Russian. The exact same thing happens on every non-Latin layout β€” Ukrainian, Bulgarian, Serbian, Greek, Hebrew, Arabic, Armenian, Georgian and others. So this small change helps quite a lot of people at once.

Thank you again for everything you put into this project β€” it is genuinely the best terminal experience on Windows. πŸ™

What goes wrong

The terminal key handler matches the shortcut against event.key:

if (event.type === 'keydown' && event.ctrlKey && event.key === 'c') {

event.key is the character the key produces with the current layout. On a Russian layout that physical key produces с β€” U+0441, Cyrillic "es" β€” and not c β€” U+0063, Latin "c". The two look identical on screen but they are different characters, so the condition is never true. The keystroke then falls through to the PTY as a plain ^C, which is why copying silently does nothing and the running command gets interrupted instead. Ctrl+V has the same problem.

Native Windows applications do not suffer from this because they match virtual-key codes, which are layout-independent. That is why Ctrl+C works everywhere else in the OS and this looks like a wmux-specific bug to the user.

The fix

event.code is the physical key and never changes with the layout: KeyC stays KeyC on Russian, Greek, Hebrew or anything else. Fixing all of them therefore does not need a list of languages β€” one check is enough.

Matching on event.code alone would be wrong, though. On Dvorak or Colemak the user presses the key that shows C, which is physically a different key. So event.key is still checked first, and event.code is only used as a fallback when the produced character is not a Latin letter:

export function isLetterKey(event: TerminalKeyEvent, letter: string, code: string): boolean {
  if (event.key === letter) return true;
  return event.code === code && !/^[a-z]$/i.test(event.key);
}
Layout event.key event.code Result
English (QWERTY) c KeyC matches on key β€” unchanged
German, Spanish, French c KeyC matches on key β€” unchanged
Russian, Greek, Hebrew, Arabic с, ψ, ב… KeyC matches on code β€” fixed
Dvorak / Colemak (key showing C) c KeyI matches on key β€” still works
Dvorak (physical KeyC, shows j) j KeyC Latin letter, no match β€” correct

The predicate lives in src/renderer/hooks/terminal-keys.ts, next to isShiftEnter, so it can be tested without spinning up Electron.

What is deliberately not changed

  • Ctrl+C with no selection still falls through to the PTY as ^C (SIGINT). Only the "there is a selection, so copy it" branch is affected, exactly as before.
  • Ctrl+Shift+C is unchanged on Latin layouts: Shift uppercases event.key, so it arrives as C, which is still a Latin letter and therefore never reaches this branch β€” it goes to the global copy shortcut as it always did. On a non-Latin layout it now lands in the terminal copy branch, which does the same thing the copy shortcut would have done; previously it did nothing at all there, because the global handler compares characters too.
  • The trimTrailingWhitespace handling from copy bugΒ #102 and the preventDefault that stops the double paste are kept as they were.

Tests

Added unit tests in tests/unit/terminal-keys.test.ts covering Latin, Cyrillic, Greek and both Dvorak directions.

Also verified manually on Windows 10 with Russian and English layouts:

  • Russian layout, text selected, Ctrl+C β†’ copies (was: nothing, and the command got interrupted)
  • Russian layout, nothing selected, Ctrl+C β†’ still interrupts the command
  • Russian layout, Ctrl+V β†’ pastes
  • English layout β†’ behaves exactly as before

npm test, npm run typecheck and npm run lint are all as green as they are on master. (The one failing test I see locally, pty-manager β†’ "finds a real file for pwsh", fails on a clean master checkout too β€” PowerShell 7 is simply not installed on this machine.)

A note for a possible follow-up

The same symbol-based matching lives in useKeyboardShortcuts.ts and key-remaps.ts, which means that on a non-Latin layout none of the letter shortcuts work, not just copy and paste. I kept this PR narrow on purpose, but I would be happy to send a follow-up with the same treatment if you think it is worth it.

@amirlehmam
amirlehmam merged commit c6c9d1c into amirlehmam:master Sep 5, 2026
@amirlehmam
Copy link
Copy Markdown
Owner

Merged β€” thank you, and thank you for the kind words about wmux. πŸ™

This is the right diagnosis and the right fix. The part I want to call out for anyone reading later is the bit that's easy to get wrong: matching on event.code alone would have "fixed" Cyrillic and quietly broken Dvorak and Colemak, where the user presses the key that shows C and that key is physically KeyI. Checking event.key first and falling back to code only when the produced character isn't a Latin letter is what makes this correct for both directions at once β€” and your table plus the two Dvorak test cases are what prove it, including the one that must NOT match (physical KeyC producing j).

The Ctrl+Shift+C reasoning is also correct and worth having written down: Shift uppercases event.key to C, which is still Latin, so it never enters this branch on a Latin layout and still reaches the global copy shortcut. On a non-Latin layout it now lands in the terminal copy branch and does the same thing the global shortcut would have β€” which is strictly better than the nothing it did before.

Shipping in v2.9.3.

On your follow-up offer: yes, please. useKeyboardShortcuts.ts and key-remaps.ts comparing characters means that on a Russian layout essentially the whole shortcut table is dead, not just copy/paste β€” which is a much bigger papercut than this one and the same predicate solves it. Two things worth keeping in mind when you get to it:

  1. key-remaps.ts is user-authored config ([keys] in config.toml), so whatever a user wrote there is a character they typed and expects to match by character. The code fallback has to be additive, never a reinterpretation of an existing binding.
  2. Index shortcuts (Ctrl+1-9) are a separate trap β€” wmux deliberately never reads a digit off e.key there because AZERTY produces &, Γ©, " on the unshifted number row. That path is already code-based; don't regress it into the letter predicate.

Keep it as narrow per-PR as you did here and it'll go in just as fast.

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.

2 participants

0