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     return 0;
607 }
608 
609 static UINT GetCurLayoutNum(HKL hKL)
610 {
611     UINT i, nCount;
612     HKL ahKL[256];
613 
614     nCount = GetKeyboardLayoutList(ARRAYSIZE(ahKL), ahKL);
615     for (i = 0; i < nCount; ++i)
616     {
617         if (ahKL[i] == hKL)
618             return i + 1;
619     }
620 
621     return 0;
622 }
623 
624 static BOOL RememberLastActive(HWND hwnd, HWND hwndFore)
625 {
626     TCHAR szClass[64];
627 
628     hwndFore = GetAncestor(hwndFore, GA_ROOT);
629 
630     if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, ARRAYSIZE(szClass)))
631         return FALSE;
632 
633     if (_tcsicmp(szClass, szKbSwitcherName) == 0 ||
634         _tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0)
635     {
636         return FALSE; /* Special window */
637     }
638 
639     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
640               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
641     if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
642     {
643         HKL hKL = GetKeyboardLayout(0);
644         UpdateLanguageDisplay(hwnd, hKL);
645     }
646 
647     g_hwndLastActive = hwndFore;
648     return TRUE;
649 }
650 
651 LRESULT CALLBACK
652 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
653 {
654     static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL;
655     static UINT s_uTaskbarRestart;
656     POINT pt;
657     HMENU hLeftPopupMenu;
658 
659     switch (Message)
660     {
661         case WM_CREATE:
662         {
663             if (!SetHooks())
664             {
665                 MessageBox(NULL, TEXT("SetHooks failed."), NULL, MB_ICONERROR);
666                 return -1;
667             }
668 
669             AddTrayIcon(hwnd);
670 
671             ActivateLayout(hwnd, ulCurrentLayoutNum, NULL, FALSE);
672             s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
673             break;
674         }
675 
676         case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */
677         {
678             UpdateLanguageDisplay(hwnd, (HKL)lParam);
679             break;
680         }
681 
682         case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */
683         {
684             HWND hwndFore = GetForegroundWindow();
685             if (RememberLastActive(hwnd, hwndFore))
686                 return UpdateLanguageDisplayCurrent(hwnd, hwndFore);
687             break;
688         }
689 
690         case WM_NOTIFYICONMSG:
691         {
692             switch (lParam)
693             {
694                 case WM_RBUTTONUP:
695                 case WM_LBUTTONUP:
696                 {
697                     GetCursorPos(&pt);
698                     SetForegroundWindow(hwnd);
699 
700                     if (lParam == WM_LBUTTONUP)
701                     {
702                         /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
703                         hLeftPopupMenu = BuildLeftPopupMenu();
704                         TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
705                         DestroyMenu(hLeftPopupMenu);
706                     }
707                     else
708                     {
709                         if (!s_hRightPopupMenu)
710                         {
711                             s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP));
712                             s_hRightPopupMenu = GetSubMenu(s_hMenu, 0);
713                         }
714                         TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
715                     }
716 
717                     PostMessage(hwnd, WM_NULL, 0, 0);
718                     break;
719                 }
720             }
721             break;
722         }
723 
724         case WM_COMMAND:
725             switch (LOWORD(wParam))
726             {
727                 case ID_EXIT:
728                 {
729                     PostMessage(hwnd, WM_CLOSE, 0, 0);
730                     break;
731                 }
732 
733                 case ID_PREFERENCES:
734                 {
735                     INT_PTR ret = (INT_PTR)ShellExecute(hwnd, NULL,
736                                                         TEXT("control.exe"), TEXT("input.dll"),
737                                                         NULL, SW_SHOWNORMAL);
738                     if (ret <= 32)
739                         MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_ICONERROR);
740                     break;
741                 }
742 
743                 case ID_NEXTLAYOUT:
744                 {
745                     HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL;
746                     DWORD dwThreadID;
747                     HKL hKL;
748                     UINT uNum;
749                     TCHAR szClass[64];
750                     BOOL bCONWND = FALSE;
751 
752                     if (hwndTarget == NULL)
753                         hwndTarget = g_hwndLastActive;
754 
755                     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
756                               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
757                     if (hwndTarget &&
758                         GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)) &&
759                         _tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0)
760                     {
761                         bCONWND = TRUE;
762                         hwndTargetSave = hwndTarget;
763                         hwndTarget = NULL;
764                     }
765 
766                     if (hwndTarget)
767                     {
768                         dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL);
769                         hKL = GetKeyboardLayout(dwThreadID);
770                         uNum = GetCurLayoutNum(hKL);
771                         if (uNum != 0)
772                             ulCurrentLayoutNum = uNum;
773                     }
774 
775                     ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE);
776 
777                     /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and
778                               DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */
779                     if (bCONWND)
780                     {
781                         ActivateLayout(hwnd, ulCurrentLayoutNum, hwndTargetSave, TRUE);
782                     }
783                     break;
784                 }
785 
786                 default:
787                 {
788                     if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000)
789                     {
790                         if (!IsWindow(g_hwndLastActive))
791                         {
792                             g_hwndLastActive = NULL;
793                         }
794                         ActivateLayout(hwnd, LOWORD(wParam), g_hwndLastActive, FALSE);
795                     }
796                     break;
797                 }
798             }
799             break;
800 
801         case WM_SETTINGCHANGE:
802         {
803             if (wParam == SPI_SETNONCLIENTMETRICS)
804             {
805                 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
806                 break;
807             }
808         }
809         break;
810 
811         case WM_DESTROY:
812         {
813             DeleteHooks();
814             DestroyMenu(s_hMenu);
815             DeleteTrayIcon(hwnd);
816             PostQuitMessage(0);
817             break;
818         }
819 
820         default:
821         {
822             if (Message == s_uTaskbarRestart)
823             {
824                 AddTrayIcon(hwnd);
825                 break;
826             }
827             else if (Message == ShellHookMessage)
828             {
829                 if (wParam == HSHELL_LANGUAGE)
830                     PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
831                 else if (wParam == HSHELL_WINDOWACTIVATED)
832                     PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam);
833 
834                 break;
835             }
836             return DefWindowProc(hwnd, Message, wParam, lParam);
837         }
838     }
839 
840     return 0;
841 }
842 
843 INT WINAPI
844 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
845 {
846     WNDCLASS WndClass;
847     MSG msg;
848     HANDLE hMutex;
849     HWND hwnd;
850 
851     switch (GetUserDefaultUILanguage())
852     {
853         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
854             SetProcessDefaultLayout(LAYOUT_RTL);
855             break;
856         default:
857             break;
858     }
859 
860     hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
861     if (!hMutex)
862         return 1;
863 
864     if (GetLastError() == ERROR_ALREADY_EXISTS)
865     {
866         CloseHandle(hMutex);
867         return 1;
868     }
869 
870     hInst = hInstance;
871     hProcessHeap = GetProcessHeap();
872 
873     ZeroMemory(&WndClass, sizeof(WndClass));
874     WndClass.lpfnWndProc   = WndProc;
875     WndClass.hInstance     = hInstance;
876     WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
877     WndClass.lpszClassName = szKbSwitcherName;
878     if (!RegisterClass(&WndClass))
879     {
880         CloseHandle(hMutex);
881         return 1;
882     }
883 
884     hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
885     ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
886     RegisterShellHookWindow(hwnd);
887 
888     while (GetMessage(&msg, NULL, 0, 0))
889     {
890         TranslateMessage(&msg);
891         DispatchMessage(&msg);
892     }
893 
894     CloseHandle(hMutex);
895     return 0;
896 }
897