1 (изменено: stealzy, Сегодня 01:05:22)

Тема: AHK: Класс для операций с раскладками

Получение информации о раскладках: текущая раскладка в окне, список загруженных раскладок в системе;
Установка раскладки: следующей/предыдущей в списке, другой локали (языка), конкретной раскладки по имени, по идентификатору, по номеру. В окне или во всех окнах.

#SingleInstance force
Loop % Lyt.GetList().MaxIndex()
	str .= A_Index ": " Lyt.GetList()[A_Index].LocName " - " Lyt.GetList()[A_Index].LayoutName
	. "`n" Format("{:#010x}", Lyt.GetList()[A_Index].hkl) "`n"
MsgBox,, Your system loaded layout list, % str
F1::ToolTip % Lyt.Set("EN")           ; set first EN locale layout in system loaded layout list
F2::ToolTip % Lyt.Set("switch")       ; switch input locale. Lyt.Set() do the same.
F3::ToolTip % Lyt.Set("forward")      ; move forward (cycle) in layout list
F4::ToolTip % Lyt.Set(2)              ; set second layout in list
F7::ToolTip % Lyt.Set("-EN")          ; set first non-english layout in list
F8::ToolTip % Lyt.Set(0x4090409)      ; set en-US layout by HKL
F9::Lyt.Set("forward", WinExist("AutoHotkey Help ahk_class HH Parent")) ; in opened AutoHotkey.chm windows
F10::Lyt.Set("en", "AutoHotkey Help ahk_class HH Parent")
F11::MsgBox % Lyt.GetLayoutName("AutoHotkey Help ahk_class HH Parent")
F12::MsgBox % "HKL: " Format("{:#010x}", Lyt.GetInputHKL()) "`n№: " Lyt.GetNum() " in system loaded list"
 . "`nLayoutName: " Lyt.GetLayoutName() "`nLocale: " Lyt.GetLocaleName() " (" Lyt.GetLocaleName(,, true) ")`n" Lyt.GetList()[3].KLID
; . "`n" Lyt.GetList()[2].LayoutName " " Lyt.GetList()[2].LocName " " Lyt.GetList()[2].LocFullName
Esc::ExitApp

Class Lyt {
	static SISO639LANGNAME              := 0x0059 ; ISO abbreviated language name, eg "EN"
	static LOCALE_SENGLANGUAGE          := 0x1001 ; Full language name, eg "English"
	static WM_INPUTLANGCHANGEREQUEST    := 0x0050
	static INPUTLANGCHANGE_FORWARD      := 0x0002
	static INPUTLANGCHANGE_BACKWARD     := 0x0004
	static KLIDsREG_PATH                := "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\"
	; =========================================================================================================
	; PUBLIC METHOD Set()
	; Parameters:     arg (optional)   - (switch / forward / backward / 2-letter locale indicator name (EN) /
	;  / number of layout in system loaded layout list / language id e.g. HKL (0x04090409)). Default: switch
	;                 win (optional)   - (ahk format WinTitle / hWnd / "global"). Default: Active Window
	; Return value:   empty or description of error
	; =========================================================================================================
	Set(arg := "switch", win := "") {
		IfEqual win, 0, Return "Window not found"
		hWnd := (win = "")
						? WinExist("A")
						: ( win + 0
								? WinExist("ahk_id" win)
								: win = "global"
									? win
									: WinExist(win) )
		IfEqual hWnd, 0, Return "Window not found" ; WinExist() return 0

		if (arg = "forward") {
			Return this.Change(, this.INPUTLANGCHANGE_FORWARD, hWnd)
		} else if (arg = "backward") {
			Return this.Change(, this.INPUTLANGCHANGE_BACKWARD, hWnd)
		} else if (arg = "switch") {
			tmphWnd := (hWnd = "global") ? WinExist("A") : hWnd
			HKL := this.GetInputHKL(tmphWnd)
			HKL_Number := this.GetNum(,HKL)
			LytList := this.GetList()
			Loop % HKL_Number - 1 {
				If (LytList[A_Index].hkl & 0xFFFF  !=  HKL & 0xFFFF)
					Return this.Change(LytList[A_Index].hkl,, hWnd)
			}
			Loop % LytList.MaxIndex() - HKL_Number
				If (LytList[A_Index + HKL_Number].hkl & 0xFFFF  !=  HKL & 0xFFFF)
					Return this.Change(LytList[A_Index + HKL_Number].hkl,, hWnd)
		} else if (arg ~= "^-?[A-z]{2}") {
			invert := ((SubStr(arg, 1, 1) = "-") && (arg := SubStr(arg, 2, 2))) ? true : false
			For index, layout in this.GetList()
				if (InStr(layout.LocName, arg) ^ invert)
					Return this.Change(layout.hkl,, hWnd)
			Return "HKL from this locale not found in system loaded layout list"
		} else if (arg > 0 && arg <= this.GetList().MaxIndex()) { ; HKL number in system loaded layout list
			Return this.Change(this.GetList()[arg].hkl,, hWnd)
		} else if (arg > 0x400 || arg < 0) { ; HKL handle input
			For index, layout in this.GetList()
				if layout.hkl = arg
					Return this.Change(arg,, hWnd)
			Return "This HKL not found in system loaded layout list"
		} else
			Return "Not valid input"
	}
	Change(HKL := 0, INPUTLANGCHANGE := 0, hWnd := 0) {
		Return (hWnd = "global")
			? this.ChangeGlobal(HKL, INPUTLANGCHANGE)
			: this.ChangeLocal(HKL, INPUTLANGCHANGE, hWnd)
	}
	ChangeGlobal(HKL, INPUTLANGCHANGE) { ; in all windows
		If (INPUTLANGCHANGE != 0)
			Return "FORWARD and BACKWARD not support with global parametr."
		IfNotEqual A_DetectHiddenWindows, On, DetectHiddenWindows % (prevDHW := "Off") ? "On" : ""
		WinGet List, List
		Loop % List
			this.ChangeLangRequest(HKL, INPUTLANGCHANGE, List%A_Index%)
		DetectHiddenWindows % prevDHW
	}
	ChangeLocal(HKL, INPUTLANGCHANGE, hWnd) {
		(hWnd = this.GetTaskBarHwnd()) ? this.ChangeTaskBar(HKL, INPUTLANGCHANGE) : this.ChangeLangRequest(HKL, INPUTLANGCHANGE, hWnd)
	}
	GetTaskBarHwnd() {
		static TaskBarHwnd
		Return TaskBarHwnd ? TaskBarHwnd : (TaskBarHwnd := WinExist("ahk_class Shell_TrayWnd ahk_exe explorer.exe"))
	}
	ChangeTaskBar(HKL, INPUTLANGCHANGE) {
		
	ChangeTaskBar(HKL, INPUTLANGCHANGE) {
		IfNotEqual A_DetectHiddenWindows, On, DetectHiddenWindows % (prevDHW := "Off") ? "On" : ""
		this.ChangeLangRequest(HKL, INPUTLANGCHANGE, WinExist("ahk_class Shell_TrayWnd ahk_exe explorer.exe"))
		this.ChangeLangRequest(HKL, INPUTLANGCHANGE, hWnd := WinExist("ahk_class NativeHWNDHost ahk_exe explorer.exe"))
		If INPUTLANGCHANGE {
			Sleep 20
			HKL := this.GetInputHKL(hWnd), INPUTLANGCHANGE := 0
		}
		; to update Language bar indicator
		this.ChangeLangRequest(HKL, INPUTLANGCHANGE, WinExist("ahk_class CiceroUIWndFrame ahk_exe explorer.exe"))
		this.ChangeLangRequest(HKL, INPUTLANGCHANGE, WinExist("ahk_class DV2ControlHost ahk_exe explorer.exe"))
		DetectHiddenWindows % prevDHW
	}
	ChangeLangRequest(HKL, INPUTLANGCHANGE, hWnd) {
		PostMessage, this.WM_INPUTLANGCHANGEREQUEST, % HKL ? "" : INPUTLANGCHANGE, % HKL ? HKL : "",
		, % "ahk_id" ((hWndOwn := DllCall("GetWindow", Ptr, hWnd, UInt, GW_OWNER := 4, Ptr)) ? hWndOwn : hWnd)
	}

	GetNum(win := "", HKL := 0) { ; layout Number in system loaded layout list
		HKL ? : HKL := this.GetInputHKL(win)
		If HKL {
			For index, layout in this.GetList()
				if (layout.hkl = HKL)
					Return index
		}
		Else If (KLID := this.KLID, this.KLID := "")
			For index, layout in this.GetList()
				if (layout.KLID = KLID)
					Return index
	}
	GetList() { ; List of system loaded layouts
		Static Layouts
		If IsObject(Layouts)
			Return Layouts
		Else {
			VarSetCapacity(List, A_PtrSize*5)
			Size := DllCall("GetKeyboardLayoutList", Int, 5, Str, List)
			Layouts := []
			Loop % Size {
				Layouts[A_Index] := {}
				Layouts[A_Index].hkl := NumGet(List, A_PtrSize*(A_Index - 1))
				Layouts[A_Index].LocName := this.GetLocaleName(, Layouts[A_Index].hkl)
				Layouts[A_Index].LocFullName := this.GetLocaleName(, Layouts[A_Index].hkl, true)
				Layouts[A_Index].LayoutName := this.GetLayoutName(, Layouts[A_Index].hkl)
				Layouts[A_Index].KLID := this.GetKLIDfromHKL(Layouts[A_Index].hkl)
			}
			Return Layouts
		}
	}
	GetLocaleName(win := "", HKL := false, FullName := false) { ; e.g. "EN"
		HKL ? : HKL := this.GetInputHKL(win)
		If HKL
			LocID := HKL & 0xFFFF
		Else If (HKL = 0)  ;ConsoleWindow
			LocID := "0x" . SubStr(this.KLID, -3), this.KLID := ""
		Else
			Return

		LCType := FullName ? this.LOCALE_SENGLANGUAGE : this.SISO639LANGNAME
		Size := (DllCall("GetLocaleInfo", UInt, LocID, UInt, LCType, UInt, 0, UInt, 0) * 2)
		VarSetCapacity(localeSig, Size, 0)
		DllCall("GetLocaleInfo", UInt, LocID, UInt, LCType, Str, localeSig, UInt, Size)
		Return localeSig
	}
	GetLayoutName(win := "", HKL := false) { ; Layout name in OS display language - "US" or "United States-Dvorak"
		HKL ? : HKL := this.GetInputHKL(win)
		If HKL
			KLID := this.GetKLIDfromHKL(HKL)
		Else If (HKL = 0)  ;ConsoleWindow
			KLID := this.KLID, this.KLID := ""
		Else
			Return

		RegRead LayoutName, % this.KLIDsREG_PATH KLID, Layout Display Name
		DllCall("Shlwapi.dll\SHLoadIndirectString", "Ptr", &LayoutName, "Ptr", &LayoutName, "UInt", outBufSize:=50, "UInt", 0)
		if !LayoutName
			RegRead LayoutName, % this.KLIDsREG_PATH KLID, Layout Text

		Return LayoutName
	}
	; Only for loaded in system HKL
	GetKLIDfromHKL(HKL) {
		VarSetCapacity(KLID, 8 * (A_IsUnicode ? 2 : 1))

		priorHKL := DllCall("GetKeyboardLayout", "Ptr", DllCall("GetWindowThreadProcessId", "Ptr", 0, "UInt", 0, "Ptr"), "Ptr")
		if !DllCall("ActivateKeyboardLayout", "Ptr", HKL, "UInt", 0)
		|| !DllCall("GetKeyboardLayoutName", "Ptr", &KLID)
			Return false
		DllCall("ActivateKeyboardLayout", "Ptr", priorHKL, "UInt", 0)

		Return StrGet(&KLID)
	}
	; =========================================================================================================
	; PUBLIC METHOD GetInputHKL()
	; Parameters:     win (optional)   - ("" / hWnd / WinTitle). Default: "" — Active Window
	; Return value:   HKL of window / if handle incorrect, system default layout HKL return / 0 - if KLID found
	; =========================================================================================================
	GetInputHKL(win := "") {
		If win = 0
			Return,, ErrorLevel := "Window not found"
		hWnd := (win = "")
						? WinExist("A")
						: win + 0
							? WinExist("ahk_id" win)
							: WinExist(win)
		If hWnd = 0
			Return,, ErrorLevel := "Window " win " not found"

		WinGetClass, class
		if (class == "ConsoleWindowClass") {
				WinGet, consolePID, PID
				DllCall("AttachConsole", Ptr, consolePID)
				VarSetCapacity(KLID, 16)
				DllCall("GetConsoleKeyboardLayoutName", Str, KLID)
				DllCall("FreeConsole")
				this.KLID := KLID
				Return 0
		} else {
			; Dvorak on OSx64 0xfffffffff0020409 = -268303351   ->   0xf0020409 = 4026663945 Dvorak on OSx86
			HKL_8B := DllCall("GetKeyboardLayout", Ptr, DllCall("GetWindowThreadProcessId", Ptr, hWnd, UInt, 0, Ptr), Ptr)
			Return (A_PtrSize = 4) ? HKL_8B & 0xFFFFffff : HKL_8B
		}
	}
}

Компиляция найденного на форуме, плюс определение 2-буквенного имени языка, взятие отображаемого имени раскладки из реестра при отсутствии в dll ресурса, исправления для работы в x64 ОС и x64 AutoHotkey, обработка HKL/KLID/LocID.
Fix: Полноценно работает с панелью задач благодаря наводке от Malcev.
Upd: Многое переписано и исправлено.

2 (изменено: teadrinker, 2017-02-16 23:22:37)

Re: AHK: Класс для операций с раскладками

stealzy пишет:

Возможно для неактивного окна стоит брать список контролов, и слать всем?

Хорошая догадка, только бывают окна, где нет контролов, поэтому нужно слать ещё и самому окну.

stealzy пишет:

С параметром Forward тогда не выйдет.

Да, но можно определять текущую раскладку, и слать следующую в списке.

stealzy пишет:

По параметру INPUTLANGCHANGE_SYSCHARSET

Не заморачивайся, там куда-то совсем глубоко копать нужно, на практике его влияния замечено не было.

Заметил несколько недочётов:

VarSetCapacity(KLID, 8*(A_IsUnicode+1))

Такая конструкция в ANSI-версии не сработает, там A_IsUnicode равно пустому значению.

Hex(num) {
	OldFormat := A_FormatInteger
	SetFormat, Integer, Hex
	num += 0
	SetFormat, Integer, %OldFormat%
	Return num
}

Устаревший способ. Сейчас для преобразования в hex есть функция Format().

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Skype dmitry_fiveg

3 (изменено: stealzy, 2018-01-16 22:56:51)

Re: AHK: Класс для операций с раскладками

Обновил класс, теперь без костылей переключает раскладки на панели задач, исправлена работа в комбинациях с x64 процессом / ОС.

Если кто может протестировать напишите все ли работает. Меняется ли раскладка при фокусе на панели задач?