🪲 Bugs
Open

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

WK
Win Kiativinyu·1 month ago

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