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