1 /*
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)
7 * Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
8 */
9
10 #include "kbswitch.h"
11 #include <shlobj.h>
12 #include <shlwapi_undoc.h>
13 #include <imm.h>
14 #include <imm32_undoc.h>
15
16 /*
17 * This program kbswitch is a mimic of Win2k's internat.exe.
18 * However, there are some differences.
19 *
20 * Comparing with WinNT4 ActivateKeyboardLayout, WinXP ActivateKeyboardLayout has
21 * process boundary, so we cannot activate the IME keyboard layout from the outer process.
22 * It needs special care.
23 *
24 * We use global hook by our kbsdll.dll, to watch the shell and the windows.
25 *
26 * It might not work correctly on Vista+ because keyboard layout change notification
27 * won't be generated in Vista+.
28 */
29
30 #define WM_NOTIFYICONMSG (WM_USER + 248)
31
32 PKBSWITCHSETHOOKS KbSwitchSetHooks = NULL;
33 PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks = NULL;
34 UINT ShellHookMessage = 0;
35
36 HINSTANCE hInst;
37 HANDLE hProcessHeap;
38 HMODULE g_hHookDLL = NULL;
39 INT g_nCurrentLayoutNum = 1;
40 HICON g_hTrayIcon = NULL;
41 HWND g_hwndLastActive = NULL;
42 INT g_cKLs = 0;
43 HKL g_ahKLs[64];
44
45 typedef struct
46 {
47 DWORD dwLayoutId;
48 HKL hKL;
49 TCHAR szKLID[CCH_LAYOUT_ID + 1];
50 } SPECIAL_ID, *PSPECIAL_ID;
51
52 SPECIAL_ID g_SpecialIds[80];
53 INT g_cSpecialIds = 0;
54
LoadSpecialIds(VOID)55 static VOID LoadSpecialIds(VOID)
56 {
57 TCHAR szKLID[KL_NAMELENGTH], szLayoutId[16];
58 DWORD dwSize, dwIndex;
59 HKEY hKey, hLayoutKey;
60
61 g_cSpecialIds = 0;
62
63 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
64 TEXT("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts"),
65 0, KEY_READ, &hKey) != ERROR_SUCCESS)
66 {
67 return;
68 }
69
70 for (dwIndex = 0; dwIndex < 1000; ++dwIndex)
71 {
72 dwSize = ARRAYSIZE(szKLID);
73 if (RegEnumKeyEx(hKey, dwIndex, szKLID, &dwSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
74 break;
75
76 if (RegOpenKeyEx(hKey, szKLID, 0, KEY_READ, &hLayoutKey) != ERROR_SUCCESS)
77 continue;
78
79 dwSize = sizeof(szLayoutId);
80 if (RegQueryValueEx(hLayoutKey, TEXT("Layout Id"), NULL, NULL,
81 (LPBYTE)szLayoutId, &dwSize) == ERROR_SUCCESS)
82 {
83 DWORD dwKLID = _tcstoul(szKLID, NULL, 16);
84 WORD wLangId = LOWORD(dwKLID), wLayoutId = LOWORD(_tcstoul(szLayoutId, NULL, 16));
85 HKL hKL = (HKL)(LONG_PTR)(SPECIAL_MASK | MAKELONG(wLangId, wLayoutId));
86
87 /* Add a special ID */
88 g_SpecialIds[g_cSpecialIds].dwLayoutId = wLayoutId;
89 g_SpecialIds[g_cSpecialIds].hKL = hKL;
90 StringCchCopy(g_SpecialIds[g_cSpecialIds].szKLID,
91 ARRAYSIZE(g_SpecialIds[g_cSpecialIds].szKLID), szKLID);
92 ++g_cSpecialIds;
93 }
94
95 RegCloseKey(hLayoutKey);
96
97 if (g_cSpecialIds >= ARRAYSIZE(g_SpecialIds))
98 {
99 OutputDebugStringA("g_SpecialIds is full!");
100 break;
101 }
102 }
103
104 RegCloseKey(hKey);
105 }
106
107 static VOID
GetKLIDFromHKL(HKL hKL,LPTSTR szKLID,SIZE_T KLIDLength)108 GetKLIDFromHKL(HKL hKL, LPTSTR szKLID, SIZE_T KLIDLength)
109 {
110 szKLID[0] = 0;
111
112 if (IS_IME_HKL(hKL))
113 {
114 StringCchPrintf(szKLID, KLIDLength, _T("%08lx"), (DWORD)(DWORD_PTR)hKL);
115 return;
116 }
117
118 if (IS_SPECIAL_HKL(hKL))
119 {
120 INT i;
121 for (i = 0; i < g_cSpecialIds; ++i)
122 {
123 if (g_SpecialIds[i].hKL == hKL)
124 {
125 StringCchCopy(szKLID, KLIDLength, g_SpecialIds[i].szKLID);
126 return;
127 }
128 }
129 }
130 else
131 {
132 StringCchPrintf(szKLID, KLIDLength, _T("%08lx"), LOWORD(hKL));
133 }
134 }
135
UpdateLayoutList(HKL hKL OPTIONAL)136 static VOID UpdateLayoutList(HKL hKL OPTIONAL)
137 {
138 INT iKL;
139
140 if (!hKL)
141 {
142 HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow());
143 DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL);
144 hKL = GetKeyboardLayout(dwTID);
145 }
146
147 g_cKLs = GetKeyboardLayoutList(ARRAYSIZE(g_ahKLs), g_ahKLs);
148
149 g_nCurrentLayoutNum = -1;
150 for (iKL = 0; iKL < g_cKLs; ++iKL)
151 {
152 if (g_ahKLs[iKL] == hKL)
153 {
154 g_nCurrentLayoutNum = iKL + 1;
155 break;
156 }
157 }
158
159 if (g_nCurrentLayoutNum == -1 && g_cKLs < ARRAYSIZE(g_ahKLs))
160 {
161 g_nCurrentLayoutNum = g_cKLs;
162 g_ahKLs[g_cKLs++] = hKL;
163 }
164 }
165
GetHKLFromLayoutNum(INT nLayoutNum)166 static HKL GetHKLFromLayoutNum(INT nLayoutNum)
167 {
168 if (0 <= (nLayoutNum - 1) && (nLayoutNum - 1) < g_cKLs)
169 {
170 return g_ahKLs[nLayoutNum - 1];
171 }
172 else
173 {
174 HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow());
175 DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL);
176 return GetKeyboardLayout(dwTID);
177 }
178 }
179
180 static VOID
GetKLIDFromLayoutNum(INT nLayoutNum,LPTSTR szKLID,SIZE_T KLIDLength)181 GetKLIDFromLayoutNum(INT nLayoutNum, LPTSTR szKLID, SIZE_T KLIDLength)
182 {
183 GetKLIDFromHKL(GetHKLFromLayoutNum(nLayoutNum), szKLID, KLIDLength);
184 }
185
186 static BOOL
GetSystemLibraryPath(LPTSTR szPath,SIZE_T cchPath,LPCTSTR FileName)187 GetSystemLibraryPath(LPTSTR szPath, SIZE_T cchPath, LPCTSTR FileName)
188 {
189 if (!GetSystemDirectory(szPath, cchPath))
190 return FALSE;
191
192 StringCchCat(szPath, cchPath, TEXT("\\"));
193 StringCchCat(szPath, cchPath, FileName);
194 return TRUE;
195 }
196
197 static BOOL
GetLayoutName(INT nLayoutNum,LPTSTR szName,SIZE_T NameLength)198 GetLayoutName(INT nLayoutNum, LPTSTR szName, SIZE_T NameLength)
199 {
200 HKEY hKey;
201 HRESULT hr;
202 DWORD dwBufLen;
203 TCHAR szBuf[MAX_PATH], szKLID[CCH_LAYOUT_ID + 1];
204
205 GetKLIDFromLayoutNum(nLayoutNum, szKLID, ARRAYSIZE(szKLID));
206
207 StringCchPrintf(szBuf, ARRAYSIZE(szBuf),
208 _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szKLID);
209
210 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
211 return FALSE;
212
213 /* Use "Layout Display Name" value as an entry name if possible */
214 hr = SHLoadRegUIString(hKey, _T("Layout Display Name"), szName, NameLength);
215 if (SUCCEEDED(hr))
216 {
217 RegCloseKey(hKey);
218 return TRUE;
219 }
220
221 /* Otherwise, use "Layout Text" value as an entry name */
222 dwBufLen = NameLength * sizeof(TCHAR);
223 if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL,
224 (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
225 {
226 RegCloseKey(hKey);
227 return TRUE;
228 }
229
230 RegCloseKey(hKey);
231 return FALSE;
232 }
233
GetImeFile(LPTSTR szImeFile,SIZE_T cchImeFile,LPCTSTR szKLID)234 static BOOL GetImeFile(LPTSTR szImeFile, SIZE_T cchImeFile, LPCTSTR szKLID)
235 {
236 HKEY hKey;
237 DWORD dwBufLen;
238 TCHAR szBuf[MAX_PATH];
239
240 szImeFile[0] = UNICODE_NULL;
241
242 if (_tcslen(szKLID) != CCH_LAYOUT_ID)
243 return FALSE; /* Invalid LCID */
244
245 if (szKLID[0] != TEXT('E') && szKLID[0] != TEXT('e'))
246 return FALSE; /* Not an IME HKL */
247
248 StringCchPrintf(szBuf, ARRAYSIZE(szBuf),
249 _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szKLID);
250
251 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
252 return FALSE;
253
254 dwBufLen = cchImeFile * sizeof(TCHAR);
255 if (RegQueryValueEx(hKey, _T("IME File"), NULL, NULL,
256 (LPBYTE)szImeFile, &dwBufLen) != ERROR_SUCCESS)
257 {
258 szImeFile[0] = UNICODE_NULL;
259 }
260
261 RegCloseKey(hKey);
262
263 return (szImeFile[0] != UNICODE_NULL);
264 }
265
266 typedef struct tagLOAD_ICON
267 {
268 INT cxIcon, cyIcon;
269 HICON hIcon;
270 } LOAD_ICON, *PLOAD_ICON;
271
272 static BOOL CALLBACK
EnumResNameProc(HMODULE hModule,LPCTSTR lpszType,LPTSTR lpszName,LPARAM lParam)273 EnumResNameProc(
274 HMODULE hModule,
275 LPCTSTR lpszType,
276 LPTSTR lpszName,
277 LPARAM lParam)
278 {
279 PLOAD_ICON pLoadIcon = (PLOAD_ICON)lParam;
280 pLoadIcon->hIcon = (HICON)LoadImage(hModule, lpszName, IMAGE_ICON,
281 pLoadIcon->cxIcon, pLoadIcon->cyIcon,
282 LR_DEFAULTCOLOR);
283 if (pLoadIcon->hIcon)
284 return FALSE; /* Stop enumeration */
285 return TRUE;
286 }
287
FakeExtractIcon(LPCTSTR szIconPath,INT cxIcon,INT cyIcon)288 static HICON FakeExtractIcon(LPCTSTR szIconPath, INT cxIcon, INT cyIcon)
289 {
290 LOAD_ICON LoadIcon = { cxIcon, cyIcon, NULL };
291 HMODULE hImeDLL = LoadLibraryEx(szIconPath, NULL, LOAD_LIBRARY_AS_DATAFILE);
292 if (hImeDLL)
293 {
294 EnumResourceNames(hImeDLL, RT_GROUP_ICON, EnumResNameProc, (LPARAM)&LoadIcon);
295 FreeLibrary(hImeDLL);
296 }
297 return LoadIcon.hIcon;
298 }
299
BitmapFromIcon(HICON hIcon)300 static HBITMAP BitmapFromIcon(HICON hIcon)
301 {
302 HDC hdcScreen = GetDC(NULL);
303 HDC hdc = CreateCompatibleDC(hdcScreen);
304 INT cxIcon = GetSystemMetrics(SM_CXSMICON);
305 INT cyIcon = GetSystemMetrics(SM_CYSMICON);
306 HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon);
307 HGDIOBJ hbmOld;
308
309 if (hbm != NULL)
310 {
311 hbmOld = SelectObject(hdc, hbm);
312 DrawIconEx(hdc, 0, 0, hIcon, cxIcon, cyIcon, 0, GetSysColorBrush(COLOR_MENU), DI_NORMAL);
313 SelectObject(hdc, hbmOld);
314 }
315
316 DeleteDC(hdc);
317 ReleaseDC(NULL, hdcScreen);
318 return hbm;
319 }
320
321 static HICON
CreateTrayIcon(LPTSTR szKLID,LPCTSTR szImeFile OPTIONAL)322 CreateTrayIcon(LPTSTR szKLID, LPCTSTR szImeFile OPTIONAL)
323 {
324 LANGID LangID;
325 TCHAR szBuf[4];
326 HDC hdcScreen, hdc;
327 HBITMAP hbmColor, hbmMono, hBmpOld;
328 HFONT hFont, hFontOld;
329 LOGFONT lf;
330 RECT rect;
331 ICONINFO IconInfo;
332 HICON hIcon;
333 INT cxIcon = GetSystemMetrics(SM_CXSMICON);
334 INT cyIcon = GetSystemMetrics(SM_CYSMICON);
335 TCHAR szPath[MAX_PATH];
336
337 if (szImeFile && szImeFile[0])
338 {
339 if (GetSystemLibraryPath(szPath, ARRAYSIZE(szPath), szImeFile))
340 return FakeExtractIcon(szPath, cxIcon, cyIcon);
341 }
342
343 /* Getting "EN", "FR", etc. from English, French, ... */
344 LangID = LANGIDFROMLCID(_tcstoul(szKLID, NULL, 16));
345 if (GetLocaleInfo(LangID,
346 LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
347 szBuf,
348 ARRAYSIZE(szBuf)) == 0)
349 {
350 szBuf[0] = szBuf[1] = _T('?');
351 }
352 szBuf[2] = 0; /* Truncate the identifier to two characters: "ENG" --> "EN" etc. */
353
354 /* Create hdc, hbmColor and hbmMono */
355 hdcScreen = GetDC(NULL);
356 hdc = CreateCompatibleDC(hdcScreen);
357 hbmColor = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon);
358 ReleaseDC(NULL, hdcScreen);
359 hbmMono = CreateBitmap(cxIcon, cyIcon, 1, 1, NULL);
360
361 /* Checking NULL */
362 if (!hdc || !hbmColor || !hbmMono)
363 {
364 if (hbmMono)
365 DeleteObject(hbmMono);
366 if (hbmColor)
367 DeleteObject(hbmColor);
368 if (hdc)
369 DeleteDC(hdc);
370 return NULL;
371 }
372
373 /* Create a font */
374 hFont = NULL;
375 if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0))
376 {
377 /* Override the current size with something manageable */
378 lf.lfHeight = -11;
379 lf.lfWidth = 0;
380 hFont = CreateFontIndirect(&lf);
381 }
382 if (!hFont)
383 hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
384
385 SetRect(&rect, 0, 0, cxIcon, cyIcon);
386
387 /* Draw hbmColor */
388 hBmpOld = SelectObject(hdc, hbmColor);
389 SetDCBrushColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
390 FillRect(hdc, &rect, (HBRUSH)GetStockObject(DC_BRUSH));
391 hFontOld = SelectObject(hdc, hFont);
392 SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
393 SetBkMode(hdc, TRANSPARENT);
394 DrawText(hdc, szBuf, 2, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
395 SelectObject(hdc, hFontOld);
396
397 /* Fill hbmMono with black */
398 SelectObject(hdc, hbmMono);
399 PatBlt(hdc, 0, 0, cxIcon, cyIcon, BLACKNESS);
400 SelectObject(hdc, hBmpOld);
401
402 /* Create an icon from hbmColor and hbmMono */
403 IconInfo.fIcon = TRUE;
404 IconInfo.xHotspot = IconInfo.yHotspot = 0;
405 IconInfo.hbmColor = hbmColor;
406 IconInfo.hbmMask = hbmMono;
407 hIcon = CreateIconIndirect(&IconInfo);
408
409 /* Clean up */
410 DeleteObject(hFont);
411 DeleteObject(hbmMono);
412 DeleteObject(hbmColor);
413 DeleteDC(hdc);
414
415 return hIcon;
416 }
417
418 static VOID
AddTrayIcon(HWND hwnd)419 AddTrayIcon(HWND hwnd)
420 {
421 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
422 TCHAR szKLID[CCH_LAYOUT_ID + 1], szName[MAX_PATH], szImeFile[80];
423
424 GetKLIDFromLayoutNum(g_nCurrentLayoutNum, szKLID, ARRAYSIZE(szKLID));
425 GetLayoutName(g_nCurrentLayoutNum, szName, ARRAYSIZE(szName));
426 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID);
427
428 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
429 tnid.hIcon = CreateTrayIcon(szKLID, szImeFile);
430 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
431
432 Shell_NotifyIcon(NIM_ADD, &tnid);
433
434 if (g_hTrayIcon)
435 DestroyIcon(g_hTrayIcon);
436 g_hTrayIcon = tnid.hIcon;
437 }
438
439 static VOID
DeleteTrayIcon(HWND hwnd)440 DeleteTrayIcon(HWND hwnd)
441 {
442 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1 };
443 Shell_NotifyIcon(NIM_DELETE, &tnid);
444
445 if (g_hTrayIcon)
446 {
447 DestroyIcon(g_hTrayIcon);
448 g_hTrayIcon = NULL;
449 }
450 }
451
452 static VOID
UpdateTrayIcon(HWND hwnd,LPTSTR szKLID,LPTSTR szName)453 UpdateTrayIcon(HWND hwnd, LPTSTR szKLID, LPTSTR szName)
454 {
455 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
456 TCHAR szImeFile[80];
457
458 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID);
459
460 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
461 tnid.hIcon = CreateTrayIcon(szKLID, szImeFile);
462 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
463
464 Shell_NotifyIcon(NIM_MODIFY, &tnid);
465
466 if (g_hTrayIcon)
467 DestroyIcon(g_hTrayIcon);
468 g_hTrayIcon = tnid.hIcon;
469 }
470
471 static BOOL CALLBACK
EnumWindowsProc(HWND hwnd,LPARAM lParam)472 EnumWindowsProc(HWND hwnd, LPARAM lParam)
473 {
474 PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, INPUTLANGCHANGE_SYSCHARSET, lParam);
475 return TRUE;
476 }
477
478 static VOID
ActivateLayout(HWND hwnd,ULONG uLayoutNum,HWND hwndTarget OPTIONAL,BOOL bNoActivate)479 ActivateLayout(HWND hwnd, ULONG uLayoutNum, HWND hwndTarget OPTIONAL, BOOL bNoActivate)
480 {
481 HKL hKl;
482 TCHAR szKLID[CCH_LAYOUT_ID + 1], szLangName[MAX_PATH];
483 LANGID LangID;
484
485 /* The layout number starts from one. Zero is invalid */
486 if (uLayoutNum == 0 || uLayoutNum > 0xFF) /* Invalid */
487 return;
488
489 GetKLIDFromLayoutNum(uLayoutNum, szKLID, ARRAYSIZE(szKLID));
490 LangID = (LANGID)_tcstoul(szKLID, NULL, 16);
491
492 /* Switch to the new keyboard layout */
493 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
494 UpdateTrayIcon(hwnd, szKLID, szLangName);
495
496 if (hwndTarget && !bNoActivate)
497 SetForegroundWindow(hwndTarget);
498
499 hKl = LoadKeyboardLayout(szKLID, KLF_ACTIVATE);
500 if (hKl)
501 ActivateKeyboardLayout(hKl, KLF_SETFORPROCESS);
502
503 /* Post WM_INPUTLANGCHANGEREQUEST */
504 if (hwndTarget)
505 {
506 PostMessage(hwndTarget, WM_INPUTLANGCHANGEREQUEST,
507 INPUTLANGCHANGE_SYSCHARSET, (LPARAM)hKl);
508 }
509 else
510 {
511 EnumWindows(EnumWindowsProc, (LPARAM) hKl);
512 }
513
514 g_nCurrentLayoutNum = uLayoutNum;
515 }
516
517 static HMENU
BuildLeftPopupMenu(VOID)518 BuildLeftPopupMenu(VOID)
519 {
520 HMENU hMenu = CreatePopupMenu();
521 TCHAR szName[MAX_PATH], szKLID[CCH_LAYOUT_ID + 1], szImeFile[80];
522 HICON hIcon;
523 MENUITEMINFO mii = { sizeof(mii) };
524 INT iKL;
525
526 for (iKL = 0; iKL < g_cKLs; ++iKL)
527 {
528 GetKLIDFromHKL(g_ahKLs[iKL], szKLID, ARRAYSIZE(szKLID));
529 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID);
530
531 if (!GetLayoutName(iKL + 1, szName, ARRAYSIZE(szName)))
532 continue;
533
534 mii.fMask = MIIM_ID | MIIM_STRING;
535 mii.wID = iKL + 1;
536 mii.dwTypeData = szName;
537
538 hIcon = CreateTrayIcon(szKLID, szImeFile);
539 if (hIcon)
540 {
541 mii.hbmpItem = BitmapFromIcon(hIcon);
542 if (mii.hbmpItem)
543 mii.fMask |= MIIM_BITMAP;
544 }
545
546 InsertMenuItem(hMenu, -1, TRUE, &mii);
547 DestroyIcon(hIcon);
548 }
549
550 CheckMenuItem(hMenu, g_nCurrentLayoutNum, MF_CHECKED);
551
552 return hMenu;
553 }
554
555 BOOL
SetHooks(VOID)556 SetHooks(VOID)
557 {
558 g_hHookDLL = LoadLibrary(_T("kbsdll.dll"));
559 if (!g_hHookDLL)
560 {
561 return FALSE;
562 }
563
564 KbSwitchSetHooks = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchSetHooks");
565 KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchDeleteHooks");
566
567 if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL)
568 {
569 return FALSE;
570 }
571
572 return KbSwitchSetHooks();
573 }
574
575 VOID
DeleteHooks(VOID)576 DeleteHooks(VOID)
577 {
578 if (KbSwitchDeleteHooks)
579 {
580 KbSwitchDeleteHooks();
581 KbSwitchDeleteHooks = NULL;
582 }
583 if (g_hHookDLL)
584 {
585 FreeLibrary(g_hHookDLL);
586 g_hHookDLL = NULL;
587 }
588 }
589
GetLayoutNum(HKL hKL)590 static UINT GetLayoutNum(HKL hKL)
591 {
592 INT iKL;
593
594 for (iKL = 0; iKL < g_cKLs; ++iKL)
595 {
596 if (g_ahKLs[iKL] == hKL)
597 return iKL + 1;
598 }
599
600 return 0;
601 }
602
603 ULONG
GetNextLayout(VOID)604 GetNextLayout(VOID)
605 {
606 return (g_nCurrentLayoutNum % g_cKLs) + 1;
607 }
608
609 UINT
UpdateLanguageDisplay(HWND hwnd,HKL hKL)610 UpdateLanguageDisplay(HWND hwnd, HKL hKL)
611 {
612 TCHAR szKLID[MAX_PATH], szLangName[MAX_PATH];
613 LANGID LangID;
614
615 GetKLIDFromHKL(hKL, szKLID, ARRAYSIZE(szKLID));
616 LangID = (LANGID)_tcstoul(szKLID, NULL, 16);
617 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
618 UpdateTrayIcon(hwnd, szKLID, szLangName);
619 g_nCurrentLayoutNum = GetLayoutNum(hKL);
620
621 return 0;
622 }
623
624 HWND
GetTargetWindow(HWND hwndFore)625 GetTargetWindow(HWND hwndFore)
626 {
627 TCHAR szClass[64];
628 HWND hwndIME;
629 HWND hwndTarget = hwndFore;
630 if (hwndTarget == NULL)
631 hwndTarget = GetForegroundWindow();
632
633 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass));
634 if (_tcsicmp(szClass, szKbSwitcherName) == 0)
635 hwndTarget = g_hwndLastActive;
636
637 hwndIME = ImmGetDefaultIMEWnd(hwndTarget);
638 return (hwndIME ? hwndIME : hwndTarget);
639 }
640
641 UINT
UpdateLanguageDisplayCurrent(HWND hwnd,HWND hwndFore)642 UpdateLanguageDisplayCurrent(HWND hwnd, HWND hwndFore)
643 {
644 DWORD dwThreadID = GetWindowThreadProcessId(GetTargetWindow(hwndFore), NULL);
645 HKL hKL = GetKeyboardLayout(dwThreadID);
646 UpdateLanguageDisplay(hwnd, hKL);
647
648 return 0;
649 }
650
RememberLastActive(HWND hwnd,HWND hwndFore)651 static BOOL RememberLastActive(HWND hwnd, HWND hwndFore)
652 {
653 TCHAR szClass[64];
654
655 hwndFore = GetAncestor(hwndFore, GA_ROOT);
656
657 if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, ARRAYSIZE(szClass)))
658 return FALSE;
659
660 if (_tcsicmp(szClass, szKbSwitcherName) == 0 ||
661 _tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0)
662 {
663 return FALSE; /* Special window */
664 }
665
666 /* FIXME: CONWND needs special handling */
667 if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
668 {
669 HKL hKL = GetKeyboardLayout(0);
670 UpdateLanguageDisplay(hwnd, hKL);
671 }
672
673 g_hwndLastActive = hwndFore;
674 return TRUE;
675 }
676
677 LRESULT CALLBACK
WndProc(HWND hwnd,UINT Message,WPARAM wParam,LPARAM lParam)678 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
679 {
680 static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL;
681 static UINT s_uTaskbarRestart;
682 POINT pt;
683 HMENU hLeftPopupMenu;
684
685 switch (Message)
686 {
687 case WM_CREATE:
688 {
689 if (!SetHooks())
690 {
691 MessageBox(NULL, TEXT("SetHooks failed."), NULL, MB_ICONERROR);
692 return -1;
693 }
694
695 LoadSpecialIds();
696
697 UpdateLayoutList(NULL);
698 AddTrayIcon(hwnd);
699
700 ActivateLayout(hwnd, g_nCurrentLayoutNum, NULL, TRUE);
701 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
702 break;
703 }
704
705 case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */
706 {
707 UpdateLayoutList((HKL)lParam);
708 UpdateLanguageDisplay(hwnd, (HKL)lParam);
709 break;
710 }
711
712 case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */
713 {
714 HWND hwndFore = GetForegroundWindow();
715 if (RememberLastActive(hwnd, hwndFore))
716 return UpdateLanguageDisplayCurrent(hwnd, hwndFore);
717 break;
718 }
719
720 case WM_NOTIFYICONMSG:
721 {
722 switch (lParam)
723 {
724 case WM_RBUTTONUP:
725 case WM_LBUTTONUP:
726 {
727 UpdateLayoutList(NULL);
728
729 GetCursorPos(&pt);
730 SetForegroundWindow(hwnd);
731
732 if (lParam == WM_LBUTTONUP)
733 {
734 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
735 hLeftPopupMenu = BuildLeftPopupMenu();
736 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
737 DestroyMenu(hLeftPopupMenu);
738 }
739 else
740 {
741 if (!s_hRightPopupMenu)
742 {
743 s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP));
744 s_hRightPopupMenu = GetSubMenu(s_hMenu, 0);
745 }
746 TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
747 }
748
749 PostMessage(hwnd, WM_NULL, 0, 0);
750 break;
751 }
752 }
753 break;
754 }
755
756 case WM_COMMAND:
757 switch (LOWORD(wParam))
758 {
759 case ID_EXIT:
760 {
761 PostMessage(hwnd, WM_CLOSE, 0, 0);
762 break;
763 }
764
765 case ID_PREFERENCES:
766 {
767 INT_PTR ret = (INT_PTR)ShellExecute(hwnd, NULL,
768 TEXT("control.exe"), TEXT("input.dll"),
769 NULL, SW_SHOWNORMAL);
770 if (ret <= 32)
771 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_ICONERROR);
772 break;
773 }
774
775 case ID_NEXTLAYOUT:
776 {
777 HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL;
778 DWORD dwThreadID;
779 HKL hKL;
780 UINT uNum;
781 TCHAR szClass[64];
782 BOOL bCONWND = FALSE;
783
784 if (hwndTarget == NULL)
785 hwndTarget = g_hwndLastActive;
786
787 /* FIXME: CONWND needs special handling */
788 if (hwndTarget &&
789 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)) &&
790 _tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
791 {
792 bCONWND = TRUE;
793 hwndTargetSave = hwndTarget;
794 hwndTarget = NULL;
795 }
796
797 if (hwndTarget)
798 {
799 dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL);
800 hKL = GetKeyboardLayout(dwThreadID);
801 uNum = GetLayoutNum(hKL);
802 if (uNum != 0)
803 g_nCurrentLayoutNum = uNum;
804 }
805
806 ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE);
807
808 /* FIXME: CONWND needs special handling */
809 if (bCONWND)
810 ActivateLayout(hwnd, g_nCurrentLayoutNum, hwndTargetSave, TRUE);
811
812 break;
813 }
814
815 default:
816 {
817 if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000)
818 {
819 if (!IsWindow(g_hwndLastActive))
820 {
821 g_hwndLastActive = NULL;
822 }
823 ActivateLayout(hwnd, LOWORD(wParam), g_hwndLastActive, FALSE);
824 }
825 break;
826 }
827 }
828 break;
829
830 case WM_SETTINGCHANGE:
831 {
832 if (wParam == SPI_SETNONCLIENTMETRICS)
833 {
834 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
835 break;
836 }
837 }
838 break;
839
840 case WM_DESTROY:
841 {
842 DeleteHooks();
843 DestroyMenu(s_hMenu);
844 DeleteTrayIcon(hwnd);
845 PostQuitMessage(0);
846 break;
847 }
848
849 default:
850 {
851 if (Message == s_uTaskbarRestart)
852 {
853 UpdateLayoutList(NULL);
854 AddTrayIcon(hwnd);
855 break;
856 }
857 else if (Message == ShellHookMessage)
858 {
859 if (wParam == HSHELL_LANGUAGE)
860 PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
861 else if (wParam == HSHELL_WINDOWACTIVATED)
862 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
863
864 break;
865 }
866 return DefWindowProc(hwnd, Message, wParam, lParam);
867 }
868 }
869
870 return 0;
871 }
872
873 INT WINAPI
_tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInst,LPTSTR lpCmdLine,INT nCmdShow)874 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
875 {
876 WNDCLASS WndClass;
877 MSG msg;
878 HANDLE hMutex;
879 HWND hwnd;
880
881 switch (GetUserDefaultUILanguage())
882 {
883 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
884 SetProcessDefaultLayout(LAYOUT_RTL);
885 break;
886 default:
887 break;
888 }
889
890 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
891 if (!hMutex)
892 return 1;
893
894 if (GetLastError() == ERROR_ALREADY_EXISTS)
895 {
896 CloseHandle(hMutex);
897 return 1;
898 }
899
900 hInst = hInstance;
901 hProcessHeap = GetProcessHeap();
902
903 ZeroMemory(&WndClass, sizeof(WndClass));
904 WndClass.lpfnWndProc = WndProc;
905 WndClass.hInstance = hInstance;
906 WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
907 WndClass.lpszClassName = szKbSwitcherName;
908 if (!RegisterClass(&WndClass))
909 {
910 CloseHandle(hMutex);
911 return 1;
912 }
913
914 hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
915 ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
916 RegisterShellHookWindow(hwnd);
917
918 while (GetMessage(&msg, NULL, 0, 0))
919 {
920 TranslateMessage(&msg);
921 DispatchMessage(&msg);
922 }
923
924 CloseHandle(hMutex);
925 return 0;
926 }
927