Keyboard shortcuts fail with non-English layouts on Windows - temporary AutoHotkey workaround available
On Windows, many Capacities keyboard shortcuts do not work correctly when a non-English keyboard layout is active.
For example, with the Russian layout enabled, shortcuts such as Ctrl+A, Ctrl+C, Ctrl+V and other Ctrl+letter combinations may fail. Switching to English makes the same physical key combinations work again.
This makes working in Cyrillic unnecessarily frustrating because users have to switch to English just to use shortcuts.
Expected behavior
Keyboard shortcuts should work independently of the active keyboard layout and should use the physical key / layout-independent key code.
Temporary workaround
I created an AutoHotkey v1 workaround for Capacities.exe.
It temporarily switches Capacities to US English when a Ctrl-based shortcut is pressed, forwards the shortcut, and restores the previous layout afterwards.
The workaround uses physical key positions and is active only inside Capacities.
Environment
Windows desktop app
Russian + US English keyboard layouts
Suggested fix
Please handle shortcuts using layout-independent physical key codes, for example KeyboardEvent.code or an equivalent mechanism.
Script:
#Requires AutoHotkey v1.1.33+
#NoEnv
#SingleInstance Force
#Persistent
#InstallKeybdHook
#MaxThreadsPerHotkey 1
#MaxHotkeysPerInterval 300
SetBatchLines, -1
SetKeyDelay, 10, 10
SetTitleMatchMode, 2
SendMode, Event
Saved := ""
English := DllCall("LoadKeyboardLayout", "Str", "00000409", "UInt", 0, "Ptr")
if (!English)
ExitApp, 2
Menu, Tray, Tip, Capacities keyboard layout fix
OnExit("Cleanup")
; Scan codes identify physical keys independently of RU/EN layout.
Hotkey, If, ShouldHandle()
for _, sc in StrSplit("002 003 004 005 006 007 008 009 00A 00B 00C 00D 010 011 012 013 014 015 016 017 018 019 01A 01B 01E 01F 020 021 022 023 024 025 026 027 028 029 02B 02C 02D 02E 02F 030 031 032 033 034 035", " ")
Hotkey, % "$*sc" sc, ForwardShortcut
Hotkey, If
SetTimer, WatchContext, 20
return
#If ShouldHandle()
#If
ForwardShortcut:
Critical
sc := SubStr(A_ThisHotkey, 3)
Forward(sc)
return
WatchContext:
if (IsObject(Saved) && (!WinActive("ahk_id " Saved.root) || (!GetKeyState("LControl", "P") && !GetKeyState("RControl", "P"))))
Restore()
return
ShouldHandle() {
return WinActive("ahk_exe Capacities.exe") && GetKeyState("Ctrl", "P") && !GetKeyState("Alt", "P") && !GetKeyState("LWin", "P") && !GetKeyState("RWin", "P")
}
GetTarget(root) {
tid := DllCall("GetWindowThreadProcessId", "Ptr", root, "Ptr", 0, "UInt")
VarSetCapacity(info, 8 + 6*A_PtrSize + 16, 0)
NumPut(VarSetCapacity(info), info, 0, "UInt")
focus := 0
if DllCall("GetGUIThreadInfo", "UInt", tid, "Ptr", &info)
focus := NumGet(info, 8 + A_PtrSize, "Ptr")
if (!focus)
focus := root
tid := DllCall("GetWindowThreadProcessId", "Ptr", focus, "Ptr", 0, "UInt")
return {root: root, hwnd: focus, tid: tid, layout: DllCall("GetKeyboardLayout", "UInt", tid, "Ptr")}
}
ChangeLayout(target, layout) {
if (!DllCall("IsWindow", "Ptr", target.hwnd))
return false
DllCall("PostMessage", "Ptr", target.hwnd, "UInt", 0x50, "Ptr", 0, "Ptr", layout)
Loop, 30 {
if (DllCall("GetKeyboardLayout", "UInt", target.tid, "Ptr") = layout)
return true
Sleep, 5
}
return false
}
Forward(sc) {
global Saved, English
root := WinExist("A")
if (IsObject(Saved) && Saved.root != root)
Restore()
target := GetTarget(root)
if (!IsObject(Saved) && target.layout != English) {
Saved := target
if (!ChangeLayout(target, English)) {
Restore()
SoundBeep, 600, 80
return false
}
}
if (!WinActive("ahk_id " root)) {
Restore()
return false
}
; Preserve Ctrl and Shift; use the English VK for the physical key.
vk := DllCall("MapVirtualKeyEx", "UInt", "0x" SubStr(sc, 3), "UInt", 3, "Ptr", English, "UInt")
key := "{Blind}{vk" Format("{:02X}", vk) sc "}"
SendEvent, % key
return true
}
Restore() {
global Saved, English
if (!IsObject(Saved))
return
target := Saved
Saved := ""
; Do not undo a layout deliberately changed by the user.
if (DllCall("GetKeyboardLayout", "UInt", target.tid, "Ptr") = English)
if (!ChangeLayout(target, target.layout) && DllCall("IsWindow", "Ptr", target.hwnd))
Saved := target
}
Cleanup(reason, code) {
Restore()
}
1 Comment
Sign in to comment

Alexey P Thanks a ton! It works well for me. Saving a lot of frustration & time switching between TH/EN keyboard layout.
Steffen Bleher Is there practical reasons Capacities not supporting this already? Request dating back to 2022 ...
https://feedback.capacities.io/b/1v35e680/feature-ideas/bug-shortcuts-dont-work-with-all-keyboard-layouts
https://feedback.capacities.io/b/1v35e680/feature-ideas/enable-shortcuts-on-non-english-keyboard-layouts