1 (изменено: stealzy, 2018-05-10 22:42:26)

Тема: 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 "`n" defaulSystemLayout
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 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.ChangeCommon(, this.INPUTLANGCHANGE_FORWARD, hWnd)
		} else if (arg = "backward") {
			Return this.ChangeCommon(, 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.ChangeCommon(LytList[A_Index].hkl,, hWnd)
			}
			Loop % LytList.MaxIndex() - HKL_Number
				If (LytList[A_Index + HKL_Number].hkl & 0xFFFF  !=  HKL & 0xFFFF)
					Return this.ChangeCommon(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.ChangeCommon(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.ChangeCommon(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.ChangeCommon(arg,, hWnd)
			Return "This HKL not found in system loaded layout list"
		} else
			Return "Not valid input"
	}
	ChangeCommon(HKL := 0, INPUTLANGCHANGE := 0, hWnd := 0) {
		Return (hWnd = "global")
			? this.ChangeGlobal(HKL, INPUTLANGCHANGE)
			: this.ChangeWindow(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.Change(HKL, INPUTLANGCHANGE, List%A_Index%)
		DetectHiddenWindows % prevDHW
	}
	ChangeWindow(HKL, INPUTLANGCHANGE, hWnd) {
		static hTaskBarHwnd := WinExist("ahk_class Shell_TrayWnd ahk_exe explorer.exe")
		(hWnd = hTaskBarHwnd)
		 ? this.ChangeTaskBar(HKL, INPUTLANGCHANGE, hTaskBarHwnd)
		 : this.Change(HKL, INPUTLANGCHANGE, hWnd)
	}
	ChangeTaskBar(HKL, INPUTLANGCHANGE, hTaskBarHwnd) {
		static hStartMenu, hLangBarInd, hDV2CH
		IfNotEqual A_DetectHiddenWindows, On, DetectHiddenWindows % (prevDHW := "Off") ? "On" : ""
		hStartMenu := hStartMenu  ? hStartMenu  : WinExist("ahk_classNativeHWNDHost ahk_exeexplorer.exe")
		hLangBarInd:= hLangBarInd ? hLangBarInd : WinExist("ahk_classCiceroUIWndFrame ahk_exeexplorer.exe")
		hDV2CH     := hDV2CH      ? hDV2CH      : WinExist("ahk_classDV2ControlHost ahk_exeexplorer.exe")

		this.Change(HKL, INPUTLANGCHANGE, hTaskBarHwnd)
		this.Change(HKL, INPUTLANGCHANGE, hStartMenu)
		If INPUTLANGCHANGE {
			Sleep 20
			HKL := this.GetInputHKL(hStartMenu), INPUTLANGCHANGE := 0
		}
		; to update Language bar indicator
		this.Change(HKL, INPUTLANGCHANGE, hLangBarInd)
		this.Change(HKL, INPUTLANGCHANGE, hDV2CH)
		DetectHiddenWindows % prevDHW
	}
	Change(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 aLayouts
		If IsObject(aLayouts)
			Return aLayouts
		Else {
			aLayouts := []
			Size := DllCall("GetKeyboardLayoutList", "UInt", 0, "Ptr", 0)
			VarSetCapacity(List, A_PtrSize*Size)
			Size := DllCall("GetKeyboardLayoutList", Int, Size, Str, List)
			Loop % Size {
				aLayouts[A_Index] := {}
				aLayouts[A_Index].hkl := NumGet(List, A_PtrSize*(A_Index - 1))
				aLayouts[A_Index].LocName := this.GetLocaleName(, aLayouts[A_Index].hkl)
				aLayouts[A_Index].LocFullName := this.GetLocaleName(, aLayouts[A_Index].hkl, true)
				aLayouts[A_Index].LayoutName := this.GetLayoutName(, aLayouts[A_Index].hkl)
				aLayouts[A_Index].KLID := this.GetKLIDfromHKL(aLayouts[A_Index].hkl)
			}
			Return aLayouts
		}
	}
	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 lang: "US", "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 layouts
	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:=DllCall("GetKeyboardLayout", Ptr,DllCall("GetWindowThreadProcessId", Ptr,hWnd, UInt,0, Ptr), Ptr)
			Return HKL & ((1 << 8*A_PtrSize) - 1)
		}
	}
}

hex(n) {
	Return Format("{:#0x}", n)
}
/*
~LShift Up::(A_PriorKey = "LShift") ? Lyt.Set("EN")
~RShift Up::(A_PriorKey = "RShift") ? Lyt.Set("RU")

#Space::PostMessage WM_INPUTLANGCHANGEREQUEST:=0x50, INPUTLANGCHANGE_FORWARD:=0x2,,, % (hWndOwn := DllCall("GetWindow", Ptr, hWnd:=WinExist("A"), UInt, GW_OWNER := 4, Ptr)) ? "ahk_id" hWndOwn : "ahk_id" hWnd

Компиляция найденного на форуме, плюс определение 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
Telegram jollycoder