2 * PROJECT: Keyboard Layout Switcher
3 * FILE: base/applications/kbswitch/kbswitch.c
4 * PURPOSE: Switching Keyboard Layouts
5 * PROGRAMMERS: Dmitry Chapyshev (dmitry@reactos.org)
6 * Colin Finck (mail@colinfinck.de)
11 #define WM_NOTIFYICONMSG (WM_USER + 248)
13 PKBSWITCHSETHOOKS KbSwitchSetHooks
= NULL
;
14 PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks
= NULL
;
15 UINT ShellHookMessage
= 0;
19 GetLayoutID(LPTSTR szLayoutNum
, LPTSTR szLCID
, SIZE_T LCIDLength
);
22 GetLayoutName(LPTSTR szLayoutNum
, LPTSTR szName
, SIZE_T NameLength
);
27 ULONG ulCurrentLayoutNum
= 1;
30 CreateTrayIcon(LPTSTR szLCID
)
35 HBITMAP hBitmap
, hBmpNew
, hBmpOld
;
37 HFONT hFontOld
, hFont
= NULL
;
41 lId
= (LANGID
)_tcstoul(szLCID
, NULL
, 16);
42 if (GetLocaleInfo(lId
,
43 LOCALE_SISO639LANGNAME
,
45 ARRAYSIZE(szBuf
)) == 0)
47 StringCchCopy(szBuf
, ARRAYSIZE(szBuf
), _T("??"));
51 hdc
= CreateCompatibleDC(hdcsrc
);
52 hBitmap
= CreateCompatibleBitmap(hdcsrc
, 16, 16);
53 ReleaseDC(NULL
, hdcsrc
);
57 hBmpNew
= CreateBitmap(16, 16, 1, 1, NULL
);
60 hBmpOld
= SelectObject(hdc
, hBitmap
);
66 SetBkColor(hdc
, GetSysColor(COLOR_HIGHLIGHT
));
67 SetTextColor(hdc
, GetSysColor(COLOR_HIGHLIGHTTEXT
));
69 ExtTextOut(hdc
, rect
.left
, rect
.top
, ETO_OPAQUE
, &rect
, _T(""), 0, NULL
);
71 hFont
= CreateFont(-11, 0, 0, 0, FW_NORMAL
, FALSE
, FALSE
, FALSE
, ANSI_CHARSET
,
72 OUT_DEFAULT_PRECIS
, CLIP_DEFAULT_PRECIS
,
73 DEFAULT_QUALITY
, FF_DONTCARE
, _T("Tahoma"));
75 hFontOld
= SelectObject(hdc
, hFont
);
76 DrawText(hdc
, _tcsupr(szBuf
), 2, &rect
, DT_SINGLELINE
|DT_CENTER
|DT_VCENTER
);
77 SelectObject(hdc
, hBmpNew
);
78 PatBlt(hdc
, 0, 0, 16, 16, BLACKNESS
);
79 SelectObject(hdc
, hBmpOld
);
80 SelectObject(hdc
, hFontOld
);
82 IconInfo
.hbmColor
= hBitmap
;
83 IconInfo
.hbmMask
= hBmpNew
;
84 IconInfo
.fIcon
= TRUE
;
86 hIcon
= CreateIconIndirect(&IconInfo
);
88 DeleteObject(hBmpNew
);
89 DeleteObject(hBmpOld
);
95 DeleteObject(hBitmap
);
101 AddTrayIcon(HWND hwnd
)
104 TCHAR szLCID
[CCH_LAYOUT_ID
+ 1];
105 TCHAR szName
[MAX_PATH
];
107 GetLayoutID(_T("1"), szLCID
, ARRAYSIZE(szLCID
));
108 GetLayoutName(_T("1"), szName
, ARRAYSIZE(szName
));
110 memset(&tnid
, 0, sizeof(tnid
));
111 tnid
.cbSize
= sizeof(NOTIFYICONDATA
);
114 tnid
.uFlags
= NIF_ICON
| NIF_MESSAGE
| NIF_TIP
;
115 tnid
.uCallbackMessage
= WM_NOTIFYICONMSG
;
116 tnid
.hIcon
= CreateTrayIcon(szLCID
);
118 StringCchCopy(tnid
.szTip
, ARRAYSIZE(tnid
.szTip
), szName
);
120 Shell_NotifyIcon(NIM_ADD
, &tnid
);
124 DelTrayIcon(HWND hwnd
)
128 memset(&tnid
, 0, sizeof(tnid
));
129 tnid
.cbSize
= sizeof(NOTIFYICONDATA
);
133 Shell_NotifyIcon(NIM_DELETE
, &tnid
);
137 UpdateTrayIcon(HWND hwnd
, LPTSTR szLCID
, LPTSTR szName
)
141 memset(&tnid
, 0, sizeof(tnid
));
142 tnid
.cbSize
= sizeof(NOTIFYICONDATA
);
145 tnid
.uFlags
= NIF_ICON
| NIF_MESSAGE
| NIF_TIP
;
146 tnid
.uCallbackMessage
= WM_NOTIFYICONMSG
;
147 tnid
.hIcon
= CreateTrayIcon(szLCID
);
149 StringCchCopy(tnid
.szTip
, ARRAYSIZE(tnid
.szTip
), szName
);
151 Shell_NotifyIcon(NIM_MODIFY
, &tnid
);
155 GetLayoutID(LPTSTR szLayoutNum
, LPTSTR szLCID
, SIZE_T LCIDLength
)
160 TCHAR szTempLCID
[CCH_LAYOUT_ID
+ 1];
163 if (RegOpenKeyEx(HKEY_CURRENT_USER
, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE
, &hKey
) == ERROR_SUCCESS
)
165 dwBufLen
= sizeof(szTempLCID
);
166 dwRes
= RegQueryValueEx(hKey
, szLayoutNum
, NULL
, NULL
, (LPBYTE
)szTempLCID
, &dwBufLen
);
168 if (dwRes
!= ERROR_SUCCESS
)
177 // Look for a substitute of this layout
178 if (RegOpenKeyEx(HKEY_CURRENT_USER
, _T("Keyboard Layout\\Substitutes"), 0, KEY_QUERY_VALUE
, &hKey
) == ERROR_SUCCESS
)
180 dwBufLen
= sizeof(szTempLCID
);
182 if (RegQueryValueEx(hKey
, szTempLCID
, NULL
, NULL
, (LPBYTE
)szLCID
, &dwBufLen
) != ERROR_SUCCESS
)
184 // No substitute found, then use the old LCID
185 StringCchCopy(szLCID
, LCIDLength
, szTempLCID
);
192 // Substitutes key couldn't be opened, so use the old LCID
193 StringCchCopy(szLCID
, LCIDLength
, szTempLCID
);
200 GetLayoutIDByHkl(HKL hKl
, LPTSTR szLayoutID
, SIZE_T LayoutIDLength
)
203 FIXME!!! This way of getting layout ID incorrect!
204 This will not work correctly for 0001040a, 00010410, etc
206 StringCchPrintf(szLayoutID
, LayoutIDLength
, _T("%08x"), LOWORD(hKl
));
210 GetLayoutName(LPTSTR szLayoutNum
, LPTSTR szName
, SIZE_T NameLength
)
214 TCHAR szBuf
[MAX_PATH
], szDispName
[MAX_PATH
], szIndex
[MAX_PATH
], szPath
[MAX_PATH
];
215 TCHAR szLCID
[CCH_LAYOUT_ID
+ 1];
219 if (!GetLayoutID(szLayoutNum
, szLCID
, ARRAYSIZE(szLCID
)))
222 StringCchPrintf(szBuf
, ARRAYSIZE(szBuf
), _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID
);
224 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE
, (LPCTSTR
)szBuf
, 0, KEY_QUERY_VALUE
, &hKey
) == ERROR_SUCCESS
)
226 dwBufLen
= sizeof(szDispName
);
228 if (RegQueryValueEx(hKey
, _T("Layout Display Name"), NULL
, NULL
, (LPBYTE
)szDispName
, &dwBufLen
) == ERROR_SUCCESS
)
230 if (szDispName
[0] == '@')
232 size_t len
= _tcslen(szDispName
);
234 for (i
= 0; i
< len
; i
++)
236 if ((szDispName
[i
] == ',') && (szDispName
[i
+ 1] == '-'))
238 for (j
= i
+ 2, k
= 0; j
< _tcslen(szDispName
)+1; j
++, k
++)
240 szIndex
[k
] = szDispName
[j
];
242 szDispName
[i
- 1] = '\0';
245 else szDispName
[i
] = szDispName
[i
+ 1];
248 if (ExpandEnvironmentStrings(szDispName
, szPath
, ARRAYSIZE(szPath
)))
250 hLib
= LoadLibrary(szPath
);
253 if (LoadString(hLib
, _ttoi(szIndex
), szPath
, ARRAYSIZE(szPath
)) != 0)
255 StringCchCopy(szName
, NameLength
, szPath
);
266 dwBufLen
= NameLength
* sizeof(TCHAR
);
268 if (RegQueryValueEx(hKey
, _T("Layout Text"), NULL
, NULL
, (LPBYTE
)szName
, &dwBufLen
) == ERROR_SUCCESS
)
281 EnumWindowsProc(HWND hwnd
, LPARAM lParam
)
283 PostMessage(hwnd
, WM_INPUTLANGCHANGEREQUEST
, 0, lParam
);
288 ActivateLayout(HWND hwnd
, ULONG uLayoutNum
)
291 TCHAR szLayoutNum
[CCH_ULONG_DEC
+ 1];
292 TCHAR szLCID
[CCH_LAYOUT_ID
+ 1];
293 TCHAR szLangName
[MAX_PATH
];
295 _ultot(uLayoutNum
, szLayoutNum
, 10);
296 GetLayoutID(szLayoutNum
, szLCID
, ARRAYSIZE(szLCID
));
298 // Switch to the new keyboard layout
299 GetLocaleInfo((LANGID
)_tcstoul(szLCID
, NULL
, 16), LOCALE_SLANGUAGE
, (LPTSTR
)szLangName
, ARRAYSIZE(szLangName
));
300 UpdateTrayIcon(hwnd
, szLCID
, szLangName
);
301 hKl
= LoadKeyboardLayout(szLCID
, KLF_ACTIVATE
);
303 EnumWindows(EnumWindowsProc
, (LPARAM
) hKl
);
305 ulCurrentLayoutNum
= uLayoutNum
;
309 BuildLeftPopupMenu(VOID
)
313 DWORD dwIndex
, dwSize
;
314 TCHAR szLayoutNum
[CCH_ULONG_DEC
+ 1];
315 TCHAR szName
[MAX_PATH
];
317 hMenu
= CreatePopupMenu();
319 // Add the keyboard layouts to the popup menu
320 if (RegOpenKeyEx(HKEY_CURRENT_USER
, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE
, &hKey
) == ERROR_SUCCESS
)
322 for (dwIndex
= 0; ; dwIndex
++)
324 dwSize
= sizeof(szLayoutNum
);
325 if (RegEnumValue(hKey
, dwIndex
, szLayoutNum
, &dwSize
, NULL
, NULL
, NULL
, NULL
) != ERROR_SUCCESS
)
328 if (!GetLayoutName(szLayoutNum
, szName
, ARRAYSIZE(szName
)))
331 AppendMenu(hMenu
, MF_STRING
, _ttoi(szLayoutNum
), szName
);
334 CheckMenuItem(hMenu
, ulCurrentLayoutNum
, MF_CHECKED
);
345 hDllLib
= LoadLibrary(_T("kbsdll.dll"));
351 KbSwitchSetHooks
= (PKBSWITCHSETHOOKS
) GetProcAddress(hDllLib
, "KbSwitchSetHooks");
352 KbSwitchDeleteHooks
= (PKBSWITCHDELETEHOOKS
) GetProcAddress(hDllLib
, "KbSwitchDeleteHooks");
354 if (KbSwitchSetHooks
== NULL
|| KbSwitchDeleteHooks
== NULL
)
359 return KbSwitchSetHooks();
365 if (KbSwitchDeleteHooks
) KbSwitchDeleteHooks();
366 if (hDllLib
) FreeLibrary(hDllLib
);
372 TCHAR szLayoutNum
[3 + 1], szLayoutID
[CCH_LAYOUT_ID
+ 1];
373 ULONG Ret
= ulCurrentLayoutNum
;
375 _ultot(ulCurrentLayoutNum
, szLayoutNum
, 10);
376 if (!GetLayoutID(szLayoutNum
, szLayoutID
, ARRAYSIZE(szLayoutID
)))
381 _ultot(Ret
+ 1, szLayoutNum
, 10);
383 if (GetLayoutID(szLayoutNum
, szLayoutID
, ARRAYSIZE(szLayoutID
)))
389 _ultot(Ret
- 1, szLayoutNum
, 10);
390 if (GetLayoutID(szLayoutNum
, szLayoutID
, ARRAYSIZE(szLayoutID
)))
400 UpdateLanguageDisplay(HWND hwnd
, HKL hKl
)
402 static TCHAR szLCID
[MAX_PATH
], szLangName
[MAX_PATH
];
404 GetLayoutIDByHkl(hKl
, szLCID
, ARRAYSIZE(szLCID
));
405 GetLocaleInfo((LANGID
)_tcstoul(szLCID
, NULL
, 16), LOCALE_SLANGUAGE
, (LPTSTR
)szLangName
, ARRAYSIZE(szLangName
));
406 UpdateTrayIcon(hwnd
, szLCID
, szLangName
);
412 UpdateLanguageDisplayCurrent(HWND hwnd
, WPARAM wParam
)
414 return UpdateLanguageDisplay(hwnd
, GetKeyboardLayout(GetWindowThreadProcessId((HWND
)wParam
, 0)));
418 WndProc(HWND hwnd
, UINT Message
, WPARAM wParam
, LPARAM lParam
)
420 static HMENU hRightPopupMenu
;
421 static UINT s_uTaskbarRestart
;
429 hRightPopupMenu
= GetSubMenu(LoadMenu(hInst
, MAKEINTRESOURCE(IDR_POPUP
)), 0);
431 ActivateLayout(hwnd
, ulCurrentLayoutNum
);
432 s_uTaskbarRestart
= RegisterWindowMessage(TEXT("TaskbarCreated"));
437 case WM_LANG_CHANGED
:
439 return UpdateLanguageDisplay(hwnd
, (HKL
)lParam
);
444 ActivateLayout(hwnd
, GetNextLayout());
449 case WM_WINDOW_ACTIVATE
:
451 return UpdateLanguageDisplayCurrent(hwnd
, wParam
);
454 case WM_NOTIFYICONMSG
:
463 SetForegroundWindow(hwnd
);
465 if (lParam
== WM_LBUTTONUP
)
467 HMENU hLeftPopupMenu
;
468 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
469 hLeftPopupMenu
= BuildLeftPopupMenu();
470 TrackPopupMenu(hLeftPopupMenu
, 0, pt
.x
, pt
.y
, 0, hwnd
, NULL
);
471 DestroyMenu(hLeftPopupMenu
);
475 TrackPopupMenu(hRightPopupMenu
, 0, pt
.x
, pt
.y
, 0, hwnd
, NULL
);
478 PostMessage(hwnd
, WM_NULL
, 0, 0);
486 switch (LOWORD(wParam
))
489 SendMessage(hwnd
, WM_CLOSE
, 0, 0);
494 SHELLEXECUTEINFO shInputDll
= {0};
496 shInputDll
.cbSize
= sizeof(shInputDll
);
497 shInputDll
.hwnd
= hwnd
;
498 shInputDll
.lpVerb
= _T("open");
499 shInputDll
.lpFile
= _T("rundll32.exe");
500 shInputDll
.lpParameters
= _T("shell32.dll,Control_RunDLL input.dll");
502 if (!ShellExecuteEx(&shInputDll
))
503 MessageBox(hwnd
, _T("Can't start input.dll"), NULL
, MB_OK
| MB_ICONERROR
);
509 ActivateLayout(hwnd
, LOWORD(wParam
));
514 case WM_SETTINGCHANGE
:
516 if (wParam
== SPI_SETDEFAULTINPUTLANG
)
518 //FIXME: Should detect default language changes by CPL applet or by other tools and update UI
520 if (wParam
== SPI_SETNONCLIENTMETRICS
)
522 return UpdateLanguageDisplayCurrent(hwnd
, wParam
);
530 DestroyMenu(hRightPopupMenu
);
538 if(Message
== s_uTaskbarRestart
)
543 if (Message
== ShellHookMessage
&& wParam
== HSHELL_LANGUAGE
)
545 PostMessage(hwnd
, WM_LANG_CHANGED
, wParam
, lParam
);
549 return DefWindowProc(hwnd
, Message
, wParam
, lParam
);
553 _tWinMain(HINSTANCE hInstance
, HINSTANCE hPrevInst
, LPTSTR lpCmdLine
, INT nCmdShow
)
555 WNDCLASS WndClass
= {0};
560 switch (GetUserDefaultUILanguage())
562 case MAKELANGID(LANG_HEBREW
, SUBLANG_DEFAULT
):
563 SetProcessDefaultLayout(LAYOUT_RTL
);
569 hMutex
= CreateMutex(NULL
, FALSE
, szKbSwitcherName
);
573 if (GetLastError() == ERROR_ALREADY_EXISTS
)
580 hProcessHeap
= GetProcessHeap();
583 WndClass
.lpfnWndProc
= WndProc
;
584 WndClass
.cbClsExtra
= 0;
585 WndClass
.cbWndExtra
= 0;
586 WndClass
.hInstance
= hInstance
;
587 WndClass
.hIcon
= NULL
;
588 WndClass
.hCursor
= NULL
;
589 WndClass
.hbrBackground
= NULL
;
590 WndClass
.lpszMenuName
= NULL
;
591 WndClass
.lpszClassName
= szKbSwitcherName
;
593 if (!RegisterClass(&WndClass
))
599 hwnd
= CreateWindow(szKbSwitcherName
, NULL
, 0, 0, 0, 1, 1, HWND_DESKTOP
, NULL
, hInstance
, NULL
);
600 ShellHookMessage
= RegisterWindowMessage(L
"SHELLHOOK");
601 RegisterShellHookWindow(hwnd
);
603 while(GetMessage(&msg
,NULL
,0,0))
605 TranslateMessage(&msg
);
606 DispatchMessage(&msg
);