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