[PRINTING]
[reactos.git] / reactos / base / applications / kbswitch / kbswitch.c
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 substitude 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 CALLBACK
400 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
401 {
402 static HMENU hRightPopupMenu;
403 static TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
404 static UINT s_uTaskbarRestart;
405
406 switch (Message)
407 {
408 case WM_CREATE:
409 {
410 SetHooks();
411 AddTrayIcon(hwnd);
412 hRightPopupMenu = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)), 0);
413
414 ActivateLayout(hwnd, ulCurrentLayoutNum);
415 s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
416
417 return 0;
418 }
419
420 case WM_LANG_CHANGED:
421 {
422 GetLayoutIDByHkl((HKL)lParam, szLCID, ARRAYSIZE(szLCID));
423 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, ARRAYSIZE(szLangName));
424 UpdateTrayIcon(hwnd, szLCID, szLangName);
425
426 return 0;
427 }
428
429 case WM_LOAD_LAYOUT:
430 {
431 ActivateLayout(hwnd, GetNextLayout());
432
433 return 0;
434 }
435
436 case WM_WINDOW_ACTIVATE:
437 {
438 GetLayoutIDByHkl(GetKeyboardLayout(GetWindowThreadProcessId((HWND)wParam, 0)), szLCID, ARRAYSIZE(szLCID));
439 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, ARRAYSIZE(szLangName));
440 UpdateTrayIcon(hwnd, szLCID, szLangName);
441
442 return 0;
443 }
444
445 case WM_NOTIFYICONMSG:
446 switch (lParam)
447 {
448 case WM_RBUTTONUP:
449 case WM_LBUTTONUP:
450 {
451 POINT pt;
452
453 GetCursorPos(&pt);
454 SetForegroundWindow(hwnd);
455
456 if (lParam == WM_LBUTTONUP)
457 {
458 HMENU hLeftPopupMenu;
459 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
460 hLeftPopupMenu = BuildLeftPopupMenu();
461 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
462 DestroyMenu(hLeftPopupMenu);
463 }
464 else
465 {
466 TrackPopupMenu(hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
467 }
468
469 PostMessage(hwnd, WM_NULL, 0, 0);
470
471 return 0;
472 }
473 }
474 break;
475
476 case WM_COMMAND:
477 switch (LOWORD(wParam))
478 {
479 case ID_EXIT:
480 SendMessage(hwnd, WM_CLOSE, 0, 0);
481 return 0;
482
483 case ID_PREFERENCES:
484 {
485 SHELLEXECUTEINFO shInputDll = {0};
486
487 shInputDll.cbSize = sizeof(shInputDll);
488 shInputDll.hwnd = hwnd;
489 shInputDll.lpVerb = _T("open");
490 shInputDll.lpFile = _T("rundll32.exe");
491 shInputDll.lpParameters = _T("shell32.dll,Control_RunDLL input.dll");
492
493 if (!ShellExecuteEx(&shInputDll))
494 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_OK | MB_ICONERROR);
495 }
496
497 default:
498 ActivateLayout(hwnd, LOWORD(wParam));
499 return 0;
500 }
501 break;
502
503 case WM_SETTINGCHANGE:
504 {
505 if (wParam == SPI_SETDEFAULTINPUTLANG)
506 {
507 //FIXME: Should detect default language changes by CPL applet or by other tools and update UI
508 }
509 }
510 break;
511
512 case WM_DESTROY:
513 {
514 DeleteHooks();
515 DestroyMenu(hRightPopupMenu);
516 DelTrayIcon(hwnd);
517 PostQuitMessage(0);
518
519 return 0;
520 }
521
522 default:
523 if(Message == s_uTaskbarRestart)
524 AddTrayIcon(hwnd);
525 break;
526 }
527
528 if (Message == ShellHookMessage && wParam == HSHELL_LANGUAGE)
529 {
530 PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
531 return 0;
532 }
533
534 return DefWindowProc(hwnd, Message, wParam, lParam);
535 }
536
537 INT WINAPI
538 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
539 {
540 WNDCLASS WndClass = {0};
541 MSG msg;
542 HANDLE hMutex;
543 HWND hwnd;
544
545 switch (GetUserDefaultUILanguage())
546 {
547 case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
548 SetProcessDefaultLayout(LAYOUT_RTL);
549 break;
550 default:
551 break;
552 }
553
554 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
555 if (!hMutex)
556 return 1;
557
558 if (GetLastError() == ERROR_ALREADY_EXISTS)
559 {
560 CloseHandle(hMutex);
561 return 1;
562 }
563
564 hInst = hInstance;
565 hProcessHeap = GetProcessHeap();
566
567 WndClass.style = 0;
568 WndClass.lpfnWndProc = WndProc;
569 WndClass.cbClsExtra = 0;
570 WndClass.cbWndExtra = 0;
571 WndClass.hInstance = hInstance;
572 WndClass.hIcon = NULL;
573 WndClass.hCursor = NULL;
574 WndClass.hbrBackground = NULL;
575 WndClass.lpszMenuName = NULL;
576 WndClass.lpszClassName = szKbSwitcherName;
577
578 if (!RegisterClass(&WndClass))
579 {
580 CloseHandle(hMutex);
581 return 1;
582 }
583
584 hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
585 ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
586 RegisterShellHookWindow(hwnd);
587
588 while(GetMessage(&msg,NULL,0,0))
589 {
590 TranslateMessage(&msg);
591 DispatchMessage(&msg);
592 }
593
594 CloseHandle(hMutex);
595
596 return 0;
597 }