Fix physical location of Undo (Ctrl-Z) and Redo (Ctrl-Y) shortcuts between German and Russian keyboard layouts in Windows

I am mimicking another victim of keyboard layout chaos, which suffers from similar yet slightly different issue.

I am using German and Russian keyboard layouts extensively on my Windows 10 workstation and switch often between them. The problem is related to the fact that letters Y and Z are switched in the German keyboard layout comparing to the standard English QWERTY layout.

If I use German layout, the Undo shortcut (Ctrl-Z) is binded to the key Z, which is located in the middle of the first letter row between T and U. The Redo (Ctrl-Y) is binded to the key Y, which is located left to X in the low row of letters on the German QWERTZ layout.

If you switch the layout to the Russian, suddenly an evil thing is happening. Undo and Redo shortcuts mutually change their position on the keyboard. Undo (Ctrl-Z) is located now on Y (lower row, left to X) and the Redo on Z (upper row, between T and U). This is location is identical to the location of Ctrl-Z and Ctrl-Y on the standard English keyboard layout.

That means that the location of Undo and Redo shortcuts is changing to the opposite all the time I switch the keyboard layout. As you can imagine I am always pressing the wrong shortcut, because it is impossible to learn two osciallating opposite patterns, at least my spine brain protests. Since I use Undo and Redo shortcuts very often, this behavior irritates me a lot.

I want to fix the physical location (keys) of the Redo and Undo shortcuts in the whole set of Windows applications either to German or better English layout.

Using English layout permanently for all kind of latin based texts although fixes the issue with jumping Undo/Redo shortcuts, but is not an option for me, because I need to type German texts with all umlauts and ß (do not suggest an international layout to enter accents, because German is my primary language and I want to type its native letters with one keystroke).

As the colleague in the referenced question already mentioned, there is no obvious answer in the Internet. The same problem is solved on Ubuntu by creating a custom keyboard layout. I cannot find any information about creating custom layout for Windows or patching the current one.

This behavior is consistent among all version of Windows I know and related deeply in the initial keyboard layout designs, which are just incompatible for users happen to use German-Russian language pair like me.

Good solution shall be implemented in Windows directly without any additional software, or only free open source software. Working solutions involving AutoHotKey are welcome.

With the help of @miroxlav I came up with this half-working AHK script:

; Undo
$^z:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4070409) ; revert undo-redo hotkey mapping if in German layout Send ^y Else Send ^н ; pass the keystroke through
Return
; Redo
$^y:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4070409) ; revert undo-redo hotkey mapping if in German layout Send ^z Else Send ^я ; pass the keystroke through
Return

This script perfectly remaps Ctrl-Y <-> Ctrl-Z on German layout, but corrupts both hotkeys on Russian layout. I use ^н and ^я with the hope to emulate Ctrl-Y or Ctrl-Z scan-code equivalents for Russian layout, but it does not work (generates н for Ctrl-Y and nothing visible for Ctrl-Z). Using ^z and ^y for Russian does not work either, generating letters z and y respectively.

Different to Miroxlav answer I had to use keyboard hook ($) to avoid looping and remove labels (probably copy paste mistake)

Solution suggested by Williams:

^SC02c::Send ^y
^SC015::Send ^z

unfortunately does not work either. It generates plain letters 'y' and 'z' on Russian layout and no change on German layout (Ctrl-Z and Ctrl-Y passed through in the original mapping).

Still looking for the solution.

4

3 Answers

You may simply create your own keyboard layout using Microsoft Keyboard Layout Creator 1.4 from Official Microsoft Download Center.

Youtube provides a plenty of video tutorials how to use it (like this with Russian locale) - just search for its name. But basically, you clone one of existing keyboard layouts and modify it according to your needs.

AutoHotKey solution:

(note: replace 0x4090409 constant (US keyboard) with value which applies to your layout)

$^z:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4090409) Send ^z Else Send ^y
Return
$^y:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4090409) Send ^y Else Send ^z
Return

Hook ($) is needed to avoid recursion, i.e. Send ^z or Send ^y invoking just another AHK macro.

I tested that keyboard detection condition, it worked nicely.

Of course, you can place keyboard detection into function and therefore optimize the code etc. I was lazy to do that. :)

9

I found finally a working solution for me (AutoHotKey script):

; Undo Ctrl-Z
^sc02C:: Send, ^{sc015}
Return
; Redo Ctrl-Y
^sc015:: Send, ^{sc02C}
Return

It perfectly keeps undo key (Ctrl-Z) on its logical place - low left row - independently from the keyboard layout.

Thank you @Miroxlav and and @Williams for bringing pieces for the solution.

Despite the fact that the accepted answer works, it only works if the selected layout at the time was the German layout, otherwise it flips the undo function in all other layouts.

To solve this you just have to replace the US code 0x4090409 with the German code 0x4070407. Also you have change the if statement like this:

$^z:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4070407) Send ^y Else Send ^z
Return
$^y:: hWnd := WinExist("A") ThreadID := DllCall("GetWindowThreadProcessId", "UInt", hWnd, "UInt", 0) hKL := DllCall("GetKeyboardLayout", "UInt", ThreadID, "UInt") If (hKL = 0x4070407) Send ^z Else Send ^y
Return

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like