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