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 HICON
240 CreateTrayIcon(LPTSTR szLCID, LPCTSTR szImeFile OPTIONAL)
241 {
242     LANGID LangID;
243     TCHAR szBuf[4];
244     HDC hdcScreen, hdc;
245     HBITMAP hbmColor, hbmMono, hBmpOld;
246     HFONT hFont, hFontOld;
247     LOGFONT lf;
248     RECT rect;
249     ICONINFO IconInfo;
250     HICON hIcon;
251     INT cxIcon = GetSystemMetrics(SM_CXSMICON);
252     INT cyIcon = GetSystemMetrics(SM_CYSMICON);
253     TCHAR szPath[MAX_PATH];
254 
255     if (szImeFile && szImeFile[0])
256     {
257         if (GetSystemLibraryPath(szPath, ARRAYSIZE(szPath), szImeFile))
258             return FakeExtractIcon(szPath, cxIcon, cyIcon);
259     }
260 
261     /* Getting "EN", "FR", etc. from English, French, ... */
262     LangID = LANGIDFROMLCID(_tcstoul(szLCID, NULL, 16));
263     if (GetLocaleInfo(LangID,
264                       LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE,
265                       szBuf,
266                       ARRAYSIZE(szBuf)) == 0)
267     {
268         szBuf[0] = szBuf[1] = _T('?');
269     }
270     szBuf[2] = 0; /* Truncate the identifier to two characters: "ENG" --> "EN" etc. */
271 
272     /* Create hdc, hbmColor and hbmMono */
273     hdcScreen = GetDC(NULL);
274     hdc = CreateCompatibleDC(hdcScreen);
275     hbmColor = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon);
276     ReleaseDC(NULL, hdcScreen);
277     hbmMono = CreateBitmap(cxIcon, cyIcon, 1, 1, NULL);
278 
279     /* Checking NULL */
280     if (!hdc || !hbmColor || !hbmMono)
281     {
282         if (hbmMono)
283             DeleteObject(hbmMono);
284         if (hbmColor)
285             DeleteObject(hbmColor);
286         if (hdc)
287             DeleteDC(hdc);
288         return NULL;
289     }
290 
291     /* Create a font */
292     hFont = NULL;
293     if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0))
294     {
295         /* Override the current size with something manageable */
296         lf.lfHeight = -11;
297         lf.lfWidth = 0;
298         hFont = CreateFontIndirect(&lf);
299     }
300     if (!hFont)
301         hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
302 
303     SetRect(&rect, 0, 0, cxIcon, cyIcon);
304 
305     /* Draw hbmColor */
306     hBmpOld = SelectObject(hdc, hbmColor);
307     SetDCBrushColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
308     FillRect(hdc, &rect, (HBRUSH)GetStockObject(DC_BRUSH));
309     hFontOld = SelectObject(hdc, hFont);
310     SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
311     SetBkMode(hdc, TRANSPARENT);
312     DrawText(hdc, szBuf, 2, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
313     SelectObject(hdc, hFontOld);
314 
315     /* Fill hbmMono with black */
316     SelectObject(hdc, hbmMono);
317     PatBlt(hdc, 0, 0, cxIcon, cyIcon, BLACKNESS);
318     SelectObject(hdc, hBmpOld);
319 
320     /* Create an icon from hbmColor and hbmMono */
321     IconInfo.fIcon = TRUE;
322     IconInfo.xHotspot = IconInfo.yHotspot = 0;
323     IconInfo.hbmColor = hbmColor;
324     IconInfo.hbmMask = hbmMono;
325     hIcon = CreateIconIndirect(&IconInfo);
326 
327     /* Clean up */
328     DeleteObject(hFont);
329     DeleteObject(hbmMono);
330     DeleteObject(hbmColor);
331     DeleteDC(hdc);
332 
333     return hIcon;
334 }
335 
336 static VOID
337 AddTrayIcon(HWND hwnd)
338 {
339     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
340     TCHAR szLCID[CCH_LAYOUT_ID + 1], szName[MAX_PATH];
341     TCHAR szImeFile[80];
342 
343     GetLayoutID(_T("1"), szLCID, ARRAYSIZE(szLCID));
344     GetLayoutName(_T("1"), szName, ARRAYSIZE(szName));
345     GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szLCID);
346 
347     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
348     tnid.hIcon = CreateTrayIcon(szLCID, szImeFile);
349     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
350 
351     Shell_NotifyIcon(NIM_ADD, &tnid);
352 
353     if (g_hTrayIcon)
354         DestroyIcon(g_hTrayIcon);
355     g_hTrayIcon = tnid.hIcon;
356 }
357 
358 static VOID
359 DeleteTrayIcon(HWND hwnd)
360 {
361     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1 };
362     Shell_NotifyIcon(NIM_DELETE, &tnid);
363 
364     if (g_hTrayIcon)
365     {
366         DestroyIcon(g_hTrayIcon);
367         g_hTrayIcon = NULL;
368     }
369 }
370 
371 static VOID
372 UpdateTrayIcon(HWND hwnd, LPTSTR szLCID, LPTSTR szName)
373 {
374     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
375     TCHAR szImeFile[80];
376 
377     GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szLCID);
378 
379     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
380     tnid.hIcon = CreateTrayIcon(szLCID, szImeFile);
381     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
382 
383     Shell_NotifyIcon(NIM_MODIFY, &tnid);
384 
385     if (g_hTrayIcon)
386         DestroyIcon(g_hTrayIcon);
387     g_hTrayIcon = tnid.hIcon;
388 }
389 
390 static VOID
391 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID, SIZE_T LayoutIDLength)
392 {
393     StringCchPrintf(szLayoutID, LayoutIDLength, _T("%08lx"), (DWORD)(DWORD_PTR)(hKl));
394 }
395 
396 static BOOL CALLBACK
397 EnumWindowsProc(HWND hwnd, LPARAM lParam)
398 {
399     PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, INPUTLANGCHANGE_SYSCHARSET, lParam);
400     return TRUE;
401 }
402 
403 static VOID
404 ActivateLayout(HWND hwnd, ULONG uLayoutNum, HWND hwndTarget OPTIONAL, BOOL bNoActivate)
405 {
406     HKL hKl;
407     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLCID[CCH_LAYOUT_ID + 1], szLangName[MAX_PATH];
408     LANGID LangID;
409 
410     /* The layout number starts from one. Zero is invalid */
411     if (uLayoutNum == 0 || uLayoutNum > 0xFF) /* Invalid */
412         return;
413 
414     _ultot(uLayoutNum, szLayoutNum, 10);
415     GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID));
416     LangID = (LANGID)_tcstoul(szLCID, NULL, 16);
417 
418     /* Switch to the new keyboard layout */
419     GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
420     UpdateTrayIcon(hwnd, szLCID, szLangName);
421 
422     if (hwndTarget && !bNoActivate)
423         SetForegroundWindow(hwndTarget);
424 
425     hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE);
426     if (hKl)
427         ActivateKeyboardLayout(hKl, KLF_SETFORPROCESS);
428 
429     /* Post WM_INPUTLANGCHANGEREQUEST */
430     if (hwndTarget)
431     {
432         PostMessage(hwndTarget, WM_INPUTLANGCHANGEREQUEST,
433                     INPUTLANGCHANGE_SYSCHARSET, (LPARAM)hKl);
434     }
435     else
436     {
437         EnumWindows(EnumWindowsProc, (LPARAM) hKl);
438     }
439 
440     ulCurrentLayoutNum = uLayoutNum;
441 }
442 
443 static HMENU
444 BuildLeftPopupMenu(VOID)
445 {
446     HMENU hMenu = CreatePopupMenu();
447     HKEY hKey;
448     DWORD dwIndex, dwSize;
449     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szName[MAX_PATH];
450 
451     /* Add the keyboard layouts to the popup menu */
452     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0,
453                      KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
454     {
455         for (dwIndex = 0; ; dwIndex++)
456         {
457             dwSize = sizeof(szLayoutNum);
458             if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL,
459                              NULL, NULL) != ERROR_SUCCESS)
460             {
461                 break;
462             }
463 
464             if (!GetLayoutName(szLayoutNum, szName, ARRAYSIZE(szName)))
465                 continue;
466 
467             AppendMenu(hMenu, MF_STRING, _ttoi(szLayoutNum), szName);
468         }
469 
470         CheckMenuItem(hMenu, ulCurrentLayoutNum, MF_CHECKED);
471 
472         RegCloseKey(hKey);
473     }
474 
475     return hMenu;
476 }
477 
478 static ULONG
479 GetMaxLayoutNum(VOID)
480 {
481     HKEY hKey;
482     ULONG dwIndex, dwSize, uLayoutNum, uMaxLayoutNum = 0;
483     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLayoutID[CCH_LAYOUT_ID + 1];
484 
485     /* Get the maximum layout number in the Preload key */
486     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0,
487                      KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
488     {
489         for (dwIndex = 0; ; dwIndex++)
490         {
491             dwSize = sizeof(szLayoutNum);
492             if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL,
493                              NULL, NULL) != ERROR_SUCCESS)
494             {
495                 break;
496             }
497 
498             if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
499             {
500                 uLayoutNum = _ttoi(szLayoutNum);
501                 if (uMaxLayoutNum < uLayoutNum)
502                     uMaxLayoutNum = uLayoutNum;
503             }
504         }
505 
506         RegCloseKey(hKey);
507     }
508 
509     return uMaxLayoutNum;
510 }
511 
512 BOOL
513 SetHooks(VOID)
514 {
515     g_hHookDLL = LoadLibrary(_T("kbsdll.dll"));
516     if (!g_hHookDLL)
517     {
518         return FALSE;
519     }
520 
521     KbSwitchSetHooks    = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchSetHooks");
522     KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchDeleteHooks");
523 
524     if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL)
525     {
526         return FALSE;
527     }
528 
529     return KbSwitchSetHooks();
530 }
531 
532 VOID
533 DeleteHooks(VOID)
534 {
535     if (KbSwitchDeleteHooks)
536     {
537         KbSwitchDeleteHooks();
538         KbSwitchDeleteHooks = NULL;
539     }
540     if (g_hHookDLL)
541     {
542         FreeLibrary(g_hHookDLL);
543         g_hHookDLL = NULL;
544     }
545 }
546 
547 ULONG
548 GetNextLayout(VOID)
549 {
550     TCHAR szLayoutNum[3 + 1], szLayoutID[CCH_LAYOUT_ID + 1];
551     ULONG uLayoutNum, uMaxNum = GetMaxLayoutNum();
552 
553     for (uLayoutNum = ulCurrentLayoutNum + 1; ; ++uLayoutNum)
554     {
555         if (uLayoutNum > uMaxNum)
556             uLayoutNum = 1;
557         if (uLayoutNum == ulCurrentLayoutNum)
558             break;
559 
560         _ultot(uLayoutNum, szLayoutNum, 10);
561         if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
562             return uLayoutNum;
563     }
564 
565     return ulCurrentLayoutNum;
566 }
567 
568 UINT
569 UpdateLanguageDisplay(HWND hwnd, HKL hKl)
570 {
571     TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
572     LANGID LangID;
573 
574     GetLayoutIDByHkl(hKl, szLCID, ARRAYSIZE(szLCID));
575     LangID = (LANGID)_tcstoul(szLCID, NULL, 16);
576     GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
577     UpdateTrayIcon(hwnd, szLCID, szLangName);
578 
579     return 0;
580 }
581 
582 HWND
583 GetTargetWindow(HWND hwndFore)
584 {
585     TCHAR szClass[64];
586     HWND hwndIME;
587     HWND hwndTarget = hwndFore;
588     if (hwndTarget == NULL)
589         hwndTarget = GetForegroundWindow();
590 
591     GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass));
592     if (_tcsicmp(szClass, szKbSwitcherName) == 0)
593         hwndTarget = g_hwndLastActive;
594 
595     hwndIME = ImmGetDefaultIMEWnd(hwndTarget);
596     return (hwndIME ? hwndIME : hwndTarget);
597 }
598 
599 UINT
600 UpdateLanguageDisplayCurrent(HWND hwnd, HWND hwndFore)
601 {
602     DWORD dwThreadID = GetWindowThreadProcessId(GetTargetWindow(hwndFore), NULL);
603     HKL hKL = GetKeyboardLayout(dwThreadID);
604     UpdateLanguageDisplay(hwnd, hKL);
605 
606     if (IsWindow(g_hwndLastActive))
607         SetForegroundWindow(g_hwndLastActive);
608 
609     return 0;
610 }
611 
612 static UINT GetCurLayoutNum(HKL hKL)
613 {
614     UINT i, nCount;
615     HKL ahKL[256];
616 
617     nCount = GetKeyboardLayoutList(ARRAYSIZE(ahKL), ahKL);
618     for (i = 0; i < nCount; ++i)
619     {
620         if (ahKL[i] == hKL)
621             return i + 1;
622     }
623 
624     return 0;
625 }
626 
627 static BOOL RememberLastActive(HWND hwnd, HWND hwndFore)
628 {
629     TCHAR szClass[64];
630 
631     hwndFore = GetAncestor(hwndFore, GA_ROOT);
632 
633     if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, ARRAYSIZE(szClass)))
634         return FALSE;
635 
636     if (_tcsicmp(szClass, szKbSwitcherName) == 0 ||
637         _tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0)
638     {
639         return FALSE; /* Special window */
640     }
641 
642     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
643               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
644     if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
645     {
646         HKL hKL = GetKeyboardLayout(0);
647         UpdateLanguageDisplay(hwnd, hKL);
648     }
649 
650     g_hwndLastActive = hwndFore;
651     return TRUE;
652 }
653 
654 LRESULT CALLBACK
655 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
656 {
657     static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL;
658     static UINT s_uTaskbarRestart;
659     POINT pt;
660     HMENU hLeftPopupMenu;
661 
662     switch (Message)
663     {
664         case WM_CREATE:
665         {
666             if (!SetHooks())
667             {
668                 MessageBox(NULL, TEXT("SetHooks failed."), NULL, MB_ICONERROR);
669                 return -1;
670             }
671 
672             AddTrayIcon(hwnd);
673 
674             ActivateLayout(hwnd, ulCurrentLayoutNum, NULL, FALSE);
675             s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
676             break;
677         }
678 
679         case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */
680         {
681             UpdateLanguageDisplay(hwnd, (HKL)lParam);
682             break;
683         }
684 
685         case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */
686         {
687             HWND hwndFore = GetForegroundWindow();
688             if (RememberLastActive(hwnd, hwndFore))
689                 return UpdateLanguageDisplayCurrent(hwnd, hwndFore);
690             break;
691         }
692 
693         case WM_NOTIFYICONMSG:
694         {
695             switch (lParam)
696             {
697                 case WM_RBUTTONUP:
698                 case WM_LBUTTONUP:
699                 {
700                     GetCursorPos(&pt);
701                     SetForegroundWindow(hwnd);
702 
703                     if (lParam == WM_LBUTTONUP)
704                     {
705                         /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
706                         hLeftPopupMenu = BuildLeftPopupMenu();
707                         TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
708                         DestroyMenu(hLeftPopupMenu);
709                     }
710                     else
711                     {
712                         if (!s_hRightPopupMenu)
713                         {
714                             s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP));
715                             s_hRightPopupMenu = GetSubMenu(s_hMenu, 0);
716                         }
717                         TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
718                     }
719 
720                     PostMessage(hwnd, WM_NULL, 0, 0);
721                     break;
722                 }
723             }
724             break;
725         }
726 
727         case WM_COMMAND:
728             switch (LOWORD(wParam))
729             {
730                 case ID_EXIT:
731                 {
732                     PostMessage(hwnd, WM_CLOSE, 0, 0);
733                     break;
734                 }
735 
736                 case ID_PREFERENCES:
737                 {
738                     INT_PTR ret = (INT_PTR)ShellExecute(hwnd, NULL,
739                                                         TEXT("control.exe"), TEXT("input.dll"),
740                                                         NULL, SW_SHOWNORMAL);
741                     if (ret <= 32)
742                         MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_ICONERROR);
743                     break;
744                 }
745 
746                 case ID_NEXTLAYOUT:
747                 {
748                     HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL;
749                     DWORD dwThreadID;
750                     HKL hKL;
751                     UINT uNum;
752                     TCHAR szClass[64];
753                     BOOL bCONWND = FALSE;
754 
755                     if (hwndTarget == NULL)
756                         hwndTarget = g_hwndLastActive;
757 
758                     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
759                               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
760                     if (hwndTarget &&
761                         GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)) &&
762                         _tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
763                     {
764                         bCONWND = TRUE;
765                         hwndTargetSave = hwndTarget;
766                         hwndTarget = NULL;
767                     }
768 
769                     if (hwndTarget)
770                     {
771                         dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL);
772                         hKL = GetKeyboardLayout(dwThreadID);
773                         uNum = GetCurLayoutNum(hKL);
774                         if (uNum != 0)
775                             ulCurrentLayoutNum = uNum;
776                     }
777 
778                     ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE);
779 
780                     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
781                               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
782                     if (bCONWND)
783                     {
784                         ActivateLayout(hwnd, ulCurrentLayoutNum, hwndTargetSave, TRUE);
785                     }
786                     break;
787                 }
788 
789                 default:
790                 {
791                     if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000)
792                     {
793                         if (!IsWindow(g_hwndLastActive))
794                         {
795                             g_hwndLastActive = NULL;
796                         }
797                         ActivateLayout(hwnd, LOWORD(wParam), g_hwndLastActive, FALSE);
798                     }
799                     break;
800                 }
801             }
802             break;
803 
804         case WM_SETTINGCHANGE:
805         {
806             if (wParam == SPI_SETNONCLIENTMETRICS)
807             {
808                 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
809                 break;
810             }
811         }
812         break;
813 
814         case WM_DESTROY:
815         {
816             DeleteHooks();
817             DestroyMenu(s_hMenu);
818             DeleteTrayIcon(hwnd);
819             PostQuitMessage(0);
820             break;
821         }
822 
823         default:
824         {
825             if (Message == s_uTaskbarRestart)
826             {
827                 AddTrayIcon(hwnd);
828                 break;
829             }
830             else if (Message == ShellHookMessage)
831             {
832                 if (wParam == HSHELL_LANGUAGE)
833                     PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
834                 else if (wParam == HSHELL_WINDOWACTIVATED)
835                     PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
836 
837                 break;
838             }
839             return DefWindowProc(hwnd, Message, wParam, lParam);
840         }
841     }
842 
843     return 0;
844 }
845 
846 INT WINAPI
847 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
848 {
849     WNDCLASS WndClass;
850     MSG msg;
851     HANDLE hMutex;
852     HWND hwnd;
853 
854     switch (GetUserDefaultUILanguage())
855     {
856         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
857             SetProcessDefaultLayout(LAYOUT_RTL);
858             break;
859         default:
860             break;
861     }
862 
863     hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
864     if (!hMutex)
865         return 1;
866 
867     if (GetLastError() == ERROR_ALREADY_EXISTS)
868     {
869         CloseHandle(hMutex);
870         return 1;
871     }
872 
873     hInst = hInstance;
874     hProcessHeap = GetProcessHeap();
875 
876     ZeroMemory(&WndClass, sizeof(WndClass));
877     WndClass.lpfnWndProc   = WndProc;
878     WndClass.hInstance     = hInstance;
879     WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
880     WndClass.lpszClassName = szKbSwitcherName;
881     if (!RegisterClass(&WndClass))
882     {
883         CloseHandle(hMutex);
884         return 1;
885     }
886 
887     hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
888     ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
889     RegisterShellHookWindow(hwnd);
890 
891     while (GetMessage(&msg, NULL, 0, 0))
892     {
893         TranslateMessage(&msg);
894         DispatchMessage(&msg);
895     }
896 
897     CloseHandle(hMutex);
898     return 0;
899 }
900