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