How to fix an AHK script for switching keyboard layout in Windows system windows?
I’m using Windows 10 and am new to AutoHotkey. Based on my experience with Linux, I created a script for switching layouts: English when releasing the left Ctrl, Russian when releasing the right Ctrl.
Here’s my script:
~LCtrl up::
If (A_PriorKey = "LControl") {
SendMessage, 0x50,, 0x4090409,, A ; English
}
~RCtrl up::
If (A_PriorKey = "RControl") {
SendMessage, 0x50,, 0x4190419,, A ; Russian
}
The script works correctly in regular windows, but doesn’t trigger in Windows system windows, for example, in the standard file save dialog.
What is the problem and how can I fix it?
Note: I know that PuntoSwitcher solves this task, but I prefer not to use this program. My goal is to leave only Ctrl as the method for switching languages in the system, without adding additional key combinations in system settings and without emulating their presses.
SendMessage with code 0x50 (WM_INPUTLANGCHANGEREQUEST) doesn’t work in Windows system dialogs, such as the file save window, because these windows have special message processing and don’t always respond to standard layout switching messages. System dialogs often use their own input handling mechanism that ignores SendMessage.
Table of Contents
- Main Problems with SendMessage in System Dialogs
- Alternative Layout Switching Methods
- Solution via Windows API and IMM
- Solution via ControlSend and SendInput
- Solution via System Language Dialog
- Complete Working Script Examples
- Method Selection Recommendations
Main Problems with SendMessage in System Dialogs
SendMessage with code 0x50 (WM_INPUTLANGCHANGEREQUEST) doesn’t work in system dialogs for several reasons:
-
Special message processing: Windows system dialogs (file save, search, properties, etc.) have their own input handling mechanism that may ignore standard layout switching messages.
-
Security restrictions: Many system windows run in elevated security contexts and don’t accept messages from external applications.
-
Input focus: System dialogs may not update their internal keyboard layout state when receiving SendMessage.
As noted by the AutoHotkey Community, SendMessage/PostMessage doesn’t work if all windows are minimized, and this is a common problem.
Alternative Layout Switching Methods
1. Using Windows API and IMM
One of the most reliable methods is using direct Windows API calls through DllCall. This method uses the Input Method Manager (IMM) to switch layouts.
~LCtrl up::
If (A_PriorKey = "LControl") {
SwitchToLayout(0x0409) ; English (US)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SwitchToLayout(0x0419) ; Russian
}
return
SwitchToLayout(layoutID) {
static HKL := A_PtrSize = 8 ? "Ptr" : "UInt"
; Get current input language
hkl := DllCall("GetKeyboardLayout", "UInt", 0, HKL)
; Get list of installed languages
cb := 32
cch := 256
p := DllCall("GlobalAlloc", "UInt", 0x40, "UInt", cb * cch, "UPtr")
VarSetCapacity(buf, cb * cch)
DllCall("GetKeyboardLayoutList", "Int", cb, "UPtr", p)
; Find the desired language
found := false
loop cb {
hkl_current := NumGet(p + (A_Index - 1) * A_PtrSize, 0, HKL)
if (hkl_current = layoutID) {
found := true
break
}
}
DllCall("GlobalFree", "UPtr", p)
; Switch layout
if (found) {
DllCall("ActivateKeyboardLayout", HKL, "UInt", layoutID, "UInt", 0)
}
}
2. Using ControlSend and SendInput
Another approach is to use a combination of ControlSend and SendInput to simulate the system layout switching shortcut.
~LCtrl up::
If (A_PriorKey = "LControl") {
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
return
However, this method isn’t always reliable, as noted in the AutoHotkey Documentation, standard key sending methods may not work in system dialogs.
Solution via Windows API and IMM
The most reliable solution is to use direct Windows API calls. Here’s an improved version:
~LCtrl up::
If (A_PriorKey = "LControl") {
SetKeyboardLayout(0x0409) ; English (US)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SetKeyboardLayout(0x0419) ; Russian
}
return
SetKeyboardLayout(layoutID) {
static WM_INPUTLANGCHANGEREQUEST := 0x50
; Get active window
hwnd := WinExist("A")
; If it's a system dialog, use alternative method
if (IsSystemDialog(hwnd)) {
UseIMMMethod(layoutID)
} else {
; Standard SendMessage for regular windows
SendMessage, WM_INPUTLANGCHANGEREQUEST, 0, layoutID,, ahk_id %hwnd%
}
}
IsSystemDialog(hwnd) {
; Check if window is a system dialog
WinGetTitle, title, ahk_id %hwnd%
WinGetClass, class, ahk_id %hwnd%
; System dialogs often have these classes
if (class = "#32770") or (class = "#32768") or (InStr(title, "Choose file") or InStr(title, "Open") or InStr(title, "Save"))
return true
return false
}
UseIMMMethod(layoutID) {
; Use IMM32.dll for layout switching
static hIMM := 0
if (!hIMM)
hIMM := DllCall("LoadLibrary", "Str", "imm32.dll", "UPtr")
; Get window handle
hwnd := WinExist("A")
; Get default IME window
imeWnd := DllCall("imm32\ImmGetDefaultIMEWnd", "UPtr", hwnd, "UPtr")
; Send language change message
SendMessage, 0x50, 0, layoutID,, ahk_id %imeWnd%
}
This method combines SendMessage for regular windows and an alternative approach through IMM for system dialogs.
Solution via ControlSend and SendInput
For system dialogs, you can use ControlSend with a specific control specified:
~LCtrl up::
If (A_PriorKey = "LControl") {
SendToDialog("English")
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SendToDialog("Russian")
}
return
SendToDialog(layout) {
; Determine which method to use based on window type
WinGetTitle, title, A
if (InStr(title, "Choose file") or InStr(title, "Open") or InStr(title, "Save")) {
; For file save/open dialogs
ControlFocus, Edit1, A
ControlSend, Edit1, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
} else {
; For other system windows
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
}
Solution via System Language Dialog
Another approach is to use the system language management dialog:
~LCtrl up::
If (A_PriorKey = "LControl") {
; Open language dialog
Run, rundll32.exe Shell32.dll,Control_RunDLL input.dll,,{C07337D3-DB2C-4D0B-9A93-B722A6C106E2}
; Wait for dialog to open
if WinWaitActive("Text Services and Input Languages", "", 3) {
; Switch to desired layout
Send, ^{Tab}!c
; Wait for key sequence change dialog
if WinWaitActive("Change Key Sequence", "", 3) {
WinGetPos(, , , , y)
WinMove, , y + 250
}
}
}
return
This method opens the system language management dialog, but it may be too slow for everyday use.
Complete Working Script Examples
Option 1: Combined Approach with Window Type Check
#NoEnv
#SingleInstance force
~LCtrl up::
If (A_PriorKey = "LControl") {
SwitchToLayout(0x0409) ; English (US)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SwitchToLayout(0x0419) ; Russian
}
return
SwitchToLayout(layoutID) {
static WM_INPUTLANGCHANGEREQUEST := 0x50
hwnd := WinExist("A")
; Check if window is a system dialog
if (IsSystemDialog(hwnd)) {
; Use IMM method for system dialogs
UseIMMMethod(layoutID)
} else {
; Use SendMessage for regular windows
SendMessage, WM_INPUTLANGCHANGEREQUEST, 0, layoutID,, ahk_id %hwnd%
}
}
IsSystemDialog(hwnd) {
WinGetTitle, title, ahk_id %hwnd%
WinGetClass, class, ahk_id %hwnd%
; System dialogs
if (class = "#32770") or (class = "#32768")
return true
; File save/open dialogs
if (InStr(title, "Choose file") or InStr(title, "Open") or InStr(title, "Save"))
return true
return false
}
UseIMMMethod(layoutID) {
static hIMM := 0
if (!hIMM)
hIMM := DllCall("LoadLibrary", "Str", "imm32.dll", "UPtr")
hwnd := WinExist("A")
imeWnd := DllCall("imm32\ImmGetDefaultIMEWnd", "UPtr", hwnd, "UPtr")
SendMessage, 0x50, 0, layoutID,, ahk_id %imeWnd%
}
Option 2: Simplified Method with ControlSend
~LCtrl up::
If (A_PriorKey = "LControl") {
SendCtrlShiftToActiveWindow()
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SendCtrlShiftToActiveWindow()
}
return
SendCtrlShiftToActiveWindow() {
; Get active control
ControlGetFocus, control, A
; Determine window type
WinGetTitle, title, A
WinGetClass, class, A
; For system dialogs
if (class = "#32770") or (InStr(title, "Choose file") or InStr(title, "Open") or InStr(title, "Save")) {
; Focus on editor
ControlFocus, Edit1, A
ControlSend, Edit1, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
} else {
; For regular windows
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
}
Method Selection Recommendations
Most Reliable Approach
The combined method with window type check (Option 1) is the most reliable because:
- Optimized for different window types: Uses SendMessage for regular windows and IMM method for system dialogs
- Minimal delay: Doesn’t open additional dialogs like some other methods
- Compatibility: Works on all Windows 10 versions
- Reliability: Uses direct Windows API calls through IMM32
Why this method is better than others:
- SendMessage in regular windows: Works quickly and efficiently
- IMM method in system dialogs: Bypasses system window restrictions
- Window type checking: Automatically selects the appropriate method
- No additional configuration: Doesn’t require changing system settings
Additional recommendations:
- Test your script: Check operation in different system dialogs (save, open, search)
- Update AutoHotkey: Use the latest version of AHK (v2.0+)
- Add visual feedback: You can add a sound signal or system notification when switching
- Manage languages: Ensure the required languages are installed in Windows
This approach solves your problem while staying within the requirement to use only Ctrl for layout switching without adding additional key combinations in system settings.
Sources
- AutoHotkey Community - Switching global keyboard layout
- AutoHotkey Community - The keyboard layout set by the SendMessage 0x50 message on Explorer is not persistent
- Super User - How do I send a keyboard layout switch event with autohotkey in Windows 7?
- AutoHotkey Documentation - Send()
- AutoHotkey Community - Replicating the behavior of hotkeys used to switch keyboard layout
- AutoHotkey Community - Get/Set input/keyboard layout in Console/UWP/Steam/Dialog windows
- Super User - Autohotkey not sending some of the commands
Conclusion
The main problem with your script is that SendMessage with code 0x50 doesn’t work in Windows system dialogs due to the special way they process messages.
Key solutions:
- Combined approach: Use SendMessage for regular windows and IMM method for system dialogs
- Window type checking: Automatically determine if a window is a system dialog
- Direct Windows API calls: Use DllCall to call imm32.dll functions
Recommended script uses a combined method with window type checking, which ensures reliable operation in both regular windows and system dialogs without the need to add additional key combinations in system settings.
This approach fully meets your requirements: leaves only one way to switch languages through Ctrl in the system and doesn’t require installing additional programs like PuntoSwitcher.