here's a very basic example of keyboard layout (input language) switcher.
- operates on the active window (any window, not limited to AHK script's own!)
- switches all available keyboard layouts in cycle
- Displays tooltip for 0.5 second with the keyboard layout name and Country name
- The default switch key is a single Right Control
- it switches languages instantly without an awkward delay seen with Punto Switcher or other utilities (IMHO)
Code:
$~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.
- I use it because I don't like punto switcher, keyboard ninja, and other utilities I tried.
- It works for a selection of text, the default is Pause key.
- 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.
Code:
$~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%
}