NeuroAgent

Fix AHK Script for Keyboard Layout Switching in System Windows

Solution for fixing AHK keyboard layout switching script in Windows system dialogs. Learn why SendMessage doesn't work and how to fix the script to work in file save dialogs and other system windows.

Question

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.

NeuroAgent

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

SendMessage with code 0x50 (WM_INPUTLANGCHANGEREQUEST) doesn’t work in system dialogs for several reasons:

  1. Special message processing: Windows system dialogs (file save, search, properties, etc.) have their own input handling mechanism that may ignore standard layout switching messages.

  2. Security restrictions: Many system windows run in elevated security contexts and don’t accept messages from external applications.

  3. 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.

autohotkey
~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.

autohotkey
~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:

autohotkey
~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:

autohotkey
~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:

autohotkey
~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

autohotkey
#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

autohotkey
~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:

  1. Optimized for different window types: Uses SendMessage for regular windows and IMM method for system dialogs
  2. Minimal delay: Doesn’t open additional dialogs like some other methods
  3. Compatibility: Works on all Windows 10 versions
  4. 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:

  1. Test your script: Check operation in different system dialogs (save, open, search)
  2. Update AutoHotkey: Use the latest version of AHK (v2.0+)
  3. Add visual feedback: You can add a sound signal or system notification when switching
  4. 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

  1. AutoHotkey Community - Switching global keyboard layout
  2. AutoHotkey Community - The keyboard layout set by the SendMessage 0x50 message on Explorer is not persistent
  3. Super User - How do I send a keyboard layout switch event with autohotkey in Windows 7?
  4. AutoHotkey Documentation - Send()
  5. AutoHotkey Community - Replicating the behavior of hotkeys used to switch keyboard layout
  6. AutoHotkey Community - Get/Set input/keyboard layout in Console/UWP/Steam/Dialog windows
  7. 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:

  1. Combined approach: Use SendMessage for regular windows and IMM method for system dialogs
  2. Window type checking: Automatically determine if a window is a system dialog
  3. 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.