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