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 HBITMAP BitmapFromIcon(HICON hIcon) 240 { 241 HDC hdcScreen = GetDC(NULL); 242 HDC hdc = CreateCompatibleDC(hdcScreen); 243 INT cxIcon = GetSystemMetrics(SM_CXSMICON); 244 INT cyIcon = GetSystemMetrics(SM_CYSMICON); 245 HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon); 246 HGDIOBJ hbmOld; 247 248 if (hbm != NULL) 249 { 250 hbmOld = SelectObject(hdc, hbm); 251 DrawIconEx(hdc, 0, 0, hIcon, cxIcon, cyIcon, 0, GetSysColorBrush(COLOR_MENU), DI_NORMAL); 252 SelectObject(hdc, hbmOld); 253 } 254 255 DeleteDC(hdc); 256 ReleaseDC(NULL, hdcScreen); 257 return hbm; 258 } 259 260 static HICON 261 CreateTrayIcon(LPTSTR szLCID, LPCTSTR szImeFile OPTIONAL) 262 { 263 LANGID LangID; 264 TCHAR szBuf[4]; 265 HDC hdcScreen, hdc; 266 HBITMAP hbmColor, hbmMono, hBmpOld; 267 HFONT hFont, hFontOld; 268 LOGFONT lf; 269 RECT rect; 270 ICONINFO IconInfo; 271 HICON hIcon; 272 INT cxIcon = GetSystemMetrics(SM_CXSMICON); 273 INT cyIcon = GetSystemMetrics(SM_CYSMICON); 274 TCHAR szPath[MAX_PATH]; 275 276 if (szImeFile && szImeFile[0]) 277 { 278 if (GetSystemLibraryPath(szPath, ARRAYSIZE(szPath), szImeFile)) 279 return FakeExtractIcon(szPath, cxIcon, cyIcon); 280 } 281 282 /* Getting "EN", "FR", etc. from English, French, ... */ 283 LangID = LANGIDFROMLCID(_tcstoul(szLCID, NULL, 16)); 284 if (GetLocaleInfo(LangID, 285 LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE, 286 szBuf, 287 ARRAYSIZE(szBuf)) == 0) 288 { 289 szBuf[0] = szBuf[1] = _T('?'); 290 } 291 szBuf[2] = 0; /* Truncate the identifier to two characters: "ENG" --> "EN" etc. */ 292 293 /* Create hdc, hbmColor and hbmMono */ 294 hdcScreen = GetDC(NULL); 295 hdc = CreateCompatibleDC(hdcScreen); 296 hbmColor = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon); 297 ReleaseDC(NULL, hdcScreen); 298 hbmMono = CreateBitmap(cxIcon, cyIcon, 1, 1, NULL); 299 300 /* Checking NULL */ 301 if (!hdc || !hbmColor || !hbmMono) 302 { 303 if (hbmMono) 304 DeleteObject(hbmMono); 305 if (hbmColor) 306 DeleteObject(hbmColor); 307 if (hdc) 308 DeleteDC(hdc); 309 return NULL; 310 } 311 312 /* Create a font */ 313 hFont = NULL; 314 if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0)) 315 { 316 /* Override the current size with something manageable */ 317 lf.lfHeight = -11; 318 lf.lfWidth = 0; 319 hFont = CreateFontIndirect(&lf); 320 } 321 if (!hFont) 322 hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); 323 324 SetRect(&rect, 0, 0, cxIcon, cyIcon); 325 326 /* Draw hbmColor */ 327 hBmpOld = SelectObject(hdc, hbmColor); 328 SetDCBrushColor(hdc, GetSysColor(COLOR_HIGHLIGHT)); 329 FillRect(hdc, &rect, (HBRUSH)GetStockObject(DC_BRUSH)); 330 hFontOld = SelectObject(hdc, hFont); 331 SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); 332 SetBkMode(hdc, TRANSPARENT); 333 DrawText(hdc, szBuf, 2, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); 334 SelectObject(hdc, hFontOld); 335 336 /* Fill hbmMono with black */ 337 SelectObject(hdc, hbmMono); 338 PatBlt(hdc, 0, 0, cxIcon, cyIcon, BLACKNESS); 339 SelectObject(hdc, hBmpOld); 340 341 /* Create an icon from hbmColor and hbmMono */ 342 IconInfo.fIcon = TRUE; 343 IconInfo.xHotspot = IconInfo.yHotspot = 0; 344 IconInfo.hbmColor = hbmColor; 345 IconInfo.hbmMask = hbmMono; 346 hIcon = CreateIconIndirect(&IconInfo); 347 348 /* Clean up */ 349 DeleteObject(hFont); 350 DeleteObject(hbmMono); 351 DeleteObject(hbmColor); 352 DeleteDC(hdc); 353 354 return hIcon; 355 } 356 357 static VOID 358 AddTrayIcon(HWND hwnd) 359 { 360 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP }; 361 TCHAR szLCID[CCH_LAYOUT_ID + 1], szName[MAX_PATH]; 362 TCHAR szImeFile[80]; 363 364 GetLayoutID(_T("1"), szLCID, ARRAYSIZE(szLCID)); 365 GetLayoutName(_T("1"), szName, ARRAYSIZE(szName)); 366 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szLCID); 367 368 tnid.uCallbackMessage = WM_NOTIFYICONMSG; 369 tnid.hIcon = CreateTrayIcon(szLCID, szImeFile); 370 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName); 371 372 Shell_NotifyIcon(NIM_ADD, &tnid); 373 374 if (g_hTrayIcon) 375 DestroyIcon(g_hTrayIcon); 376 g_hTrayIcon = tnid.hIcon; 377 } 378 379 static VOID 380 DeleteTrayIcon(HWND hwnd) 381 { 382 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1 }; 383 Shell_NotifyIcon(NIM_DELETE, &tnid); 384 385 if (g_hTrayIcon) 386 { 387 DestroyIcon(g_hTrayIcon); 388 g_hTrayIcon = NULL; 389 } 390 } 391 392 static VOID 393 UpdateTrayIcon(HWND hwnd, LPTSTR szLCID, LPTSTR szName) 394 { 395 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP }; 396 TCHAR szImeFile[80]; 397 398 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szLCID); 399 400 tnid.uCallbackMessage = WM_NOTIFYICONMSG; 401 tnid.hIcon = CreateTrayIcon(szLCID, szImeFile); 402 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName); 403 404 Shell_NotifyIcon(NIM_MODIFY, &tnid); 405 406 if (g_hTrayIcon) 407 DestroyIcon(g_hTrayIcon); 408 g_hTrayIcon = tnid.hIcon; 409 } 410 411 static VOID 412 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID, SIZE_T LayoutIDLength) 413 { 414 StringCchPrintf(szLayoutID, LayoutIDLength, _T("%08lx"), (DWORD)(DWORD_PTR)(hKl)); 415 } 416 417 static BOOL CALLBACK 418 EnumWindowsProc(HWND hwnd, LPARAM lParam) 419 { 420 PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, INPUTLANGCHANGE_SYSCHARSET, lParam); 421 return TRUE; 422 } 423 424 static VOID 425 ActivateLayout(HWND hwnd, ULONG uLayoutNum, HWND hwndTarget OPTIONAL, BOOL bNoActivate) 426 { 427 HKL hKl; 428 TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLCID[CCH_LAYOUT_ID + 1], szLangName[MAX_PATH]; 429 LANGID LangID; 430 431 /* The layout number starts from one. Zero is invalid */ 432 if (uLayoutNum == 0 || uLayoutNum > 0xFF) /* Invalid */ 433 return; 434 435 _ultot(uLayoutNum, szLayoutNum, 10); 436 GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID)); 437 LangID = (LANGID)_tcstoul(szLCID, NULL, 16); 438 439 /* Switch to the new keyboard layout */ 440 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName)); 441 UpdateTrayIcon(hwnd, szLCID, szLangName); 442 443 if (hwndTarget && !bNoActivate) 444 SetForegroundWindow(hwndTarget); 445 446 hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE); 447 if (hKl) 448 ActivateKeyboardLayout(hKl, KLF_SETFORPROCESS); 449 450 /* Post WM_INPUTLANGCHANGEREQUEST */ 451 if (hwndTarget) 452 { 453 PostMessage(hwndTarget, WM_INPUTLANGCHANGEREQUEST, 454 INPUTLANGCHANGE_SYSCHARSET, (LPARAM)hKl); 455 } 456 else 457 { 458 EnumWindows(EnumWindowsProc, (LPARAM) hKl); 459 } 460 461 ulCurrentLayoutNum = uLayoutNum; 462 } 463 464 static HMENU 465 BuildLeftPopupMenu(VOID) 466 { 467 HMENU hMenu = CreatePopupMenu(); 468 HKEY hKey; 469 DWORD dwIndex, dwSize; 470 TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szName[MAX_PATH]; 471 TCHAR szLCID[CCH_LAYOUT_ID + 1], szImeFile[80]; 472 HICON hIcon; 473 MENUITEMINFO mii = { sizeof(mii) }; 474 475 /* Add the keyboard layouts to the popup menu */ 476 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, 477 KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) 478 { 479 for (dwIndex = 0; ; dwIndex++) 480 { 481 dwSize = sizeof(szLayoutNum); 482 if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL, 483 NULL, NULL) != ERROR_SUCCESS) 484 { 485 break; 486 } 487 488 GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID)); 489 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szLCID); 490 491 if (!GetLayoutName(szLayoutNum, szName, ARRAYSIZE(szName))) 492 continue; 493 494 mii.fMask = MIIM_ID | MIIM_STRING; 495 mii.wID = _ttoi(szLayoutNum); 496 mii.dwTypeData = szName; 497 498 hIcon = CreateTrayIcon(szLCID, szImeFile); 499 if (hIcon) 500 { 501 mii.hbmpItem = BitmapFromIcon(hIcon); 502 if (mii.hbmpItem) 503 mii.fMask |= MIIM_BITMAP; 504 } 505 506 InsertMenuItem(hMenu, -1, TRUE, &mii); 507 DestroyIcon(hIcon); 508 } 509 510 CheckMenuItem(hMenu, ulCurrentLayoutNum, MF_CHECKED); 511 512 RegCloseKey(hKey); 513 } 514 515 return hMenu; 516 } 517 518 static ULONG 519 GetMaxLayoutNum(VOID) 520 { 521 HKEY hKey; 522 ULONG dwIndex, dwSize, uLayoutNum, uMaxLayoutNum = 0; 523 TCHAR szLayoutNum[CCH_ULONG_DEC + 1], szLayoutID[CCH_LAYOUT_ID + 1]; 524 525 /* Get the maximum layout number in the Preload key */ 526 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, 527 KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) 528 { 529 for (dwIndex = 0; ; dwIndex++) 530 { 531 dwSize = sizeof(szLayoutNum); 532 if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL, 533 NULL, NULL) != ERROR_SUCCESS) 534 { 535 break; 536 } 537 538 if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID))) 539 { 540 uLayoutNum = _ttoi(szLayoutNum); 541 if (uMaxLayoutNum < uLayoutNum) 542 uMaxLayoutNum = uLayoutNum; 543 } 544 } 545 546 RegCloseKey(hKey); 547 } 548 549 return uMaxLayoutNum; 550 } 551 552 BOOL 553 SetHooks(VOID) 554 { 555 g_hHookDLL = LoadLibrary(_T("kbsdll.dll")); 556 if (!g_hHookDLL) 557 { 558 return FALSE; 559 } 560 561 KbSwitchSetHooks = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchSetHooks"); 562 KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchDeleteHooks"); 563 564 if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL) 565 { 566 return FALSE; 567 } 568 569 return KbSwitchSetHooks(); 570 } 571 572 VOID 573 DeleteHooks(VOID) 574 { 575 if (KbSwitchDeleteHooks) 576 { 577 KbSwitchDeleteHooks(); 578 KbSwitchDeleteHooks = NULL; 579 } 580 if (g_hHookDLL) 581 { 582 FreeLibrary(g_hHookDLL); 583 g_hHookDLL = NULL; 584 } 585 } 586 587 ULONG 588 GetNextLayout(VOID) 589 { 590 TCHAR szLayoutNum[3 + 1], szLayoutID[CCH_LAYOUT_ID + 1]; 591 ULONG uLayoutNum, uMaxNum = GetMaxLayoutNum(); 592 593 for (uLayoutNum = ulCurrentLayoutNum + 1; ; ++uLayoutNum) 594 { 595 if (uLayoutNum > uMaxNum) 596 uLayoutNum = 1; 597 if (uLayoutNum == ulCurrentLayoutNum) 598 break; 599 600 _ultot(uLayoutNum, szLayoutNum, 10); 601 if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID))) 602 return uLayoutNum; 603 } 604 605 return ulCurrentLayoutNum; 606 } 607 608 UINT 609 UpdateLanguageDisplay(HWND hwnd, HKL hKl) 610 { 611 TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH]; 612 LANGID LangID; 613 614 GetLayoutIDByHkl(hKl, szLCID, ARRAYSIZE(szLCID)); 615 LangID = (LANGID)_tcstoul(szLCID, NULL, 16); 616 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName)); 617 UpdateTrayIcon(hwnd, szLCID, szLangName); 618 619 return 0; 620 } 621 622 HWND 623 GetTargetWindow(HWND hwndFore) 624 { 625 TCHAR szClass[64]; 626 HWND hwndIME; 627 HWND hwndTarget = hwndFore; 628 if (hwndTarget == NULL) 629 hwndTarget = GetForegroundWindow(); 630 631 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)); 632 if (_tcsicmp(szClass, szKbSwitcherName) == 0) 633 hwndTarget = g_hwndLastActive; 634 635 hwndIME = ImmGetDefaultIMEWnd(hwndTarget); 636 return (hwndIME ? hwndIME : hwndTarget); 637 } 638 639 UINT 640 UpdateLanguageDisplayCurrent(HWND hwnd, HWND hwndFore) 641 { 642 DWORD dwThreadID = GetWindowThreadProcessId(GetTargetWindow(hwndFore), NULL); 643 HKL hKL = GetKeyboardLayout(dwThreadID); 644 UpdateLanguageDisplay(hwnd, hKL); 645 646 return 0; 647 } 648 649 static UINT GetCurLayoutNum(HKL hKL) 650 { 651 UINT i, nCount; 652 HKL ahKL[256]; 653 654 nCount = GetKeyboardLayoutList(ARRAYSIZE(ahKL), ahKL); 655 for (i = 0; i < nCount; ++i) 656 { 657 if (ahKL[i] == hKL) 658 return i + 1; 659 } 660 661 return 0; 662 } 663 664 static BOOL RememberLastActive(HWND hwnd, HWND hwndFore) 665 { 666 TCHAR szClass[64]; 667 668 hwndFore = GetAncestor(hwndFore, GA_ROOT); 669 670 if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, ARRAYSIZE(szClass))) 671 return FALSE; 672 673 if (_tcsicmp(szClass, szKbSwitcherName) == 0 || 674 _tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0) 675 { 676 return FALSE; /* Special window */ 677 } 678 679 /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and 680 DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */ 681 if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0) 682 { 683 HKL hKL = GetKeyboardLayout(0); 684 UpdateLanguageDisplay(hwnd, hKL); 685 } 686 687 g_hwndLastActive = hwndFore; 688 return TRUE; 689 } 690 691 LRESULT CALLBACK 692 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) 693 { 694 static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL; 695 static UINT s_uTaskbarRestart; 696 POINT pt; 697 HMENU hLeftPopupMenu; 698 699 switch (Message) 700 { 701 case WM_CREATE: 702 { 703 if (!SetHooks()) 704 { 705 MessageBox(NULL, TEXT("SetHooks failed."), NULL, MB_ICONERROR); 706 return -1; 707 } 708 709 AddTrayIcon(hwnd); 710 711 ActivateLayout(hwnd, ulCurrentLayoutNum, NULL, TRUE); 712 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); 713 break; 714 } 715 716 case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */ 717 { 718 UpdateLanguageDisplay(hwnd, (HKL)lParam); 719 break; 720 } 721 722 case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */ 723 { 724 HWND hwndFore = GetForegroundWindow(); 725 if (RememberLastActive(hwnd, hwndFore)) 726 return UpdateLanguageDisplayCurrent(hwnd, hwndFore); 727 break; 728 } 729 730 case WM_NOTIFYICONMSG: 731 { 732 switch (lParam) 733 { 734 case WM_RBUTTONUP: 735 case WM_LBUTTONUP: 736 { 737 GetCursorPos(&pt); 738 SetForegroundWindow(hwnd); 739 740 if (lParam == WM_LBUTTONUP) 741 { 742 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */ 743 hLeftPopupMenu = BuildLeftPopupMenu(); 744 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL); 745 DestroyMenu(hLeftPopupMenu); 746 } 747 else 748 { 749 if (!s_hRightPopupMenu) 750 { 751 s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)); 752 s_hRightPopupMenu = GetSubMenu(s_hMenu, 0); 753 } 754 TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL); 755 } 756 757 PostMessage(hwnd, WM_NULL, 0, 0); 758 break; 759 } 760 } 761 break; 762 } 763 764 case WM_COMMAND: 765 switch (LOWORD(wParam)) 766 { 767 case ID_EXIT: 768 { 769 PostMessage(hwnd, WM_CLOSE, 0, 0); 770 break; 771 } 772 773 case ID_PREFERENCES: 774 { 775 INT_PTR ret = (INT_PTR)ShellExecute(hwnd, NULL, 776 TEXT("control.exe"), TEXT("input.dll"), 777 NULL, SW_SHOWNORMAL); 778 if (ret <= 32) 779 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_ICONERROR); 780 break; 781 } 782 783 case ID_NEXTLAYOUT: 784 { 785 HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL; 786 DWORD dwThreadID; 787 HKL hKL; 788 UINT uNum; 789 TCHAR szClass[64]; 790 BOOL bCONWND = FALSE; 791 792 if (hwndTarget == NULL) 793 hwndTarget = g_hwndLastActive; 794 795 /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and 796 DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */ 797 if (hwndTarget && 798 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)) && 799 _tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0) 800 { 801 bCONWND = TRUE; 802 hwndTargetSave = hwndTarget; 803 hwndTarget = NULL; 804 } 805 806 if (hwndTarget) 807 { 808 dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL); 809 hKL = GetKeyboardLayout(dwThreadID); 810 uNum = GetCurLayoutNum(hKL); 811 if (uNum != 0) 812 ulCurrentLayoutNum = uNum; 813 } 814 815 ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE); 816 817 /* FIXME: CONWND is multithreaded but KLF_SETFORPROCESS and 818 DefWindowProc.WM_INPUTLANGCHANGEREQUEST won't work yet */ 819 if (bCONWND) 820 { 821 ActivateLayout(hwnd, ulCurrentLayoutNum, hwndTargetSave, TRUE); 822 } 823 break; 824 } 825 826 default: 827 { 828 if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000) 829 { 830 if (!IsWindow(g_hwndLastActive)) 831 { 832 g_hwndLastActive = NULL; 833 } 834 ActivateLayout(hwnd, LOWORD(wParam), g_hwndLastActive, FALSE); 835 } 836 break; 837 } 838 } 839 break; 840 841 case WM_SETTINGCHANGE: 842 { 843 if (wParam == SPI_SETNONCLIENTMETRICS) 844 { 845 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam); 846 break; 847 } 848 } 849 break; 850 851 case WM_DESTROY: 852 { 853 DeleteHooks(); 854 DestroyMenu(s_hMenu); 855 DeleteTrayIcon(hwnd); 856 PostQuitMessage(0); 857 break; 858 } 859 860 default: 861 { 862 if (Message == s_uTaskbarRestart) 863 { 864 AddTrayIcon(hwnd); 865 break; 866 } 867 else if (Message == ShellHookMessage) 868 { 869 if (wParam == HSHELL_LANGUAGE) 870 PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam); 871 else if (wParam == HSHELL_WINDOWACTIVATED) 872 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam); 873 874 break; 875 } 876 return DefWindowProc(hwnd, Message, wParam, lParam); 877 } 878 } 879 880 return 0; 881 } 882 883 INT WINAPI 884 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow) 885 { 886 WNDCLASS WndClass; 887 MSG msg; 888 HANDLE hMutex; 889 HWND hwnd; 890 891 switch (GetUserDefaultUILanguage()) 892 { 893 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT): 894 SetProcessDefaultLayout(LAYOUT_RTL); 895 break; 896 default: 897 break; 898 } 899 900 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName); 901 if (!hMutex) 902 return 1; 903 904 if (GetLastError() == ERROR_ALREADY_EXISTS) 905 { 906 CloseHandle(hMutex); 907 return 1; 908 } 909 910 hInst = hInstance; 911 hProcessHeap = GetProcessHeap(); 912 913 ZeroMemory(&WndClass, sizeof(WndClass)); 914 WndClass.lpfnWndProc = WndProc; 915 WndClass.hInstance = hInstance; 916 WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); 917 WndClass.lpszClassName = szKbSwitcherName; 918 if (!RegisterClass(&WndClass)) 919 { 920 CloseHandle(hMutex); 921 return 1; 922 } 923 924 hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL); 925 ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK"); 926 RegisterShellHookWindow(hwnd); 927 928 while (GetMessage(&msg, NULL, 0, 0)) 929 { 930 TranslateMessage(&msg); 931 DispatchMessage(&msg); 932 } 933 934 CloseHandle(hMutex); 935 return 0; 936 } 937