5521b61a60a84789f491c6bb1c1a06910ba81734
[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 PROC KbSwitchSetHooks = NULL;
14 PROC KbSwitchDeleteHooks = NULL;
15
16
17 static BOOL
18 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID);
19
20 static BOOL
21 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName);
22
23 HINSTANCE hInst;
24 HANDLE hProcessHeap;
25 HMODULE hDllLib;
26 ULONG ulCurrentLayoutNum = 1;
27 UINT cxSmIcon, cySmIcon;
28
29
30 static HICON
31 CreateTrayIcon(LPTSTR szLCID)
32 {
33 LANGID lId;
34 TCHAR szBuf[3];
35 HDC hdc, hdcsrc;
36 HBITMAP hBitmap, hBmpNew, hBmpOld;
37 RECT rect;
38 DWORD bkColor, bkText;
39 HFONT hFontOld, hFont = NULL;
40 ICONINFO IconInfo;
41 HICON hIcon = NULL;
42
43 lId = (LANGID)_tcstoul(szLCID, NULL, 16);
44 if (GetLocaleInfo(lId,
45 LOCALE_SISO639LANGNAME,
46 szBuf,
47 sizeof(szBuf) / sizeof(TCHAR)) == 0)
48 {
49 lstrcpy(szBuf, _T("??\0"));
50 }
51
52 hdcsrc = GetDC(NULL);
53 hdc = CreateCompatibleDC(hdcsrc);
54 hBitmap = CreateCompatibleBitmap(hdcsrc, cxSmIcon, cySmIcon);
55 ReleaseDC(NULL, hdcsrc);
56
57 if (hdc && hBitmap)
58 {
59 hBmpNew = CreateBitmap(cxSmIcon, cySmIcon, 1, 1, NULL);
60 if (hBmpNew)
61 {
62 hBmpOld = SelectObject(hdc, hBitmap);
63 rect.right = cxSmIcon;
64 rect.left = 0;
65 rect.bottom = cySmIcon;
66 rect.top = 0;
67
68 bkColor = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
69 bkText = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
70
71 ExtTextOut(hdc, rect.left, rect.top, ETO_OPAQUE, &rect, _T(""), 0, NULL);
72
73 hFont = (HFONT) GetStockObject(DEFAULT_GUI_FONT);
74 if (!hFont)
75 goto End;
76
77 hFontOld = SelectObject(hdc, hFont);
78 DrawText(hdc, _tcsupr(szBuf), 2, &rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
79 SelectObject(hdc, hBmpNew);
80 PatBlt(hdc, 0, 0, cxSmIcon, cySmIcon, BLACKNESS);
81 SelectObject(hdc, hBmpOld);
82 SelectObject(hdc, hFontOld);
83
84 IconInfo.hbmColor = hBitmap;
85 IconInfo.hbmMask = hBmpNew;
86 IconInfo.fIcon = TRUE;
87
88 hIcon = CreateIconIndirect(&IconInfo);
89 End:
90 if (hBmpNew) DeleteObject(hBmpNew);
91 if (hBmpOld) DeleteObject(hBmpOld);
92 if (hFont) DeleteObject(hFont);
93 }
94 }
95
96 if (hdc) DeleteDC(hdc);
97 if (hBitmap) DeleteObject(hBitmap);
98
99 return hIcon;
100 }
101
102 static VOID
103 AddTrayIcon(HWND hwnd)
104 {
105 NOTIFYICONDATA tnid;
106 TCHAR szLCID[CCH_LAYOUT_ID + 1];
107 TCHAR szName[MAX_PATH];
108
109 GetLayoutID(_T("1"), szLCID);
110 GetLayoutName(_T("1"), szName);
111
112 tnid.cbSize = sizeof(NOTIFYICONDATA);
113 tnid.hWnd = hwnd;
114 tnid.uID = 1;
115 tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
116 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
117 tnid.hIcon = CreateTrayIcon(szLCID);
118
119 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
120
121 Shell_NotifyIcon(NIM_ADD, &tnid);
122 }
123
124 static VOID
125 DelTrayIcon(HWND hwnd)
126 {
127 NOTIFYICONDATA tnid;
128
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 tnid.cbSize = sizeof(NOTIFYICONDATA);
142 tnid.hWnd = hwnd;
143 tnid.uID = 1;
144 tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
145 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
146 tnid.hIcon = CreateTrayIcon(szLCID);
147
148 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
149
150 Shell_NotifyIcon(NIM_MODIFY, &tnid);
151 }
152
153 static BOOL
154 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID)
155 {
156 DWORD dwBufLen;
157 DWORD dwRes;
158 HKEY hKey;
159 TCHAR szTempLCID[CCH_LAYOUT_ID + 1];
160
161 // Get the Layout ID
162 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
163 {
164 dwBufLen = sizeof(szTempLCID);
165 dwRes = RegQueryValueEx(hKey, szLayoutNum, NULL, NULL, (LPBYTE)szTempLCID, &dwBufLen);
166
167 if (dwRes != ERROR_SUCCESS)
168 {
169 RegCloseKey(hKey);
170 return FALSE;
171 }
172
173 RegCloseKey(hKey);
174 }
175
176 // Look for a substitude of this layout
177 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Substitutes"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
178 {
179 dwBufLen = sizeof(szTempLCID);
180
181 if (RegQueryValueEx(hKey, szTempLCID, NULL, NULL, (LPBYTE)szLCID, &dwBufLen) != ERROR_SUCCESS)
182 {
183 // No substitute found, then use the old LCID
184 lstrcpy(szLCID, szTempLCID);
185 }
186
187 RegCloseKey(hKey);
188 }
189 else
190 {
191 // Substitutes key couldn't be opened, so use the old LCID
192 lstrcpy(szLCID, szTempLCID);
193 }
194
195 return TRUE;
196 }
197
198 VOID
199 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID)
200 {
201 /*
202 FIXME!!! This way of getting layout ID incorrect!
203 This will not work correctly for 0001040a, 00010410, etc
204 */
205 wsprintf(szLayoutID, _T("%08x"), LOWORD(hKl));
206 }
207
208 static BOOL
209 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName)
210 {
211 HKEY hKey;
212 DWORD dwBufLen;
213 TCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
214 TCHAR szLCID[CCH_LAYOUT_ID + 1];
215 HANDLE hLib;
216 UINT i, j, k;
217
218 if(!GetLayoutID(szLayoutNum, szLCID))
219 return FALSE;
220
221 wsprintf(szBuf, _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID);
222
223 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
224 {
225 dwBufLen = sizeof(szBuf);
226
227 if (RegQueryValueEx(hKey, _T("Layout Display Name"), NULL, NULL, (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
228 {
229 if (szDispName[0] == '@')
230 {
231 for (i = 0; i < _tcslen(szDispName); i++)
232 {
233 if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
234 {
235 for (j = i + 2, k = 0; j < _tcslen(szDispName)+1; j++, k++)
236 {
237 szIndex[k] = szDispName[j];
238 }
239 szDispName[i - 1] = '\0';
240 break;
241 }
242 else szDispName[i] = szDispName[i + 1];
243 }
244
245 if (ExpandEnvironmentStrings(szDispName, szPath, MAX_PATH))
246 {
247 hLib = LoadLibrary(szPath);
248 if (hLib)
249 {
250 if (LoadString(hLib, _ttoi(szIndex), szPath, sizeof(szPath) / sizeof(TCHAR)) != 0)
251 {
252 _tcscpy(szName, szPath);
253 RegCloseKey(hKey);
254 FreeLibrary(hLib);
255 return TRUE;
256 }
257 FreeLibrary(hLib);
258 }
259 }
260 }
261 }
262
263 dwBufLen = sizeof(szBuf);
264
265 if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL, (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
266 {
267 RegCloseKey(hKey);
268 return TRUE;
269 }
270 }
271
272 return FALSE;
273 }
274
275 BOOL CALLBACK
276 EnumWindowsProc(HWND hwnd, LPARAM lParam)
277 {
278 PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, lParam);
279 return TRUE;
280 }
281
282 static VOID
283 ActivateLayout(HWND hwnd, ULONG uLayoutNum)
284 {
285 HKL hKl;
286 TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
287 TCHAR szLCID[CCH_LAYOUT_ID + 1];
288 TCHAR szLangName[MAX_PATH];
289
290 _ultot(uLayoutNum, szLayoutNum, 10);
291 GetLayoutID(szLayoutNum, szLCID);
292
293 // Switch to the new keyboard layout
294 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, sizeof(szLangName) / sizeof(TCHAR));
295 UpdateTrayIcon(hwnd, szLCID, szLangName);
296 hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE);
297
298 EnumWindows(EnumWindowsProc, (LPARAM) hKl);
299
300 ulCurrentLayoutNum = uLayoutNum;
301 }
302
303 static HMENU
304 BuildLeftPopupMenu()
305 {
306 HMENU hMenu;
307 HKEY hKey;
308 DWORD dwIndex, dwSize;
309 TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
310 TCHAR szName[MAX_PATH];
311
312 hMenu = CreatePopupMenu();
313
314 // Add the keyboard layouts to the popup menu
315 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
316 {
317 for(dwIndex = 0; ; dwIndex++)
318 {
319 dwSize = sizeof(szLayoutNum);
320 if(RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
321 break;
322
323 if(!GetLayoutName(szLayoutNum, szName))
324 break;
325
326 AppendMenu(hMenu, MF_STRING, _ttoi(szLayoutNum), szName);
327 }
328
329 (void)CheckMenuItem(hMenu, ulCurrentLayoutNum, MF_CHECKED);
330
331 RegCloseKey(hKey);
332 }
333
334 return hMenu;
335 }
336
337 BOOL
338 SetHooks()
339 {
340 hDllLib = LoadLibrary(_T("kbsdll.dll"));
341 if (!hDllLib) return FALSE;
342
343 KbSwitchSetHooks = (PROC) GetProcAddress(hDllLib, MAKEINTRESOURCEA(1));
344 KbSwitchDeleteHooks = (PROC) GetProcAddress(hDllLib, MAKEINTRESOURCEA(2));
345
346 if ((KbSwitchSetHooks == NULL)||(KbSwitchDeleteHooks == NULL))
347 return FALSE;
348
349 return KbSwitchSetHooks();
350 }
351
352 VOID
353 DeleteHooks()
354 {
355 if (KbSwitchDeleteHooks) KbSwitchDeleteHooks();
356 if (hDllLib) FreeLibrary(hDllLib);
357 }
358
359 ULONG
360 GetNextLayout()
361 {
362 TCHAR szLayoutNum[3 + 1], szLayoutID[CCH_LAYOUT_ID + 1];
363 ULONG Ret = ulCurrentLayoutNum;
364
365 _ultot(ulCurrentLayoutNum, szLayoutNum, 10);
366 if (!GetLayoutID(szLayoutNum, szLayoutID))
367 {
368 return -1;
369 }
370
371 _ultot(Ret + 1, szLayoutNum, 10);
372
373 if (GetLayoutID(szLayoutNum, szLayoutID))
374 {
375 return (Ret + 1);
376 }
377 else
378 {
379 _ultot(Ret - 1, szLayoutNum, 10);
380 if (GetLayoutID(szLayoutNum, szLayoutID))
381 return (Ret - 1);
382 else
383 return -1;
384 }
385
386 return -1;
387 }
388
389 static VOID
390 SettingsChanging(HWND hwnd)
391 {
392 UINT cxSmIconCur = 0, cySmIconCur = 0;
393
394 cxSmIconCur = GetSystemMetrics(SM_CXSMICON);
395 cySmIconCur = GetSystemMetrics(SM_CYSMICON);
396
397 if ((cxSmIcon != cxSmIconCur) || (cySmIcon != cySmIconCur))
398 {
399 cxSmIcon = cxSmIconCur;
400 cySmIcon = cySmIconCur;
401 }
402
403 ActivateLayout(hwnd, ulCurrentLayoutNum);
404 }
405
406 LRESULT CALLBACK
407 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
408 {
409 static HMENU hRightPopupMenu;
410 static TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
411
412 switch (Message)
413 {
414 case WM_CREATE:
415 {
416 cxSmIcon = GetSystemMetrics(SM_CXSMICON);
417 cySmIcon = GetSystemMetrics(SM_CYSMICON);
418
419 SetHooks();
420 AddTrayIcon(hwnd);
421 hRightPopupMenu = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)), 0);
422
423 ActivateLayout(hwnd, ulCurrentLayoutNum);
424
425 return 0;
426 }
427
428 case WM_LANG_CHANGED:
429 {
430 GetLayoutIDByHkl((HKL)lParam, szLCID);
431 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, sizeof(szLangName) / sizeof(TCHAR));
432 UpdateTrayIcon(hwnd, szLCID, szLangName);
433
434 return 0;
435 }
436
437 case WM_LOAD_LAYOUT:
438 {
439 ActivateLayout(hwnd, GetNextLayout());
440
441 return 0;
442 }
443
444 case WM_WINDOW_ACTIVATE:
445 {
446 GetLayoutIDByHkl(GetKeyboardLayout(GetWindowThreadProcessId((HWND)wParam, 0)), szLCID);
447 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, sizeof(szLangName) / sizeof(TCHAR));
448 UpdateTrayIcon(hwnd, szLCID, szLangName);
449
450 return 0;
451 }
452
453 case WM_NOTIFYICONMSG:
454 switch (lParam)
455 {
456 case WM_RBUTTONDOWN:
457 case WM_LBUTTONDOWN:
458 {
459 POINT pt;
460
461 GetCursorPos(&pt);
462 SetForegroundWindow(hwnd);
463
464 if (lParam == WM_LBUTTONDOWN)
465 {
466 HMENU hLeftPopupMenu;
467
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
506 default:
507 ActivateLayout(hwnd, LOWORD(wParam));
508 return 0;
509 }
510 break;
511
512 case WM_SETTINGCHANGE:
513 {
514 SettingsChanging(hwnd);
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 }
521 break;
522
523 case WM_DESTROY:
524 {
525 DeleteHooks();
526 DestroyMenu(hRightPopupMenu);
527 DelTrayIcon(hwnd);
528 PostQuitMessage(0);
529
530 return 0;
531 }
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
544 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
545 if ((!hMutex) || (GetLastError() == ERROR_ALREADY_EXISTS))
546 return 1;
547
548 hInst = hInstance;
549 hProcessHeap = GetProcessHeap();
550
551 WndClass.style = 0;
552 WndClass.lpfnWndProc = (WNDPROC)WndProc;
553 WndClass.cbClsExtra = 0;
554 WndClass.cbWndExtra = 0;
555 WndClass.hInstance = hInstance;
556 WndClass.hIcon = NULL;
557 WndClass.hCursor = NULL;
558 WndClass.hbrBackground = NULL;
559 WndClass.lpszMenuName = NULL;
560 WndClass.lpszClassName = szKbSwitcherName;
561
562 if (!RegisterClass(&WndClass))
563 return 1;
564
565 CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
566
567 while(GetMessage(&msg,NULL,0,0))
568 {
569 TranslateMessage(&msg);
570 DispatchMessage(&msg);
571 }
572
573 if (hMutex) CloseHandle(hMutex);
574
575 return 0;
576 }