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 
12 #define WM_NOTIFYICONMSG (WM_USER + 248)
13 #define CX_ICON 16
14 #define CY_ICON 16
15 
16 PKBSWITCHSETHOOKS    KbSwitchSetHooks    = NULL;
17 PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks = NULL;
18 UINT ShellHookMessage = 0;
19 DWORD dwAltShiftHotKeyId = 0, dwShiftAltHotKeyId = 0;
20 
21 HINSTANCE hInst;
22 HANDLE    hProcessHeap;
23 HMODULE   g_hHookDLL = NULL;
24 ULONG     ulCurrentLayoutNum = 1;
25 HICON     g_hTrayIcon = NULL;
26 
27 static BOOL
28 GetLayoutID(LPCTSTR szLayoutNum, LPTSTR szLCID, SIZE_T LCIDLength)
29 {
30     DWORD dwBufLen, dwRes;
31     HKEY hKey;
32     TCHAR szTempLCID[CCH_LAYOUT_ID + 1];
33 
34     /* Get the Layout ID */
35     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE,
36                      &hKey) == ERROR_SUCCESS)
37     {
38         dwBufLen = sizeof(szTempLCID);
39         dwRes = RegQueryValueEx(hKey, szLayoutNum, NULL, NULL, (LPBYTE)szTempLCID, &dwBufLen);
40         if (dwRes != ERROR_SUCCESS)
41         {
42             RegCloseKey(hKey);
43             return FALSE;
44         }
45 
46         RegCloseKey(hKey);
47     }
48 
49     /* Look for a substitute of this layout */
50     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Substitutes"), 0,
51                      KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
52     {
53         dwBufLen = sizeof(szTempLCID);
54         if (RegQueryValueEx(hKey, szTempLCID, NULL, NULL, (LPBYTE)szLCID, &dwBufLen) != ERROR_SUCCESS)
55         {
56             /* No substitute found, then use the old LCID */
57             StringCchCopy(szLCID, LCIDLength, szTempLCID);
58         }
59 
60         RegCloseKey(hKey);
61     }
62     else
63     {
64         /* Substitutes key couldn't be opened, so use the old LCID */
65         StringCchCopy(szLCID, LCIDLength, szTempLCID);
66     }
67 
68     return TRUE;
69 }
70 
71 static BOOL
72 GetLayoutName(LPCTSTR szLayoutNum, LPTSTR szName, SIZE_T NameLength)
73 {
74     HKEY hKey;
75     DWORD dwBufLen;
76     TCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
77     TCHAR szLCID[CCH_LAYOUT_ID + 1];
78     HANDLE hLib;
79     UINT i, j, k;
80 
81     if (!GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID)))
82         return FALSE;
83 
84     StringCchPrintf(szBuf, ARRAYSIZE(szBuf),
85                     _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID);
86 
87     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS)
88     {
89         return FALSE;
90     }
91 
92     /* Use "Layout Display Name" value as an entry name if possible */
93     dwBufLen = sizeof(szDispName);
94     if (RegQueryValueEx(hKey, _T("Layout Display Name"), NULL, NULL,
95                         (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
96     {
97         /* FIXME: Use shlwapi!SHLoadRegUIStringW instead if it was implemented */
98         if (szDispName[0] == '@')
99         {
100             size_t len = _tcslen(szDispName);
101 
102             for (i = 0; i < len; i++)
103             {
104                 if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
105                 {
106                     for (j = i + 2, k = 0; j < _tcslen(szDispName)+1; j++, k++)
107                     {
108                         szIndex[k] = szDispName[j];
109                     }
110                     szDispName[i - 1] = '\0';
111                     break;
112                 }
113                 else szDispName[i] = szDispName[i + 1];
114             }
115 
116             if (ExpandEnvironmentStrings(szDispName, szPath, ARRAYSIZE(szPath)))
117             {
118                 hLib = LoadLibrary(szPath);
119                 if (hLib)
120                 {
121                     if (LoadString(hLib, _ttoi(szIndex), szPath, ARRAYSIZE(szPath)))
122                     {
123                         StringCchCopy(szName, NameLength, szPath);
124                         RegCloseKey(hKey);
125                         FreeLibrary(hLib);
126                         return TRUE;
127                     }
128                     FreeLibrary(hLib);
129                 }
130             }
131         }
132     }
133 
134     /* Otherwise, use "Layout Text" value as an entry name */
135     dwBufLen = NameLength * sizeof(TCHAR);
136     if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL,
137                         (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
138     {
139         RegCloseKey(hKey);
140         return TRUE;
141     }
142 
143     RegCloseKey(hKey);
144     return FALSE;
145 }
146 
147 static HICON
148 CreateTrayIcon(LPTSTR szLCID)
149 {
150     LANGID LangID;
151     TCHAR szBuf[3];
152     HDC hdc;
153     HBITMAP hbmColor, hbmMono, hBmpOld;
154     RECT rect;
155     HFONT hFontOld, hFont;
156     ICONINFO IconInfo;
157     HICON hIcon;
158     LOGFONT lf;
159 
160     /* Getting "EN", "FR", etc. from English, French, ... */
161     LangID = (LANGID)_tcstoul(szLCID, NULL, 16);
162     if (!GetLocaleInfo(LangID, LOCALE_SISO639LANGNAME, szBuf, ARRAYSIZE(szBuf)))
163     {
164         StringCchCopy(szBuf, ARRAYSIZE(szBuf), _T("??"));
165     }
166     CharUpper(szBuf);
167 
168     /* Create hdc, hbmColor and hbmMono */
169     hdc = CreateCompatibleDC(NULL);
170     hbmColor = CreateCompatibleBitmap(hdc, CX_ICON, CY_ICON);
171     hbmMono = CreateBitmap(CX_ICON, CY_ICON, 1, 1, NULL);
172 
173     /* Create a font */
174     ZeroMemory(&lf, sizeof(lf));
175     lf.lfHeight = -11;
176     lf.lfCharSet = ANSI_CHARSET;
177     lf.lfWeight = FW_NORMAL;
178     StringCchCopy(lf.lfFaceName, ARRAYSIZE(lf.lfFaceName), _T("Tahoma"));
179     hFont = CreateFontIndirect(&lf);
180 
181     /* Checking NULL */
182     if (!hdc || !hbmColor || !hbmMono || !hFont)
183     {
184         if (hdc)
185             DeleteDC(hdc);
186         if (hbmColor)
187             DeleteObject(hbmColor);
188         if (hbmMono)
189             DeleteObject(hbmMono);
190         if (hFont)
191             DeleteObject(hFont);
192         return NULL;
193     }
194 
195     SetRect(&rect, 0, 0, CX_ICON, CY_ICON);
196 
197     /* Draw hbmColor */
198     hBmpOld = SelectObject(hdc, hbmColor);
199     SetDCBrushColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
200     FillRect(hdc, &rect, (HBRUSH)GetStockObject(DC_BRUSH));
201     hFontOld = SelectObject(hdc, hFont);
202     SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
203     SetBkMode(hdc, TRANSPARENT);
204     DrawText(hdc, szBuf, 2, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
205     SelectObject(hdc, hFontOld);
206     SelectObject(hdc, hBmpOld);
207 
208     /* Fill hbmMono by black */
209     hBmpOld = SelectObject(hdc, hbmMono);
210     PatBlt(hdc, 0, 0, CX_ICON, CY_ICON, BLACKNESS);
211     SelectObject(hdc, hBmpOld);
212 
213     /* Create an icon from hbmColor and hbmMono */
214     IconInfo.hbmColor = hbmColor;
215     IconInfo.hbmMask = hbmMono;
216     IconInfo.fIcon = TRUE;
217     hIcon = CreateIconIndirect(&IconInfo);
218 
219     /* Clean up */
220     DeleteObject(hbmColor);
221     DeleteObject(hbmMono);
222     DeleteObject(hFont);
223     DeleteDC(hdc);
224 
225     return hIcon;
226 }
227 
228 static VOID
229 AddTrayIcon(HWND hwnd)
230 {
231     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
232     TCHAR szLCID[CCH_LAYOUT_ID + 1], szName[MAX_PATH];
233 
234     GetLayoutID(_T("1"), szLCID, ARRAYSIZE(szLCID));
235     GetLayoutName(_T("1"), szName, ARRAYSIZE(szName));
236 
237     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
238     tnid.hIcon = CreateTrayIcon(szLCID);
239     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
240 
241     Shell_NotifyIcon(NIM_ADD, &tnid);
242 
243     if (g_hTrayIcon)
244         DestroyIcon(g_hTrayIcon);
245     g_hTrayIcon = tnid.hIcon;
246 }
247 
248 static VOID
249 DeleteTrayIcon(HWND hwnd)
250 {
251     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1 };
252     Shell_NotifyIcon(NIM_DELETE, &tnid);
253 
254     if (g_hTrayIcon)
255     {
256         DestroyIcon(g_hTrayIcon);
257         g_hTrayIcon = NULL;
258     }
259 }
260 
261 static VOID
262 UpdateTrayIcon(HWND hwnd, LPTSTR szLCID, LPTSTR szName)
263 {
264     NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP };
265     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
266     tnid.hIcon = CreateTrayIcon(szLCID);
267     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
268 
269     Shell_NotifyIcon(NIM_MODIFY, &tnid);
270 
271     if (g_hTrayIcon)
272         DestroyIcon(g_hTrayIcon);
273     g_hTrayIcon = tnid.hIcon;
274 }
275 
276 static VOID
277 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID, SIZE_T LayoutIDLength)
278 {
279     StringCchPrintf(szLayoutID, LayoutIDLength, _T("%08lx"), (DWORD)(DWORD_PTR)(hKl));
280 }
281 
282 static BOOL CALLBACK
283 EnumWindowsProc(HWND hwnd, LPARAM lParam)
284 {
285     PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, lParam);
286     return TRUE;
287 }
288 
289 static VOID
290 ActivateLayout(HWND hwnd, ULONG uLayoutNum)
291 {
292     HKL hKl;
293     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLCID[CCH_LAYOUT_ID + 1], szLangName[MAX_PATH];
294     LANGID LangID;
295 
296     /* The layout number starts from one. Zero is invalid */
297     if (uLayoutNum == 0 || uLayoutNum > 0xFF) /* Invalid */
298         return;
299 
300     _ultot(uLayoutNum, szLayoutNum, 10);
301     GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID));
302     LangID = (LANGID)_tcstoul(szLCID, NULL, 16);
303 
304     /* Switch to the new keyboard layout */
305     GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
306     UpdateTrayIcon(hwnd, szLCID, szLangName);
307     hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE);
308 
309     /* Post WM_INPUTLANGCHANGEREQUEST to every top-level window */
310     EnumWindows(EnumWindowsProc, (LPARAM) hKl);
311 
312     ulCurrentLayoutNum = uLayoutNum;
313 }
314 
315 static HMENU
316 BuildLeftPopupMenu(VOID)
317 {
318     HMENU hMenu = CreatePopupMenu();
319     HKEY hKey;
320     DWORD dwIndex, dwSize;
321     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szName[MAX_PATH];
322 
323     /* Add the keyboard layouts to the popup menu */
324     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0,
325                      KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
326     {
327         for (dwIndex = 0; ; dwIndex++)
328         {
329             dwSize = sizeof(szLayoutNum);
330             if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL,
331                              NULL, NULL) != ERROR_SUCCESS)
332             {
333                 break;
334             }
335 
336             if (!GetLayoutName(szLayoutNum, szName, ARRAYSIZE(szName)))
337                 break;
338 
339             AppendMenu(hMenu, MF_STRING, _ttoi(szLayoutNum), szName);
340         }
341 
342         CheckMenuItem(hMenu, ulCurrentLayoutNum, MF_CHECKED);
343 
344         RegCloseKey(hKey);
345     }
346 
347     return hMenu;
348 }
349 
350 static ULONG
351 GetMaxLayoutNum(VOID)
352 {
353     HKEY hKey;
354     ULONG dwIndex, dwSize, uLayoutNum, uMaxLayoutNum = 0;
355     TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLayoutID[CCH_LAYOUT_ID + 1];
356 
357     /* Get the maximum layout number in the Preload key */
358     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0,
359                      KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
360     {
361         for (dwIndex = 0; ; dwIndex++)
362         {
363             dwSize = sizeof(szLayoutNum);
364             if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL,
365                              NULL, NULL) != ERROR_SUCCESS)
366             {
367                 break;
368             }
369 
370             if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
371             {
372                 uLayoutNum = _ttoi(szLayoutNum);
373                 if (uMaxLayoutNum < uLayoutNum)
374                     uMaxLayoutNum = uLayoutNum;
375             }
376         }
377 
378         RegCloseKey(hKey);
379     }
380 
381     return uMaxLayoutNum;
382 }
383 
384 BOOL
385 SetHooks(VOID)
386 {
387     g_hHookDLL = LoadLibrary(_T("kbsdll.dll"));
388     if (!g_hHookDLL)
389     {
390         return FALSE;
391     }
392 
393     KbSwitchSetHooks    = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchSetHooks");
394     KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchDeleteHooks");
395 
396     if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL)
397     {
398         return FALSE;
399     }
400 
401     return KbSwitchSetHooks();
402 }
403 
404 VOID
405 DeleteHooks(VOID)
406 {
407     if (KbSwitchDeleteHooks) KbSwitchDeleteHooks();
408     if (g_hHookDLL) FreeLibrary(g_hHookDLL);
409 }
410 
411 ULONG
412 GetNextLayout(VOID)
413 {
414     TCHAR szLayoutNum[3 + 1], szLayoutID[CCH_LAYOUT_ID + 1];
415     ULONG uLayoutNum, uMaxNum = GetMaxLayoutNum();
416 
417     for (uLayoutNum = ulCurrentLayoutNum + 1; ; ++uLayoutNum)
418     {
419         if (uLayoutNum > uMaxNum)
420             uLayoutNum = 1;
421         if (uLayoutNum == ulCurrentLayoutNum)
422             break;
423 
424         _ultot(uLayoutNum, szLayoutNum, 10);
425         if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
426             return uLayoutNum;
427     }
428 
429     return ulCurrentLayoutNum;
430 }
431 
432 LRESULT
433 UpdateLanguageDisplay(HWND hwnd, HKL hKl)
434 {
435     TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
436     LANGID LangID;
437 
438     GetLayoutIDByHkl(hKl, szLCID, ARRAYSIZE(szLCID));
439     LangID = (LANGID)_tcstoul(szLCID, NULL, 16);
440     GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName));
441     UpdateTrayIcon(hwnd, szLCID, szLangName);
442 
443     return 0;
444 }
445 
446 LRESULT
447 UpdateLanguageDisplayCurrent(HWND hwnd, WPARAM wParam)
448 {
449     DWORD dwThreadID = GetWindowThreadProcessId((HWND)wParam, 0);
450     HKL hKL = GetKeyboardLayout(dwThreadID);
451     return UpdateLanguageDisplay(hwnd, hKL);
452 }
453 
454 VOID DoRegisterAltShiftHotKeys(HWND hwnd)
455 {
456     dwAltShiftHotKeyId = GlobalAddAtom(TEXT("ReactOS Alt+Shift"));
457     dwShiftAltHotKeyId = GlobalAddAtom(TEXT("ReactOS Shift+Alt"));
458 
459     RegisterHotKey(hwnd, dwAltShiftHotKeyId, MOD_ALT | MOD_SHIFT, VK_SHIFT);
460     RegisterHotKey(hwnd, dwShiftAltHotKeyId, MOD_ALT | MOD_SHIFT, VK_MENU);
461 }
462 
463 VOID DoUnregisterAltShiftHotKeys(HWND hwnd)
464 {
465     UnregisterHotKey(hwnd, dwAltShiftHotKeyId);
466     UnregisterHotKey(hwnd, dwShiftAltHotKeyId);
467 
468     GlobalDeleteAtom(dwAltShiftHotKeyId);
469     GlobalDeleteAtom(dwShiftAltHotKeyId);
470     dwAltShiftHotKeyId = dwShiftAltHotKeyId = 0;
471 }
472 
473 LRESULT CALLBACK
474 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
475 {
476     static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL;
477     static UINT s_uTaskbarRestart;
478     POINT pt;
479     HMENU hLeftPopupMenu;
480 
481     switch (Message)
482     {
483         case WM_CREATE:
484         {
485             if (!SetHooks())
486                 return -1;
487 
488             AddTrayIcon(hwnd);
489 
490             ActivateLayout(hwnd, ulCurrentLayoutNum);
491             s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
492 
493             DoRegisterAltShiftHotKeys(hwnd);
494             break;
495         }
496 
497         case WM_LANG_CHANGED:
498         {
499             return UpdateLanguageDisplay(hwnd, (HKL)lParam);
500         }
501 
502         case WM_HOTKEY:
503         {
504             if (wParam != dwAltShiftHotKeyId && wParam != dwShiftAltHotKeyId)
505                 break;
506 
507             /* FALL THROUGH */
508         }
509 
510         case WM_LOAD_LAYOUT:
511         {
512             ULONG uNextNum = GetNextLayout();
513             if (ulCurrentLayoutNum != uNextNum)
514                 ActivateLayout(hwnd, uNextNum);
515             break;
516         }
517 
518         case WM_WINDOW_ACTIVATE:
519         {
520             return UpdateLanguageDisplayCurrent(hwnd, wParam);
521         }
522 
523         case WM_NOTIFYICONMSG:
524         {
525             switch (lParam)
526             {
527                 case WM_RBUTTONUP:
528                 case WM_LBUTTONUP:
529                 {
530                     GetCursorPos(&pt);
531                     SetForegroundWindow(hwnd);
532 
533                     if (lParam == WM_LBUTTONUP)
534                     {
535                         /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
536                         hLeftPopupMenu = BuildLeftPopupMenu();
537                         TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
538                         DestroyMenu(hLeftPopupMenu);
539                     }
540                     else
541                     {
542                         if (!s_hRightPopupMenu)
543                         {
544                             s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP));
545                             s_hRightPopupMenu = GetSubMenu(s_hMenu, 0);
546                         }
547                         TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
548                     }
549 
550                     PostMessage(hwnd, WM_NULL, 0, 0);
551                     break;
552                 }
553             }
554             break;
555         }
556 
557         case WM_COMMAND:
558             switch (LOWORD(wParam))
559             {
560                 case ID_EXIT:
561                 {
562                     SendMessage(hwnd, WM_CLOSE, 0, 0);
563                     break;
564                 }
565 
566                 case ID_PREFERENCES:
567                 {
568                     SHELLEXECUTEINFO shInputDll = { sizeof(shInputDll) };
569                     shInputDll.hwnd = hwnd;
570                     shInputDll.lpVerb = _T("open");
571                     shInputDll.lpFile = _T("rundll32.exe");
572                     shInputDll.lpParameters = _T("shell32.dll,Control_RunDLL input.dll");
573                     if (!ShellExecuteEx(&shInputDll))
574                         MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_OK | MB_ICONERROR);
575 
576                     break;
577                 }
578 
579                 default:
580                 {
581                     ActivateLayout(hwnd, LOWORD(wParam));
582                     break;
583                 }
584             }
585             break;
586 
587         case WM_SETTINGCHANGE:
588         {
589             if (wParam == SPI_SETDEFAULTINPUTLANG)
590             {
591                 //FIXME: Should detect default language changes by CPL applet or by other tools and update UI
592             }
593             if (wParam == SPI_SETNONCLIENTMETRICS)
594             {
595                 return UpdateLanguageDisplayCurrent(hwnd, wParam);
596             }
597         }
598         break;
599 
600         case WM_DESTROY:
601         {
602             DoUnregisterAltShiftHotKeys(hwnd);
603             DeleteHooks();
604             DestroyMenu(s_hMenu);
605             DeleteTrayIcon(hwnd);
606             PostQuitMessage(0);
607             break;
608         }
609 
610         default:
611         {
612             if (Message == s_uTaskbarRestart)
613             {
614                 AddTrayIcon(hwnd);
615                 break;
616             }
617             else if (Message == ShellHookMessage && wParam == HSHELL_LANGUAGE)
618             {
619                 PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
620                 break;
621             }
622             return DefWindowProc(hwnd, Message, wParam, lParam);
623         }
624     }
625 
626     return 0;
627 }
628 
629 INT WINAPI
630 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
631 {
632     WNDCLASS WndClass;
633     MSG msg;
634     HANDLE hMutex;
635     HWND hwnd;
636 
637     switch (GetUserDefaultUILanguage())
638     {
639         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
640             SetProcessDefaultLayout(LAYOUT_RTL);
641             break;
642         default:
643             break;
644     }
645 
646     hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
647     if (!hMutex)
648         return 1;
649 
650     if (GetLastError() == ERROR_ALREADY_EXISTS)
651     {
652         CloseHandle(hMutex);
653         return 1;
654     }
655 
656     hInst = hInstance;
657     hProcessHeap = GetProcessHeap();
658 
659     ZeroMemory(&WndClass, sizeof(WndClass));
660     WndClass.lpfnWndProc   = WndProc;
661     WndClass.hInstance     = hInstance;
662     WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
663     WndClass.lpszClassName = szKbSwitcherName;
664     if (!RegisterClass(&WndClass))
665     {
666         CloseHandle(hMutex);
667         return 1;
668     }
669 
670     hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
671     ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
672     RegisterShellHookWindow(hwnd);
673 
674     while (GetMessage(&msg, NULL, 0, 0))
675     {
676         TranslateMessage(&msg);
677         DispatchMessage(&msg);
678     }
679 
680     CloseHandle(hMutex);
681     return 0;
682 }
683