Stable Keyboard Layout Switching in Windows 11 with AutoHotkey v2
In Windows 11, the standard Alt+Shift shortcut sometimes misses language switches during fast typing. After a system reboot, it works correctly, but over time the mechanism starts to fail. Switching via Win+Space remains stable, indicating delays in the standard hotkey handler's processing.
Common recommendations—disabling sticky keys, restarting ctfmon.exe, or reinstalling language packs—don't solve the problem in most cases.
Solution Based on AutoHotkey v2
Intercept the Alt+Shift combination via an AutoHotkey v2 script that directly calls Windows API to change the layout. This bypasses the built-in mechanism, ensuring predictable operation without misses.
Advantages of This Approach
- Fast triggering even with frequent presses.
- Protection against duplicate events.
- Works in active window threads (thread-specific layout).
An alternative is Punto Switcher with auto-switching, but for developers, manual control is preferable due to flexibility.
Script Setup
Install AutoHotkey v2. Create a file LangSwitch.ahk and insert the code:
#Requires AutoHotkey v2.0
#SingleInstance Force
; Switch between RU and EN with Alt+Shift (in any press order)
~*LAlt::TrySwitch()
~*RAlt::TrySwitch()
~*LShift::TrySwitch()
~*RShift::TrySwitch()
TrySwitch() {
static lastTick := 0
if !(GetKeyState("Alt", "P") && GetKeyState("Shift", "P"))
return
; Protection against double-triggering from simultaneous key events
now := A_TickCount
if (now - lastTick < 150)
return
lastTick := now
ToggleRuEn()
}
ToggleRuEn() {
static RU := 0x0419
static EN := 0x0409
hwnd := WinActive("A")
if !hwnd
return
; Current layout of the active window's thread
threadId := DllCall("GetWindowThreadProcessId", "Ptr", hwnd, "UInt*", 0, "UInt")
hkl := DllCall("GetKeyboardLayout", "UInt", threadId, "UPtr")
langId := hkl & 0xFFFF
target := (langId = RU) ? EN : RU
targetHKL := (target << 16) | target
; WM_INPUTLANGCHANGEREQUEST
DllCall("PostMessage", "Ptr", hwnd, "UInt", 0x50, "Ptr", 0, "Ptr", targetHKL)
}
Script Logic Breakdown
- Key Hooks:
~LAlt,~RAlt, etc., trigger on press without blocking (~) and in any order. - Combination Check:
GetKeyStateconfirms simultaneous Alt+Shift press. - Debounce: A 150 ms interval prevents multiple calls from key jitter.
- Getting Layout:
GetKeyboardLayoutfor the active window's threadId returns the HKL of the current thread. - Switching:
PostMessagewithWM_INPUTLANGCHANGEREQUEST (0x50)applies the target HKL.
Disabling the Standard Hotkey
Settings → Time & Language → Typing → Advanced keyboard settings → Input language hotkeys.
Select "Switch input language" → "Change key sequence" → "Not assigned".
Autostart and UAC
- Create a script shortcut in
shell:startup. - For UAC applications: run via
C:\Program Files\AutoHotkey\v2\AutoHotkey64_UIA.exe(uiAccess).
Requires AutoHotkey installed in Program Files.
Key Points
- The script uses thread-specific layout via
GetWindowThreadProcessIdandGetKeyboardLayout. PostMessage(0x50)ensures switching without window focus.- Debounce of 150 ms minimizes false triggers during fast input.
- UIAccess mode resolves administrator permission issues.
- RU/EN support: 0x0419 and 0x0409.
— Editorial Team
No comments yet.