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