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 <shlobj.h> 12 #include <shlwapi_undoc.h> 13 #include <imm.h> 14 #include <imm32_undoc.h> 15 16 /* 17 * This program kbswitch is a mimic of Win2k's internat.exe. 18 * However, there are some differences. 19 * 20 * Comparing with WinNT4 ActivateKeyboardLayout, WinXP ActivateKeyboardLayout has 21 * process boundary, so we cannot activate the IME keyboard layout from the outer process. 22 * It needs special care. 23 * 24 * We use global hook by our kbsdll.dll, to watch the shell and the windows. 25 * 26 * It might not work correctly on Vista+ because keyboard layout change notification 27 * won't be generated in Vista+. 28 */ 29 30 #define WM_NOTIFYICONMSG (WM_USER + 248) 31 32 PKBSWITCHSETHOOKS KbSwitchSetHooks = NULL; 33 PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks = NULL; 34 UINT ShellHookMessage = 0; 35 36 HINSTANCE hInst; 37 HANDLE hProcessHeap; 38 HMODULE g_hHookDLL = NULL; 39 INT g_nCurrentLayoutNum = 1; 40 HICON g_hTrayIcon = NULL; 41 HWND g_hwndLastActive = NULL; 42 INT g_cKLs = 0; 43 HKL g_ahKLs[64]; 44 45 typedef struct 46 { 47 DWORD dwLayoutId; 48 HKL hKL; 49 TCHAR szKLID[CCH_LAYOUT_ID + 1]; 50 } SPECIAL_ID, *PSPECIAL_ID; 51 52 SPECIAL_ID g_SpecialIds[80]; 53 INT g_cSpecialIds = 0; 54 55 static VOID LoadSpecialIds(VOID) 56 { 57 TCHAR szKLID[KL_NAMELENGTH], szLayoutId[16]; 58 DWORD dwSize, dwIndex; 59 HKEY hKey, hLayoutKey; 60 61 g_cSpecialIds = 0; 62 63 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, 64 TEXT("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts"), 65 0, KEY_READ, &hKey) != ERROR_SUCCESS) 66 { 67 return; 68 } 69 70 for (dwIndex = 0; dwIndex < 1000; ++dwIndex) 71 { 72 dwSize = ARRAYSIZE(szKLID); 73 if (RegEnumKeyEx(hKey, dwIndex, szKLID, &dwSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS) 74 break; 75 76 if (RegOpenKeyEx(hKey, szKLID, 0, KEY_READ, &hLayoutKey) != ERROR_SUCCESS) 77 continue; 78 79 dwSize = sizeof(szLayoutId); 80 if (RegQueryValueEx(hLayoutKey, TEXT("Layout Id"), NULL, NULL, 81 (LPBYTE)szLayoutId, &dwSize) == ERROR_SUCCESS) 82 { 83 DWORD dwKLID = _tcstoul(szKLID, NULL, 16); 84 WORD wLangId = LOWORD(dwKLID), wLayoutId = LOWORD(_tcstoul(szLayoutId, NULL, 16)); 85 HKL hKL = (HKL)(LONG_PTR)(SPECIAL_MASK | MAKELONG(wLangId, wLayoutId)); 86 87 /* Add a special ID */ 88 g_SpecialIds[g_cSpecialIds].dwLayoutId = wLayoutId; 89 g_SpecialIds[g_cSpecialIds].hKL = hKL; 90 StringCchCopy(g_SpecialIds[g_cSpecialIds].szKLID, 91 ARRAYSIZE(g_SpecialIds[g_cSpecialIds].szKLID), szKLID); 92 ++g_cSpecialIds; 93 } 94 95 RegCloseKey(hLayoutKey); 96 97 if (g_cSpecialIds >= ARRAYSIZE(g_SpecialIds)) 98 { 99 OutputDebugStringA("g_SpecialIds is full!"); 100 break; 101 } 102 } 103 104 RegCloseKey(hKey); 105 } 106 107 static VOID 108 GetKLIDFromHKL(HKL hKL, LPTSTR szKLID, SIZE_T KLIDLength) 109 { 110 szKLID[0] = 0; 111 112 if (IS_IME_HKL(hKL)) 113 { 114 StringCchPrintf(szKLID, KLIDLength, _T("%08lx"), (DWORD)(DWORD_PTR)hKL); 115 return; 116 } 117 118 if (IS_SPECIAL_HKL(hKL)) 119 { 120 INT i; 121 for (i = 0; i < g_cSpecialIds; ++i) 122 { 123 if (g_SpecialIds[i].hKL == hKL) 124 { 125 StringCchCopy(szKLID, KLIDLength, g_SpecialIds[i].szKLID); 126 return; 127 } 128 } 129 } 130 else 131 { 132 StringCchPrintf(szKLID, KLIDLength, _T("%08lx"), LOWORD(hKL)); 133 } 134 } 135 136 static VOID UpdateLayoutList(HKL hKL OPTIONAL) 137 { 138 INT iKL; 139 140 if (!hKL) 141 { 142 HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow()); 143 DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL); 144 hKL = GetKeyboardLayout(dwTID); 145 } 146 147 g_cKLs = GetKeyboardLayoutList(ARRAYSIZE(g_ahKLs), g_ahKLs); 148 149 g_nCurrentLayoutNum = -1; 150 for (iKL = 0; iKL < g_cKLs; ++iKL) 151 { 152 if (g_ahKLs[iKL] == hKL) 153 { 154 g_nCurrentLayoutNum = iKL + 1; 155 break; 156 } 157 } 158 159 if (g_nCurrentLayoutNum == -1 && g_cKLs < ARRAYSIZE(g_ahKLs)) 160 { 161 g_nCurrentLayoutNum = g_cKLs; 162 g_ahKLs[g_cKLs++] = hKL; 163 } 164 } 165 166 static HKL GetHKLFromLayoutNum(INT nLayoutNum) 167 { 168 if (0 <= (nLayoutNum - 1) && (nLayoutNum - 1) < g_cKLs) 169 { 170 return g_ahKLs[nLayoutNum - 1]; 171 } 172 else 173 { 174 HWND hwndTarget = (g_hwndLastActive ? g_hwndLastActive : GetForegroundWindow()); 175 DWORD dwTID = GetWindowThreadProcessId(hwndTarget, NULL); 176 return GetKeyboardLayout(dwTID); 177 } 178 } 179 180 static VOID 181 GetKLIDFromLayoutNum(INT nLayoutNum, LPTSTR szKLID, SIZE_T KLIDLength) 182 { 183 GetKLIDFromHKL(GetHKLFromLayoutNum(nLayoutNum), szKLID, KLIDLength); 184 } 185 186 static BOOL 187 GetSystemLibraryPath(LPTSTR szPath, SIZE_T cchPath, LPCTSTR FileName) 188 { 189 if (!GetSystemDirectory(szPath, cchPath)) 190 return FALSE; 191 192 StringCchCat(szPath, cchPath, TEXT("\\")); 193 StringCchCat(szPath, cchPath, FileName); 194 return TRUE; 195 } 196 197 static BOOL 198 GetLayoutName(INT nLayoutNum, LPTSTR szName, SIZE_T NameLength) 199 { 200 HKEY hKey; 201 HRESULT hr; 202 DWORD dwBufLen; 203 TCHAR szBuf[MAX_PATH], szKLID[CCH_LAYOUT_ID + 1]; 204 205 GetKLIDFromLayoutNum(nLayoutNum, szKLID, ARRAYSIZE(szKLID)); 206 207 StringCchPrintf(szBuf, ARRAYSIZE(szBuf), 208 _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szKLID); 209 210 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, KEY_READ, &hKey) != ERROR_SUCCESS) 211 return FALSE; 212 213 /* Use "Layout Display Name" value as an entry name if possible */ 214 hr = SHLoadRegUIString(hKey, _T("Layout Display Name"), szName, NameLength); 215 if (SUCCEEDED(hr)) 216 { 217 RegCloseKey(hKey); 218 return TRUE; 219 } 220 221 /* Otherwise, use "Layout Text" value as an entry name */ 222 dwBufLen = NameLength * sizeof(TCHAR); 223 if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL, 224 (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS) 225 { 226 RegCloseKey(hKey); 227 return TRUE; 228 } 229 230 RegCloseKey(hKey); 231 return FALSE; 232 } 233 234 static BOOL GetImeFile(LPTSTR szImeFile, SIZE_T cchImeFile, LPCTSTR szKLID) 235 { 236 HKEY hKey; 237 DWORD dwBufLen; 238 TCHAR szBuf[MAX_PATH]; 239 240 szImeFile[0] = UNICODE_NULL; 241 242 if (_tcslen(szKLID) != CCH_LAYOUT_ID) 243 return FALSE; /* Invalid LCID */ 244 245 if (szKLID[0] != TEXT('E') && szKLID[0] != TEXT('e')) 246 return FALSE; /* Not an IME HKL */ 247 248 StringCchPrintf(szBuf, ARRAYSIZE(szBuf), 249 _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szKLID); 250 251 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szBuf, 0, KEY_QUERY_VALUE, &hKey) != ERROR_SUCCESS) 252 return FALSE; 253 254 dwBufLen = cchImeFile * sizeof(TCHAR); 255 if (RegQueryValueEx(hKey, _T("IME File"), NULL, NULL, 256 (LPBYTE)szImeFile, &dwBufLen) != ERROR_SUCCESS) 257 { 258 szImeFile[0] = UNICODE_NULL; 259 } 260 261 RegCloseKey(hKey); 262 263 return (szImeFile[0] != UNICODE_NULL); 264 } 265 266 typedef struct tagLOAD_ICON 267 { 268 INT cxIcon, cyIcon; 269 HICON hIcon; 270 } LOAD_ICON, *PLOAD_ICON; 271 272 static BOOL CALLBACK 273 EnumResNameProc( 274 HMODULE hModule, 275 LPCTSTR lpszType, 276 LPTSTR lpszName, 277 LPARAM lParam) 278 { 279 PLOAD_ICON pLoadIcon = (PLOAD_ICON)lParam; 280 pLoadIcon->hIcon = (HICON)LoadImage(hModule, lpszName, IMAGE_ICON, 281 pLoadIcon->cxIcon, pLoadIcon->cyIcon, 282 LR_DEFAULTCOLOR); 283 if (pLoadIcon->hIcon) 284 return FALSE; /* Stop enumeration */ 285 return TRUE; 286 } 287 288 static HICON FakeExtractIcon(LPCTSTR szIconPath, INT cxIcon, INT cyIcon) 289 { 290 LOAD_ICON LoadIcon = { cxIcon, cyIcon, NULL }; 291 HMODULE hImeDLL = LoadLibraryEx(szIconPath, NULL, LOAD_LIBRARY_AS_DATAFILE); 292 if (hImeDLL) 293 { 294 EnumResourceNames(hImeDLL, RT_GROUP_ICON, EnumResNameProc, (LPARAM)&LoadIcon); 295 FreeLibrary(hImeDLL); 296 } 297 return LoadIcon.hIcon; 298 } 299 300 static HBITMAP BitmapFromIcon(HICON hIcon) 301 { 302 HDC hdcScreen = GetDC(NULL); 303 HDC hdc = CreateCompatibleDC(hdcScreen); 304 INT cxIcon = GetSystemMetrics(SM_CXSMICON); 305 INT cyIcon = GetSystemMetrics(SM_CYSMICON); 306 HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon); 307 HGDIOBJ hbmOld; 308 309 if (hbm != NULL) 310 { 311 hbmOld = SelectObject(hdc, hbm); 312 DrawIconEx(hdc, 0, 0, hIcon, cxIcon, cyIcon, 0, GetSysColorBrush(COLOR_MENU), DI_NORMAL); 313 SelectObject(hdc, hbmOld); 314 } 315 316 DeleteDC(hdc); 317 ReleaseDC(NULL, hdcScreen); 318 return hbm; 319 } 320 321 static HICON 322 CreateTrayIcon(LPTSTR szKLID, LPCTSTR szImeFile OPTIONAL) 323 { 324 LANGID LangID; 325 TCHAR szBuf[4]; 326 HDC hdcScreen, hdc; 327 HBITMAP hbmColor, hbmMono, hBmpOld; 328 HFONT hFont, hFontOld; 329 LOGFONT lf; 330 RECT rect; 331 ICONINFO IconInfo; 332 HICON hIcon; 333 INT cxIcon = GetSystemMetrics(SM_CXSMICON); 334 INT cyIcon = GetSystemMetrics(SM_CYSMICON); 335 TCHAR szPath[MAX_PATH]; 336 337 if (szImeFile && szImeFile[0]) 338 { 339 if (GetSystemLibraryPath(szPath, ARRAYSIZE(szPath), szImeFile)) 340 return FakeExtractIcon(szPath, cxIcon, cyIcon); 341 } 342 343 /* Getting "EN", "FR", etc. from English, French, ... */ 344 LangID = LANGIDFROMLCID(_tcstoul(szKLID, NULL, 16)); 345 if (GetLocaleInfo(LangID, 346 LOCALE_SABBREVLANGNAME | LOCALE_NOUSEROVERRIDE, 347 szBuf, 348 ARRAYSIZE(szBuf)) == 0) 349 { 350 szBuf[0] = szBuf[1] = _T('?'); 351 } 352 szBuf[2] = 0; /* Truncate the identifier to two characters: "ENG" --> "EN" etc. */ 353 354 /* Create hdc, hbmColor and hbmMono */ 355 hdcScreen = GetDC(NULL); 356 hdc = CreateCompatibleDC(hdcScreen); 357 hbmColor = CreateCompatibleBitmap(hdcScreen, cxIcon, cyIcon); 358 ReleaseDC(NULL, hdcScreen); 359 hbmMono = CreateBitmap(cxIcon, cyIcon, 1, 1, NULL); 360 361 /* Checking NULL */ 362 if (!hdc || !hbmColor || !hbmMono) 363 { 364 if (hbmMono) 365 DeleteObject(hbmMono); 366 if (hbmColor) 367 DeleteObject(hbmColor); 368 if (hdc) 369 DeleteDC(hdc); 370 return NULL; 371 } 372 373 /* Create a font */ 374 hFont = NULL; 375 if (SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0)) 376 { 377 /* Override the current size with something manageable */ 378 lf.lfHeight = -11; 379 lf.lfWidth = 0; 380 hFont = CreateFontIndirect(&lf); 381 } 382 if (!hFont) 383 hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); 384 385 SetRect(&rect, 0, 0, cxIcon, cyIcon); 386 387 /* Draw hbmColor */ 388 hBmpOld = SelectObject(hdc, hbmColor); 389 SetDCBrushColor(hdc, GetSysColor(COLOR_HIGHLIGHT)); 390 FillRect(hdc, &rect, (HBRUSH)GetStockObject(DC_BRUSH)); 391 hFontOld = SelectObject(hdc, hFont); 392 SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); 393 SetBkMode(hdc, TRANSPARENT); 394 DrawText(hdc, szBuf, 2, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); 395 SelectObject(hdc, hFontOld); 396 397 /* Fill hbmMono with black */ 398 SelectObject(hdc, hbmMono); 399 PatBlt(hdc, 0, 0, cxIcon, cyIcon, BLACKNESS); 400 SelectObject(hdc, hBmpOld); 401 402 /* Create an icon from hbmColor and hbmMono */ 403 IconInfo.fIcon = TRUE; 404 IconInfo.xHotspot = IconInfo.yHotspot = 0; 405 IconInfo.hbmColor = hbmColor; 406 IconInfo.hbmMask = hbmMono; 407 hIcon = CreateIconIndirect(&IconInfo); 408 409 /* Clean up */ 410 DeleteObject(hFont); 411 DeleteObject(hbmMono); 412 DeleteObject(hbmColor); 413 DeleteDC(hdc); 414 415 return hIcon; 416 } 417 418 static VOID 419 AddTrayIcon(HWND hwnd) 420 { 421 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP }; 422 TCHAR szKLID[CCH_LAYOUT_ID + 1], szName[MAX_PATH], szImeFile[80]; 423 424 GetKLIDFromLayoutNum(g_nCurrentLayoutNum, szKLID, ARRAYSIZE(szKLID)); 425 GetLayoutName(g_nCurrentLayoutNum, szName, ARRAYSIZE(szName)); 426 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID); 427 428 tnid.uCallbackMessage = WM_NOTIFYICONMSG; 429 tnid.hIcon = CreateTrayIcon(szKLID, szImeFile); 430 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName); 431 432 Shell_NotifyIcon(NIM_ADD, &tnid); 433 434 if (g_hTrayIcon) 435 DestroyIcon(g_hTrayIcon); 436 g_hTrayIcon = tnid.hIcon; 437 } 438 439 static VOID 440 DeleteTrayIcon(HWND hwnd) 441 { 442 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1 }; 443 Shell_NotifyIcon(NIM_DELETE, &tnid); 444 445 if (g_hTrayIcon) 446 { 447 DestroyIcon(g_hTrayIcon); 448 g_hTrayIcon = NULL; 449 } 450 } 451 452 static VOID 453 UpdateTrayIcon(HWND hwnd, LPTSTR szKLID, LPTSTR szName) 454 { 455 NOTIFYICONDATA tnid = { sizeof(tnid), hwnd, 1, NIF_ICON | NIF_MESSAGE | NIF_TIP }; 456 TCHAR szImeFile[80]; 457 458 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID); 459 460 tnid.uCallbackMessage = WM_NOTIFYICONMSG; 461 tnid.hIcon = CreateTrayIcon(szKLID, szImeFile); 462 StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName); 463 464 Shell_NotifyIcon(NIM_MODIFY, &tnid); 465 466 if (g_hTrayIcon) 467 DestroyIcon(g_hTrayIcon); 468 g_hTrayIcon = tnid.hIcon; 469 } 470 471 static BOOL CALLBACK 472 EnumWindowsProc(HWND hwnd, LPARAM lParam) 473 { 474 PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, INPUTLANGCHANGE_SYSCHARSET, lParam); 475 return TRUE; 476 } 477 478 static VOID 479 ActivateLayout(HWND hwnd, ULONG uLayoutNum, HWND hwndTarget OPTIONAL, BOOL bNoActivate) 480 { 481 HKL hKl; 482 TCHAR szKLID[CCH_LAYOUT_ID + 1], szLangName[MAX_PATH]; 483 LANGID LangID; 484 485 /* The layout number starts from one. Zero is invalid */ 486 if (uLayoutNum == 0 || uLayoutNum > 0xFF) /* Invalid */ 487 return; 488 489 GetKLIDFromLayoutNum(uLayoutNum, szKLID, ARRAYSIZE(szKLID)); 490 LangID = (LANGID)_tcstoul(szKLID, NULL, 16); 491 492 /* Switch to the new keyboard layout */ 493 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName)); 494 UpdateTrayIcon(hwnd, szKLID, szLangName); 495 496 if (hwndTarget && !bNoActivate) 497 SetForegroundWindow(hwndTarget); 498 499 hKl = LoadKeyboardLayout(szKLID, KLF_ACTIVATE); 500 if (hKl) 501 ActivateKeyboardLayout(hKl, KLF_SETFORPROCESS); 502 503 /* Post WM_INPUTLANGCHANGEREQUEST */ 504 if (hwndTarget) 505 { 506 PostMessage(hwndTarget, WM_INPUTLANGCHANGEREQUEST, 507 INPUTLANGCHANGE_SYSCHARSET, (LPARAM)hKl); 508 } 509 else 510 { 511 EnumWindows(EnumWindowsProc, (LPARAM) hKl); 512 } 513 514 g_nCurrentLayoutNum = uLayoutNum; 515 } 516 517 static HMENU 518 BuildLeftPopupMenu(VOID) 519 { 520 HMENU hMenu = CreatePopupMenu(); 521 TCHAR szName[MAX_PATH], szKLID[CCH_LAYOUT_ID + 1], szImeFile[80]; 522 HICON hIcon; 523 MENUITEMINFO mii = { sizeof(mii) }; 524 INT iKL; 525 526 for (iKL = 0; iKL < g_cKLs; ++iKL) 527 { 528 GetKLIDFromHKL(g_ahKLs[iKL], szKLID, ARRAYSIZE(szKLID)); 529 GetImeFile(szImeFile, ARRAYSIZE(szImeFile), szKLID); 530 531 if (!GetLayoutName(iKL + 1, szName, ARRAYSIZE(szName))) 532 continue; 533 534 mii.fMask = MIIM_ID | MIIM_STRING; 535 mii.wID = iKL + 1; 536 mii.dwTypeData = szName; 537 538 hIcon = CreateTrayIcon(szKLID, szImeFile); 539 if (hIcon) 540 { 541 mii.hbmpItem = BitmapFromIcon(hIcon); 542 if (mii.hbmpItem) 543 mii.fMask |= MIIM_BITMAP; 544 } 545 546 InsertMenuItem(hMenu, -1, TRUE, &mii); 547 DestroyIcon(hIcon); 548 } 549 550 CheckMenuItem(hMenu, g_nCurrentLayoutNum, MF_CHECKED); 551 552 return hMenu; 553 } 554 555 BOOL 556 SetHooks(VOID) 557 { 558 g_hHookDLL = LoadLibrary(_T("kbsdll.dll")); 559 if (!g_hHookDLL) 560 { 561 return FALSE; 562 } 563 564 KbSwitchSetHooks = (PKBSWITCHSETHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchSetHooks"); 565 KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(g_hHookDLL, "KbSwitchDeleteHooks"); 566 567 if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL) 568 { 569 return FALSE; 570 } 571 572 return KbSwitchSetHooks(); 573 } 574 575 VOID 576 DeleteHooks(VOID) 577 { 578 if (KbSwitchDeleteHooks) 579 { 580 KbSwitchDeleteHooks(); 581 KbSwitchDeleteHooks = NULL; 582 } 583 if (g_hHookDLL) 584 { 585 FreeLibrary(g_hHookDLL); 586 g_hHookDLL = NULL; 587 } 588 } 589 590 static UINT GetLayoutNum(HKL hKL) 591 { 592 INT iKL; 593 594 for (iKL = 0; iKL < g_cKLs; ++iKL) 595 { 596 if (g_ahKLs[iKL] == hKL) 597 return iKL + 1; 598 } 599 600 return 0; 601 } 602 603 ULONG 604 GetNextLayout(VOID) 605 { 606 return (g_nCurrentLayoutNum % g_cKLs) + 1; 607 } 608 609 UINT 610 UpdateLanguageDisplay(HWND hwnd, HKL hKL) 611 { 612 TCHAR szKLID[MAX_PATH], szLangName[MAX_PATH]; 613 LANGID LangID; 614 615 GetKLIDFromHKL(hKL, szKLID, ARRAYSIZE(szKLID)); 616 LangID = (LANGID)_tcstoul(szKLID, NULL, 16); 617 GetLocaleInfo(LangID, LOCALE_SLANGUAGE, szLangName, ARRAYSIZE(szLangName)); 618 UpdateTrayIcon(hwnd, szKLID, szLangName); 619 g_nCurrentLayoutNum = GetLayoutNum(hKL); 620 621 return 0; 622 } 623 624 HWND 625 GetTargetWindow(HWND hwndFore) 626 { 627 TCHAR szClass[64]; 628 HWND hwndIME; 629 HWND hwndTarget = hwndFore; 630 if (hwndTarget == NULL) 631 hwndTarget = GetForegroundWindow(); 632 633 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)); 634 if (_tcsicmp(szClass, szKbSwitcherName) == 0) 635 hwndTarget = g_hwndLastActive; 636 637 hwndIME = ImmGetDefaultIMEWnd(hwndTarget); 638 return (hwndIME ? hwndIME : hwndTarget); 639 } 640 641 UINT 642 UpdateLanguageDisplayCurrent(HWND hwnd, HWND hwndFore) 643 { 644 DWORD dwThreadID = GetWindowThreadProcessId(GetTargetWindow(hwndFore), NULL); 645 HKL hKL = GetKeyboardLayout(dwThreadID); 646 UpdateLanguageDisplay(hwnd, hKL); 647 648 return 0; 649 } 650 651 static BOOL RememberLastActive(HWND hwnd, HWND hwndFore) 652 { 653 TCHAR szClass[64]; 654 655 hwndFore = GetAncestor(hwndFore, GA_ROOT); 656 657 if (!IsWindowVisible(hwndFore) || !GetClassName(hwndFore, szClass, ARRAYSIZE(szClass))) 658 return FALSE; 659 660 if (_tcsicmp(szClass, szKbSwitcherName) == 0 || 661 _tcsicmp(szClass, TEXT("Shell_TrayWnd")) == 0) 662 { 663 return FALSE; /* Special window */ 664 } 665 666 /* FIXME: CONWND needs special handling */ 667 if (_tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0) 668 { 669 HKL hKL = GetKeyboardLayout(0); 670 UpdateLanguageDisplay(hwnd, hKL); 671 } 672 673 g_hwndLastActive = hwndFore; 674 return TRUE; 675 } 676 677 LRESULT CALLBACK 678 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) 679 { 680 static HMENU s_hMenu = NULL, s_hRightPopupMenu = NULL; 681 static UINT s_uTaskbarRestart; 682 POINT pt; 683 HMENU hLeftPopupMenu; 684 685 switch (Message) 686 { 687 case WM_CREATE: 688 { 689 if (!SetHooks()) 690 { 691 MessageBox(NULL, TEXT("SetHooks failed."), NULL, MB_ICONERROR); 692 return -1; 693 } 694 695 LoadSpecialIds(); 696 697 UpdateLayoutList(NULL); 698 AddTrayIcon(hwnd); 699 700 ActivateLayout(hwnd, g_nCurrentLayoutNum, NULL, TRUE); 701 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); 702 break; 703 } 704 705 case WM_LANG_CHANGED: /* Comes from kbsdll.dll and this module */ 706 { 707 UpdateLayoutList((HKL)lParam); 708 UpdateLanguageDisplay(hwnd, (HKL)lParam); 709 break; 710 } 711 712 case WM_WINDOW_ACTIVATE: /* Comes from kbsdll.dll and this module */ 713 { 714 HWND hwndFore = GetForegroundWindow(); 715 if (RememberLastActive(hwnd, hwndFore)) 716 return UpdateLanguageDisplayCurrent(hwnd, hwndFore); 717 break; 718 } 719 720 case WM_NOTIFYICONMSG: 721 { 722 switch (lParam) 723 { 724 case WM_RBUTTONUP: 725 case WM_LBUTTONUP: 726 { 727 UpdateLayoutList(NULL); 728 729 GetCursorPos(&pt); 730 SetForegroundWindow(hwnd); 731 732 if (lParam == WM_LBUTTONUP) 733 { 734 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */ 735 hLeftPopupMenu = BuildLeftPopupMenu(); 736 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL); 737 DestroyMenu(hLeftPopupMenu); 738 } 739 else 740 { 741 if (!s_hRightPopupMenu) 742 { 743 s_hMenu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)); 744 s_hRightPopupMenu = GetSubMenu(s_hMenu, 0); 745 } 746 TrackPopupMenu(s_hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL); 747 } 748 749 PostMessage(hwnd, WM_NULL, 0, 0); 750 break; 751 } 752 } 753 break; 754 } 755 756 case WM_COMMAND: 757 switch (LOWORD(wParam)) 758 { 759 case ID_EXIT: 760 { 761 PostMessage(hwnd, WM_CLOSE, 0, 0); 762 break; 763 } 764 765 case ID_PREFERENCES: 766 { 767 INT_PTR ret = (INT_PTR)ShellExecute(hwnd, NULL, 768 TEXT("control.exe"), TEXT("input.dll"), 769 NULL, SW_SHOWNORMAL); 770 if (ret <= 32) 771 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_ICONERROR); 772 break; 773 } 774 775 case ID_NEXTLAYOUT: 776 { 777 HWND hwndTarget = (HWND)lParam, hwndTargetSave = NULL; 778 DWORD dwThreadID; 779 HKL hKL; 780 UINT uNum; 781 TCHAR szClass[64]; 782 BOOL bCONWND = FALSE; 783 784 if (hwndTarget == NULL) 785 hwndTarget = g_hwndLastActive; 786 787 /* FIXME: CONWND needs special handling */ 788 if (hwndTarget && 789 GetClassName(hwndTarget, szClass, ARRAYSIZE(szClass)) && 790 _tcsicmp(szClass, TEXT("ConsoleWindowClass")) == 0) 791 { 792 bCONWND = TRUE; 793 hwndTargetSave = hwndTarget; 794 hwndTarget = NULL; 795 } 796 797 if (hwndTarget) 798 { 799 dwThreadID = GetWindowThreadProcessId(hwndTarget, NULL); 800 hKL = GetKeyboardLayout(dwThreadID); 801 uNum = GetLayoutNum(hKL); 802 if (uNum != 0) 803 g_nCurrentLayoutNum = uNum; 804 } 805 806 ActivateLayout(hwnd, GetNextLayout(), hwndTarget, TRUE); 807 808 /* FIXME: CONWND needs special handling */ 809 if (bCONWND) 810 ActivateLayout(hwnd, g_nCurrentLayoutNum, hwndTargetSave, TRUE); 811 812 break; 813 } 814 815 default: 816 { 817 if (1 <= LOWORD(wParam) && LOWORD(wParam) <= 1000) 818 { 819 if (!IsWindow(g_hwndLastActive)) 820 { 821 g_hwndLastActive = NULL; 822 } 823 ActivateLayout(hwnd, LOWORD(wParam), g_hwndLastActive, FALSE); 824 } 825 break; 826 } 827 } 828 break; 829 830 case WM_SETTINGCHANGE: 831 { 832 if (wParam == SPI_SETNONCLIENTMETRICS) 833 { 834 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam); 835 break; 836 } 837 } 838 break; 839 840 case WM_DESTROY: 841 { 842 DeleteHooks(); 843 DestroyMenu(s_hMenu); 844 DeleteTrayIcon(hwnd); 845 PostQuitMessage(0); 846 break; 847 } 848 849 default: 850 { 851 if (Message == s_uTaskbarRestart) 852 { 853 UpdateLayoutList(NULL); 854 AddTrayIcon(hwnd); 855 break; 856 } 857 else if (Message == ShellHookMessage) 858 { 859 if (wParam == HSHELL_LANGUAGE) 860 PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam); 861 else if (wParam == HSHELL_WINDOWACTIVATED) 862 PostMessage(hwnd, WM_WINDOW_ACTIVATE, wParam, lParam); 863 864 break; 865 } 866 return DefWindowProc(hwnd, Message, wParam, lParam); 867 } 868 } 869 870 return 0; 871 } 872 873 INT WINAPI 874 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow) 875 { 876 WNDCLASS WndClass; 877 MSG msg; 878 HANDLE hMutex; 879 HWND hwnd; 880 881 switch (GetUserDefaultUILanguage()) 882 { 883 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT): 884 SetProcessDefaultLayout(LAYOUT_RTL); 885 break; 886 default: 887 break; 888 } 889 890 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName); 891 if (!hMutex) 892 return 1; 893 894 if (GetLastError() == ERROR_ALREADY_EXISTS) 895 { 896 CloseHandle(hMutex); 897 return 1; 898 } 899 900 hInst = hInstance; 901 hProcessHeap = GetProcessHeap(); 902 903 ZeroMemory(&WndClass, sizeof(WndClass)); 904 WndClass.lpfnWndProc = WndProc; 905 WndClass.hInstance = hInstance; 906 WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); 907 WndClass.lpszClassName = szKbSwitcherName; 908 if (!RegisterClass(&WndClass)) 909 { 910 CloseHandle(hMutex); 911 return 1; 912 } 913 914 hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL); 915 ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK"); 916 RegisterShellHookWindow(hwnd); 917 918 while (GetMessage(&msg, NULL, 0, 0)) 919 { 920 TranslateMessage(&msg); 921 DispatchMessage(&msg); 922 } 923 924 CloseHandle(hMutex); 925 return 0; 926 } 927