Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Keyboard layout switcher


  • Please log in to reply
17 replies to this topic
wOxxOm
  • Members
  • 371 posts
  • Last active: Feb 20 2015 12:10 PM
  • Joined: 09 Feb 2006
here's a very basic example of keyboard layout (input language) switcher.
[*:19n3bf35]operates on the active window (any window, not limited to AHK script's own!) [*:19n3bf35]switches all available keyboard layouts in cycle[*:19n3bf35]Displays tooltip for 0.5 second with the keyboard layout name and Country name[*:19n3bf35]The default switch key is a single Right Control[*:19n3bf35]it switches languages instantly without an awkward delay seen with Punto Switcher or other utilities (IMHO)
$~RControl::LangSwitch(1)
$~RControl up::LangSwitch(2)

LangSwitch( iKeyDownUp=0 )
{
	static tickLast
	IfEqual,iKeyDownUp,1
	{	tickLast=%A_TickCount%
		return
	}
	IfEqual,iKeyDownUp,2
		If( A_TickCount-tickLast>200 )
			return

	HKL:=DllCall("GetKeyboardLayout", "uint",GetThreadOfWindow(), "uint")

	HKLnum:=DllCall("GetKeyboardLayoutList","uint",0,"uint",0)
	VarSetCapacity( HKLlist, HKLnum*4, 0 )
	DllCall("GetKeyboardLayoutList","uint",HKLnum,"uint",&HKLlist)
	loop,%HKLnum%
	{	if( NumGet( HKLlist, (A_Index-1)*4 ) = HKL )
		{	HKL:=NumGet( HKLlist, mod(A_Index,HKLnum)*4 )
			break
		}
	}
	ControlGetFocus,ctl,A
	SendMessage,0x50,0,HKL,%ctl%,A ;WM_INPUTLANGCHANGEREQUEST

	;show traytip
	LOCALE_SENGLANGUAGE=0x1001
	LOCALE_SENGCOUNTRY=0x1002
	VarSetCapacity( sKbd, 260, 0 )
	VarSetCapacity( sCountry, 260, 0 )
	DllCall("GetLocaleInfo","uint",HKL>>16,"uint",LOCALE_SENGLANGUAGE, "str",sKbd, "uint",260)
	DllCall("GetLocaleInfo","uint",HKL & 0xFFFF,"uint",LOCALE_SENGCOUNTRY, "str",sCountry, "uint",260)
	traytip,%sKbd%,%sCountry%
	SetTimer,REMOVE_TOOLTIP,500 ;0.5 second
	return
REMOVE_TOOLTIP:
	SetTimer,REMOVE_TOOLTIP,off
	traytip
	return
}

;returns first thread for the <processID>
;sets optional <List> to pipe | separated thread list for the <processID>
GetProcessThreadOrList( processID, byRef list="" )
{
	;THREADENTRY32 {
	THREADENTRY32_dwSize=0 ; DWORD
	THREADENTRY32_cntUsage = 4	;DWORD
	THREADENTRY32_th32ThreadID = 8	;DWORD
	THREADENTRY32_th32OwnerProcessID = 12	;DWORD
	THREADENTRY32_tpBasePri = 16	;LONG
	THREADENTRY32_tpDeltaPri = 20	;LONG
	THREADENTRY32_dwFlags = 24	;DWORD
	THREADENTRY32_SIZEOF = 28

	TH32CS_SNAPTHREAD=4

	hProcessSnap := DllCall("CreateToolhelp32Snapshot","uint",TH32CS_SNAPTHREAD, "uint",0)
	ifEqual,hProcessSnap,-1, return

	VarSetCapacity( thE, THREADENTRY32_SIZEOF, 0 )
	NumPut( THREADENTRY32_SIZEOF, thE )

	ret=-1

	if( DllCall("Thread32First","uint",hProcessSnap, "uint",&thE ))
		loop
		{
			if( NumGet( thE ) >= THREADENTRY32_th32OwnerProcessID + 4)
				if( NumGet( thE, THREADENTRY32_th32OwnerProcessID ) = processID )
				{	th := NumGet( thE, THREADENTRY32_th32ThreadID )
					IfEqual,ret,-1
						ret:=th
					list .=  th "|"
				}
			NumPut( THREADENTRY32_SIZEOF, thE )
			if( DllCall("Thread32Next","uint",hProcessSnap, "uint",&thE )=0)
				break
		}

	DllCall("CloseHandle","uint",hProcessSnap)
	StringTrimRight,list,list,1
	return ret
}

; Returns thread owning specified window handle
; default = Active window
GetThreadOfWindow( hWnd=0 )
{
	IfEqual,hWnd,0
		hWnd:=WinExist("A")
	DllCall("GetWindowThreadProcessId", "uint",hWnd, "uintp",id)
	GetProcessThreadOrList(  id, threads )
	IfEqual,threads,
		return 0
	CB:=RegisterCallback("GetThreadOfWindowCallBack","Fast")
	lRet=0
	lParam:=hWnd
	loop,parse,threads,|
	{	NumPut( hWnd, lParam )
		DllCall("EnumThreadWindows", "uint",A_LoopField, "uint",CB, "uint",&lParam)
		if( NumGet( lParam )=true )
		{	lRet:=A_LoopField
			break
		}
	}
	DllCall("GlobalFree", "uint", CB)
	return lRet
}

GetThreadOfWindowCallBack( hWnd, lParam )
{
	IfNotEqual,hWnd,% NumGet( 0+lParam )
		return true
	NumPut( true, 0+lParam )
	return 0
}

and here's just a complementary simple converter (ghbdtn <> привет) of text typed under wrong keyboard layout.
[*:19n3bf35]I use it because I don't like punto switcher, keyboard ninja, and other utilities I tried. [*:19n3bf35]It works for a selection of text, the default is Pause key.[*:19n3bf35]It uses a less compatible but much faster method of pasting the changed text instead of sending it character by character, so if there are problems I manually add the window class declaration under "decide compatibility of unicode clipboard" comment.
$~Pause::RecodeTextENRU()

RecodeTextENRU()
{
	StringCaseSense On
	AutoTrim,Off

	clipSave:=clipAnsi()
	send ^{Insert}
	sleep,50

	dest=
	text:=clipAnsi()
	StringCaseSense,On
	prevCharToEN=0
	;		АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ
	RUtoEN=F<DULT:PBQRKVYJGHCNEA{WXIO}SM">Zf,dult;pbqrkvyjghcnea[wxio]sm'.z
	RUtoSP1=хъжэбюХЪЖЭБЮ.,/"№;:?
	RUtoSP2=[];',.{}:"<>/?|@#$^&
	;"		 ABCDEFGHIJKLMNOPQRSTUVWXYZ
	ENtoRU=ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯфисвуапршолдьтщзйкыегмцчня

	loop,parse,text
	{
		destChar=%A_LoopField%

		; check explicit (non punctuations) ranges
		ifGreaterOrEqual,A_LoopField,А
			prevCharToEN=1
		else if A_LoopField between A and Z
			prevCharToEN=0
		else if A_LoopField between a and z
			prevCharToEN=0

		; to Russian
		ifEqual,prevCharToEN,0
		{	StringGetPos,i,RUtoEN,%A_LoopField%
			ifEqual,ErrorLevel,0
				Transform,destChar,chr,% i + 0xC0
			else
			{	StringGetPos,i,RUtoSP2,%A_LoopField%
				ifEqual,ErrorLevel,0
					StringMid,destChar,RUtoSP1,% i+1, 1
			}
		}

		; to English because nothing changed
		ifEqual,destChar,%A_LoopField%
		{
			StringGetPos,i,ENtoRU,%A_LoopField%
			ifEqual,ErrorLevel,0
				Transform,destChar,chr,% i + (i>=26 ? 71 : 65)
			else ; check .,;':"[]{}
			{	StringGetPos,i,RUtoSP1,%A_LoopField%
				ifEqual,ErrorLevel,0,StringMid,destChar,RUtoSP2,% i+1, 1
			}
			ifNotEqual,destChar,%A_LoopField%
				prevCharToEN=1
		}
		dest=%dest%%destChar%
	}

	; decide compatibility of unicode clipboard
	WinGetClass,cls,A
	if cls in TMsgEditor,wndclass_desked_gsk
	{
		ControlGetFocus,cls,A
		ifInString,cls,TXTRichEdit
			clipSetUnicode(dest)
		else
			Clipboard=%dest%
	}
	else
		clipSetUnicode(dest)
	sleep,50
	send +{Insert}
	sleep 50

	clipSetUnicode(clipSave)
	LangSwitch()
}

; read unicode clipboard into ansi string
clipAnsi()
{
	StringLen,L,Clipboard
	L:=(L+1)*4
	transform,ca_Clip,unicode
	varSetCapacity(ca_WideText,L,0)
	varSetCapacity(ca_AnsiText,L,0)
	; Convert UTF-8 to UTF-16.	CP_UTF8=65001
	if dllCall("MultiByteToWideChar",uint,65001, uint,0, str,ca_Clip
				  , uint,-1, str,ca_WideText, uint,L/2)
		dllCall("WideCharToMultiByte",uint,0, uint,0, str,ca_WideText
				  , uint,-1, str,ca_AnsiText, uint,L/2, uint,0, uint,0)
		; Convert UTF-16 to ANSI.  CP_ACP=0
	return ca_AnsiText
}

;--------------------------------------------------------------
; copy ansi string to clipboard in unicode mode
clipSetUnicode(cu_AnsiText)
{
	StringLen,L,cu_AnsiText
	L:=(L+1)*4
	varSetCapacity(cu_WideText,L,0)
	varSetCapacity(cu_UTFtext,L,0)
	; ANSI to UTF-16.	CP_ACP=0
	if dllCall("MultiByteToWideChar",uint,0, uint,0, str,cu_AnsiText
				  , uint,-1, str,cu_WideText, uint,L/2)
		dllCall("WideCharToMultiByte",uint,65001, uint,0, str,cu_WideText
				  , uint,-1, str,cu_UTFtext, uint,L/2, uint,0, uint,0)
		; Convert UTF-16 to UTF-8.  CP_UTF8=65001
	transform,clipboard,unicode,%cu_UTFtext%
}


sashabe
  • Members
  • 24 posts
  • Last active: Aug 05 2013 11:34 AM
  • Joined: 09 Oct 2006
Thanks!!! Спасибо))) Бесценная вещь)

wOxxOm
  • Members
  • 371 posts
  • Last active: Feb 20 2015 12:10 PM
  • Joined: 09 Feb 2006
Ооо наши везде!

IMPORTANT UPDATES:
[*:12z7rthj]now the LangSwitch script also works in dialog windows
(it sends WM_INPUTLANGCHANGEREQUEST to the active control of the active window)

[*:12z7rthj]clipAnsi and clipSetUnicode [used by RecodeTextENRU] now can handle any amount of text
previously the allocation was fixed 1000 bytes, now it's calculated based on clipboard buffer size

JDN
  • Members
  • 313 posts
  • Last active: Sep 03 2016 06:51 AM
  • Joined: 24 Mar 2004
I wanted to try your script but I'm afraid I don't understand it. Can you explain in a sentence or two what it is supposed to accomplish? Or better yet, can you give me some instructions I could follow that would help me see the intended effect?

I'm afraid that I don't understand about keyboard layouts and switching languages or keyboard layouts. In order to see the effect of your script, should I have loaded one or more keyboard layouts before pressing and releasing the right control key?

I loaded your hotkey and started an editor application. Then I pressed and released the right control key. But I didn't notice anything happen. Can you explain if I should have seen something happen?

wOxxOm
  • Members
  • 371 posts
  • Last active: Feb 20 2015 12:10 PM
  • Joined: 09 Feb 2006
just the same thing that happens when you press Ctrl-Shift or Alt-Shift which is the default Windows keyboard input language switch hotkey. However this script provides more handy control, that's it.

JDN
  • Members
  • 313 posts
  • Last active: Sep 03 2016 06:51 AM
  • Joined: 24 Mar 2004
OK. Thanks.

sashabe
  • Members
  • 24 posts
  • Last active: Aug 05 2013 11:34 AM
  • Joined: 09 Oct 2006
А нельзя ли присобачить к нему возможность транслитерации? Я пока не разобрался в скрипте...

rogal
  • Members
  • 62 posts
  • Last active: Apr 28 2009 05:26 AM
  • Joined: 23 Dec 2007
Great script. Thank you a lot!

wOxxOm
  • Members
  • 371 posts
  • Last active: Feb 20 2015 12:10 PM
  • Joined: 09 Feb 2006
UPDATES:
1. Filtering out of RControl+other keys

$~RControl::LangSwitch(1)
$~RControl up::LangSwitch(2)
Synopsis: In case you use RControl also as a modifier for arrow keys or other keys, the script previously switched language as well which isn't good. Now LangSwitch checks the time interval when hotkey was pressed and released. 200 ms is the default.

2. clipSetUnicode wrong length calculation, fixed [used by RecodeTextENRU]
clipSetUnicode(cu_AnsiText)
{
	StringLen,L,cu_AnsiText
	;the full code is in first post


Lobotomie
  • Guests
  • Last active:
  • Joined: --
Thank you, this is really useful for me ! :D

I'd like to customize it, but I think I need help :
I want to switch to the second layout while RControl is down, then switch back to the first layout when RControl is released ...

Can you help me please ? :oops:

Lobotomie
  • Guests
  • Last active:
  • Joined: --
This will do it for me with Right Alt :

RAlt::SwitchLayout(4026598409) ; switch to US International (with dead keys)
RAlt up::SwitchLayout(67699721) ; switch back to US

SwitchLayout( layoutNumber )
{
ControlGetFocus,ctl,A
SendMessage,0x50,0,layoutNumber,%ctl%,A
}

Thanks anyway

Cyberion
  • Guests
  • Last active:
  • Joined: --
Congratulations wOxxOm! Very nice work.

Two things, though.

1. You won't see the nifty tooltips if you have tooltips disabled (through TweakUI, for instance).
2. What's the advantage of your tool over the built-in Windows switcher?

If you don't mind, I would like to use the part where you get the current keyboard layout. Looks pretty complicated, though (I'm not a very experienced coder).
What do I need it for? I'm using the Accents script to enter accentuated characters in one of my languages. Unfortunately, when I switch to Russian, 'Accents' messes up my Russian keyboard layout. That is why I want to add an If statement that would disable the script whenever my keyboard layout is set to anything than the language in which I need the accents.

I'll let you know how it goes.
Congrats again. Your second script is pure genious, by the way

mihaibirsan
  • Members
  • 2 posts
  • Last active: Mar 11 2010 09:35 AM
  • Joined: 16 Feb 2009
Does anyone know how one could obtain the actual Keyboard Layout Name? Here's my system config:

Posted Image

The strings that I want to retrieve are "US" and "Romanian (Standard, cu sedile)". I can't find a way to do that, and I've been googling for quite a while now. Any hints?

wOxxOm
  • Members
  • 371 posts
  • Last active: Feb 20 2015 12:10 PM
  • Joined: 09 Feb 2006
the code in the first post, the block right after this comment - ;show traytip

cargo
  • Members
  • 1 posts
  • Last active: Feb 19 2009 12:29 PM
  • Joined: 19 Feb 2009
My dumb way of doing this translation+switching task: :roll:
; Translates selected text RU<>EN, cycles input language
Pause::Translate() ; Hotkey - Pause button

Translate()
{
Eng=qwertyuiop[]asdfghjkl;'zxcvbnm,.QWERTYUIOP{}ASDFGHJKL:"ZXCVBNM<>
Rus=йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ

Send ^{Ins} ; Copy selection to clipboard
ClipWait
r =
Loop, parse, clipboard
{
	p := InStr(Eng, A_LoopField, true)
	if p > 0
		r := r . SubStr(Rus, p, 1)
	else
	{
		p := InStr(Rus, A_LoopField, true)
		if p > 0
			r := r . SubStr(Eng, p, 1)
		else
			r := r . A_LoopField
	}
}
PostMessage, 0x50, 2, 0,, A ; Switch lang to next
SendInput {Del}%r% ; Delete selection, print translated text
}
Limitations:
- only RU<>EN translation
- there must be only two input languages configured, as they are cycled each time