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