Как исправить AHK-скрипт для переключения раскладки клавиатуры в системных окнах Windows?
Я использую Windows 10 и впервые работаю с AutoHotkey. На основе опыта работы с Linux я создал скрипт для переключения раскладки: английская при отпускании левого Ctrl, русская при отпускании правого Ctrl.
Вот мой скрипт:
~LCtrl up::
If (A_PriorKey = "LControl") {
SendMessage, 0x50,, 0x4090409,, A ; английский
}
~RCtrl up::
If (A_PriorKey = "RControl") {
SendMessage, 0x50,, 0x4190419,, A ; русский
}
Скрипт работает корректно в обычных окнах, но не срабатывает в системных окнах Windows, например, в стандартном диалоговом окне сохранения файла.
В чем заключается проблема и как ее исправить?
Примечание: Я знаю, что PuntoSwitcher решает эту задачу, но предпочитаю не использовать эту программу. Моя цель - оставить в системе единственный способ переключения языков через Ctrl, без добавления дополнительных комбинаций клавиш в системных настройках и эмуляции их нажатия.
SendMessage с кодом 0x50 (WM_INPUTLANGCHANGEREQUEST) не работает в системных диалогах Windows, таких как окно сохранения файла, потому что эти окна имеют особую обработку сообщений и не всегда реагируют на стандартные сообщения переключения раскладки. Системные диалоги часто используют собственный механизм обработки ввода, который игнорирует SendMessage.
Содержание
- Основные проблемы SendMessage в системных диалогах
- Альтернативные методы переключения раскладки
- Решение через Windows API и IMM
- Решение через ControlSend и SendInput
- Решение через системный диалог языков
- Полные примеры рабочих скриптов
- Рекомендации по выбору метода
Основные проблемы SendMessage в системных диалогах
SendMessage с кодом 0x50 (WM_INPUTLANGCHANGEREQUEST) не работает в системных диалогах по нескольким причинам:
-
Особая обработка сообщений: Системные диалоги Windows (сохранение файла, поиск, свойства и т.д.) имеют собственный механизм обработки ввода, который может игнорировать стандартные сообщения переключения раскладки.
-
Ограничения безопасности: Многие системные окна работают в повышенном контексте безопасности и не принимают сообщения от внешних приложений.
-
Фокус ввода: Системные диалоги могут не обновлять свое внутреннее состояние раскладки клавиатуры при получении SendMessage.
Как указывает AutoHotkey Community, SendMessage/PostMessage не работает, если все окна минимизированы, и это распространенная проблема.
Альтернативные методы переключения раскладки
1. Использование Windows API и IMM
Один из самых надежных методов - использование прямых вызовов Windows API через DllCall. Этот метод использует Input Method Manager (IMM) для переключения раскладки.
~LCtrl up::
If (A_PriorKey = "LControl") {
SwitchToLayout(0x0409) ; Английский (США)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SwitchToLayout(0x0419) ; Русский
}
return
SwitchToLayout(layoutID) {
static HKL := A_PtrSize = 8 ? "Ptr" : "UInt"
; Получаем текущий язык ввода
hkl := DllCall("GetKeyboardLayout", "UInt", 0, HKL)
; Получаем список установленных языков
cb := 32
cch := 256
p := DllCall("GlobalAlloc", "UInt", 0x40, "UInt", cb * cch, "UPtr")
VarSetCapacity(buf, cb * cch)
DllCall("GetKeyboardLayoutList", "Int", cb, "UPtr", p)
; Ищем нужный язык
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)
; Переключаем раскладку
if (found) {
DllCall("ActivateKeyboardLayout", HKL, "UInt", layoutID, "UInt", 0)
}
}
2. Использование ControlSend и SendInput
Другой подход - использовать комбинацию ControlSend и SendInput для имитации системной комбинации переключения раскладки.
~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
Однако этот метод не всегда надежен, так как как отмечает AutoHotkey Documentation, стандартные методы отправки клавиш могут не работать в системных диалогах.
Решение через Windows API и IMM
Наиболее надежным решением является использование прямых вызовов Windows API. Вот улучшенный вариант:
~LCtrl up::
If (A_PriorKey = "LControl") {
SetKeyboardLayout(0x0409) ; Английский (США)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SetKeyboardLayout(0x0419) ; Русский
}
return
SetKeyboardLayout(layoutID) {
static WM_INPUTLANGCHANGEREQUEST := 0x50
; Получаем активное окно
hwnd := WinExist("A")
; Если это системный диалог, используем альтернативный метод
if (IsSystemDialog(hwnd)) {
UseIMMMethod(layoutID)
} else {
; Стандартный SendMessage для обычных окон
SendMessage, WM_INPUTLANGCHANGEREQUEST, 0, layoutID,, ahk_id %hwnd%
}
}
IsSystemDialog(hwnd) {
; Проверяем, является ли окно системным диалогом
WinGetTitle, title, ahk_id %hwnd%
WinGetClass, class, ahk_id %hwnd%
; Системные диалоги часто имеют эти классы
if (class = "#32770") or (class = "#32768") or (InStr(title, "Выберите файл") or InStr(title, "Открыть") or InStr(title, "Сохранить"))
return true
return false
}
UseIMMMethod(layoutID) {
; Используем IMM32.dll для переключения раскладки
static hIMM := 0
if (!hIMM)
hIMM := DllCall("LoadLibrary", "Str", "imm32.dll", "UPtr")
; Получаем дескриптор окна
hwnd := WinExist("A")
; Получаем стандартное окно IME
imeWnd := DllCall("imm32\ImmGetDefaultIMEWnd", "UPtr", hwnd, "UPtr")
; Отправляем сообщение о смене языка
SendMessage, 0x50, 0, layoutID,, ahk_id %imeWnd%
}
Этот метод комбинирует SendMessage для обычных окон и альтернативный подход через IMM для системных диалогов.
Решение через ControlSend и SendInput
Для системных диалогов можно использовать ControlSend с указанием конкретного контрола:
~LCtrl up::
If (A_PriorKey = "LControl") {
SendToDialog("English")
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SendToDialog("Russian")
}
return
SendToDialog(layout) {
; Определяем, какой метод использовать в зависимости от типа окна
WinGetTitle, title, A
if (InStr(title, "Выберите файл") or InStr(title, "Открыть") or InStr(title, "Сохранить")) {
; Для диалогов сохранения/открытия файлов
ControlFocus, Edit1, A
ControlSend, Edit1, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
} else {
; Для других системных окон
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
}
Решение через системный диалог языков
Еще один подход - использование системного диалога управления языками:
~LCtrl up::
If (A_PriorKey = "LControl") {
; Открываем диалог языков
Run, rundll32.exe Shell32.dll,Control_RunDLL input.dll,,{C07337D3-DB2C-4D0B-9A93-B722A6C106E2}
; Ждем открытия диалога
if WinWaitActive("Text Services and Input Languages", "", 3) {
; Переключаем на нужную раскладку
Send, ^{Tab}!c
; Ждем диалога изменения комбинаций
if WinWaitActive("Change Key Sequence", "", 3) {
WinGetPos(, , , , y)
WinMove, , y + 250
}
}
}
return
Этот метод открывает системный диалог управления языками, но он может быть слишком медленным для повседневного использования.
Полные примеры рабочих скриптов
Вариант 1: Комбинированный подход с проверкой типа окна
#NoEnv
#SingleInstance force
~LCtrl up::
If (A_PriorKey = "LControl") {
SwitchToLayout(0x0409) ; Английский (США)
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SwitchToLayout(0x0419) ; Русский
}
return
SwitchToLayout(layoutID) {
static WM_INPUTLANGCHANGEREQUEST := 0x50
hwnd := WinExist("A")
; Проверяем, является ли окно системным диалогом
if (IsSystemDialog(hwnd)) {
; Для системных диалогов используем IMM метод
UseIMMMethod(layoutID)
} else {
; Для обычных окон используем SendMessage
SendMessage, WM_INPUTLANGCHANGEREQUEST, 0, layoutID,, ahk_id %hwnd%
}
}
IsSystemDialog(hwnd) {
WinGetTitle, title, ahk_id %hwnd%
WinGetClass, class, ahk_id %hwnd%
; Системные диалоги
if (class = "#32770") or (class = "#32768")
return true
; Диалоги сохранения/открытия файлов
if (InStr(title, "Выберите файл") or InStr(title, "Открыть") or InStr(title, "Сохранить"))
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%
}
Вариант 2: Упрощенный метод с ControlSend
~LCtrl up::
If (A_PriorKey = "LControl") {
SendCtrlShiftToActiveWindow()
}
return
~RCtrl up::
If (A_PriorKey = "RControl") {
SendCtrlShiftToActiveWindow()
}
return
SendCtrlShiftToActiveWindow() {
; Получаем активный контрол
ControlGetFocus, control, A
; Определяем тип окна
WinGetTitle, title, A
WinGetClass, class, A
; Для системных диалогов
if (class = "#32770") or (InStr(title, "Выберите файл") or InStr(title, "Открыть") or InStr(title, "Сохранить")) {
; Фокусируемся на редакторе
ControlFocus, Edit1, A
ControlSend, Edit1, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
} else {
; Для обычных окон
ControlSend,, {Ctrl down}{Shift down}{Ctrl up}{Shift up}, A
}
}
Рекомендации по выбору метода
Наиболее надежный подход
Комбинированный метод с проверкой типа окна (Вариант 1) является наиболее надежным, так как:
- Оптимизирован для разных типов окон: Использует SendMessage для обычных окон и IMM метод для системных диалогов
- Минимальная задержка: Не открывает дополнительные диалоги, как некоторые другие методы
- Совместимость: Работает на всех версиях Windows 10
- Надежность: Использует прямые вызовы Windows API через IMM32
Почему этот метод лучше других:
- SendMessage в обычных окнах: Работает быстро и эффективно
- IMM метод в системных диалогах: Обходит ограничения системных окон
- Проверка типа окна: Автоматически выбирает нужный метод
- Нет дополнительных настроек: Не требует изменения системных параметров
Дополнительные рекомендации:
- Тестируйте скрипт: Проверьте работу в разных системных диалогах (сохранение, открытие, поиск)
- Обновляйте AutoHotkey: Используйте последнюю версию AHK (v2.0+)
- Добавьте визуальную обратную связь: Можно добавить звуковой сигнал или системное уведомление при переключении
- Управляйте языками: Убедитесь, что нужные языки установлены в Windows
Этот подход решает вашу проблему, оставая в рамках требования использовать только Ctrl для переключения раскладки без добавления дополнительных комбинаций клавиш в системных настройках.
Источники
- 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
Заключение
Основная проблема вашего скрипта заключается в том, что SendMessage с кодом 0x50 не работает в системных диалогах Windows из-за особенностей их обработки сообщений.
Ключевые решения:
- Комбинированный подход: Используйте SendMessage для обычных окон и IMM метод для системных диалогов
- Проверка типа окна: Автоматически определять, является ли окно системным диалогом
- Прямые вызовы Windows API: Используйте DllCall для вызова функций imm32.dll
Рекомендуемый скрипт использует комбинированный метод с проверкой типа окна, что обеспечивает надежную работу как в обычных окнах, так и в системных диалогах без необходимости добавления дополнительных комбинаций клавиш в системных настройках.
Этот подход полностью соответствует вашим требованиям: оставляет в системе единственный способ переключения языков через Ctrl и не требует установки дополнительных программ вроде PuntoSwitcher.