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