1 (изменено: stealzy, 2017-02-18 12:59:37)

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

F1::Lyt.Set(0x4090409)	; set english layout by id
F2::Lyt.Set("Switch")		; switch input language. Lyt.Set() do the same.
F3::Lyt.Set("Forward")	; move forward in layout list cycle
F4::Lyt.Set(2)					; set second layout in list
F7::Lyt.Set("-en")			; set first non-english layout in list
F8::Lyt.Set("en", "global")
F9::Lyt.Set("forward", WinExist("AutoHotkey Help ahk_class HH Parent"))
F10::Lyt.Set("en", "AutoHotkey Help ahk_class HH Parent")
F11::MsgBox % Lyt.GetDisplayName("AutoHotkey Help ahk_class HH Parent")
F12::MsgBox % "HKL: " Format("{:#010x}", Lyt.GetInputHKL()) "`n№: " Lyt.GetNum() "`nDisplayName: " Lyt.GetDisplayName() "`nLanguage: " Lyt.GetLng() " - " Lyt.GetLng(,, true) "`n"
. Lyt.GetList()[2].DisplayName " " Lyt.GetList()[2].LngName " " Lyt.GetList()[2].LngFullName
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
	; ===================================================================================================================
	; PUBLIC METHOD Set()
	; Parameters:     arg (optional)   - (switch / forward / backward / 2-letter language name(en) / language number in current active layout list / language id e.g. HKL (0x04090409)). Default: switch
	;                 win (optional)   - (ahk format WinTitle / hWnd). Default: Active Window
	; Return value:   empty or description string in case of errors
	; ===================================================================================================================
	Set(arg := "switch", win := 0) {
		hWnd := win
						?	( win + 0
								? WinExist("ahk_id" win)
								: win = "global"
									? win
									: WinExist(win) )
						: WinExist("A")
		If hWnd = 0 ; WinExist() return 0
			Return "Window not found"

		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") {
			Return This.Change((This.GetNum(hWnd) != 1)  ?  This.GetList()[1].h  :  This.GetList()[2].h,, 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.LngName, arg) ^ invert)
					Return This.Change(layout.h,, hWnd)
			Return "Language not found in current layout list"
		} else if (arg <= This.GetList().MaxIndex()) {
			Return This.Change(This.GetList()[arg].h,, hWnd)
		} else if (arg > 1024) {
			Return This.Change(arg,, hWnd)
		} 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) {
		If (INPUTLANGCHANGE != 0)
			Return "Unpredictable behavior. Use other methods for global."
		tmp := A_DetectHiddenWindows
		DetectHiddenWindows On
		WinGet, List, List
		DetectHiddenWindows % tmp
		Loop % List
			This.ChangeLocal(HKL, INPUTLANGCHANGE, List%A_Index%)
	}
	ChangeLocal(HKL, INPUTLANGCHANGE, hWnd) {
		PostMessage, This.WM_INPUTLANGCHANGEREQUEST, % HKL ? "" : INPUTLANGCHANGE, % HKL ? HKL : "",, % (hWndOwn := DllCall("GetWindow", Ptr, hWnd, UInt, GW_OWNER := 4, Ptr)) ? "ahk_id" hWndOwn : "ahk_id" hWnd
	}

	GetNum(win := 0, HKL := 0) {
		HKL ? : HKL := This.GetInputHKL(win)
		For index, layout in This.GetList()
			if (layout.h = HKL)
				Return index
	}
	GetList() {
		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].h := NumGet(List, A_PtrSize*(A_Index - 1)) ;& 0xFFFF
				Layouts[A_Index].LngName := This.GetLng(, Layouts[A_Index].h)
				Layouts[A_Index].LngFullName := This.GetLng(, Layouts[A_Index].h, true)
				Layouts[A_Index].DisplayName := This.GetDisplayName(, Layouts[A_Index].h)
			}
			Return Layouts
		}
	}
	GetLng(win := 0, HKL := 0, FullName := false) {
		HKL ? : HKL := This.GetInputHKL(win)
		LocID := HKL & 0xFFFF
		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
	}
	GetDisplayName(win := 0, HKL := 0) {
		HKL ? : HKL := This.GetInputHKL(win)
		KLID := This.HKLtoKLID(HKL)
		RegRead, displayName, HKEY_LOCAL_MACHINE, SYSTEM\CurrentControlSet\Control\Keyboard Layouts\%KLID%, Layout Display Name
		if !displayName
			Return false

		DllCall("Shlwapi.dll\SHLoadIndirectString", "Ptr", &displayName, "Ptr", &displayName, "UInt", outBufSize:=50, "UInt", 0)
		if !displayName
			RegRead, displayName, HKEY_LOCAL_MACHINE, SYSTEM\CurrentControlSet\Control\Keyboard Layouts\%KLID%, Layout Text

		Return displayName
		}
		HKLtoKLID(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)
			|| !DllCall("ActivateKeyboardLayout", "Ptr", priorHKL, "UInt", 0)
				return false

			return StrGet(&KLID)
		}
	GetInputHKL(win := 0) { ; if handle incorrect, system default HKL return
		hWnd := win
						? win + 0
							? WinExist("ahk_id" win)
							: WinExist(win)
						: WinExist("A")
		If hWnd = 0
			Return "Window not found"

		WinGetClass, Class
		if (Class == "ConsoleWindowClass") {
				WinGet, consolePID, PID
				DllCall("AttachConsole", Ptr, consolePID)
				VarSetCapacity(buff, 16)
				DllCall("GetConsoleKeyboardLayoutName", Str, buff)
				DllCall("FreeConsole")
				HKL := "0x" . SubStr(buff, -3)
		} else
			HKL := DllCall("GetKeyboardLayout", Ptr, DllCall("GetWindowThreadProcessId", Ptr, hWnd, UInt, 0, Ptr), Ptr) ;& 0xFFFF
		return HKL
	}
}

В основном компиляция собранного на форуме, от себя добавил определение 2-буквенного имени языка и взятие отображаемого имени раскладки из реестра, если в dll ресурса оно не прописано (что система и делает, когда надо).
Примечание: Windows не позволяет переключить раскладку на панели задач когда фокус на ней.
Сделано это для того, чтобы по клику на языковую панель (которая является контролом панели задач) из активного окна отображаемая раскладка не сменялась тут же на текущую раскладку панели задач. И хотя не никаких причин менять раскладку на панели задач (-;, благодаря тов. Malcev и teadrinker был найден способ это делать:

+ Хак для активной панели задач

Данный код следует вставить в ChangeLocal():

WinGetClass class_, ahk_id %hWnd%
if (class_ = "Shell_TrayWnd") {
	ControlGetFocus v, ahk_id %hWnd%
	tmp := A_DetectHiddenWindows
	DetectHiddenWindows On
	ControlFocus, Desktop OpenBox Host1, ahk_class DV2ControlHost
	PostMessage, This.WM_INPUTLANGCHANGEREQUEST, % HKL ? "" : INPUTLANGCHANGE, % HKL ? HKL : "", Desktop OpenBox Host1, ahk_class DV2ControlHost
	DetectHiddenWindows % tmp
	ControlClick % v, ahk_id %hWnd%
}

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, 2017-02-17 07:20:31)

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

teadrinker, как всегда, содержательный ответ!
По недочетам переделано, ознакомился с Format() заодно.
По неактивным окнам похоже я ошибался, по крайней мере сейчас все работает.
Можно для красоты выделить метод Get(), а ему уже передавать в параметре, что надо.