[NTDLL]
[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
28 static HICON
29 CreateTrayIcon(LPTSTR szLCID)
30 {
31 LANGID lId;
32 TCHAR szBuf[3];
33 HDC hdc, hdcsrc;
34 HBITMAP hBitmap, hBmpNew, hBmpOld;
35 RECT rect;
36 DWORD bkColor, bkText;
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 sizeof(szBuf) / sizeof(TCHAR)) == 0)
46 {
47 lstrcpy(szBuf, _T("??\0"));
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 bkColor = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
67 bkText = 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);
108 GetLayoutName(_T("1"), szName);
109
110 tnid.cbSize = sizeof(NOTIFYICONDATA);
111 tnid.hWnd = hwnd;
112 tnid.uID = 1;
113 tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
114 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
115 tnid.hIcon = CreateTrayIcon(szLCID);
116
117 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
118
119 Shell_NotifyIcon(NIM_ADD, &tnid);
120 }
121
122 static VOID
123 DelTrayIcon(HWND hwnd)
124 {
125 NOTIFYICONDATA tnid;
126
127 tnid.cbSize = sizeof(NOTIFYICONDATA);
128 tnid.hWnd = hwnd;
129 tnid.uID = 1;
130
131 Shell_NotifyIcon(NIM_DELETE, &tnid);
132 }
133
134 static VOID
135 UpdateTrayIcon(HWND hwnd, LPTSTR szLCID, LPTSTR szName)
136 {
137 NOTIFYICONDATA tnid;
138
139 tnid.cbSize = sizeof(NOTIFYICONDATA);
140 tnid.hWnd = hwnd;
141 tnid.uID = 1;
142 tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
143 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
144 tnid.hIcon = CreateTrayIcon(szLCID);
145
146 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
147
148 Shell_NotifyIcon(NIM_MODIFY, &tnid);
149 }
150
151 static BOOL
152 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID)
153 {
154 DWORD dwBufLen;
155 DWORD dwRes;
156 HKEY hKey;
157 TCHAR szTempLCID[CCH_LAYOUT_ID + 1];
158
159 // Get the Layout ID
160 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
161 {
162 dwBufLen = sizeof(szTempLCID);
163 dwRes = RegQueryValueEx(hKey, szLayoutNum, NULL, NULL, (LPBYTE)szTempLCID, &dwBufLen);
164
165 if (dwRes != ERROR_SUCCESS)
166 {
167 RegCloseKey(hKey);
168 return FALSE;
169 }
170
171 RegCloseKey(hKey);
172 }
173
174 // Look for a substitude of this layout
175 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Substitutes"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
176 {
177 dwBufLen = sizeof(szTempLCID);
178
179 if (RegQueryValueEx(hKey, szTempLCID, NULL, NULL, (LPBYTE)szLCID, &dwBufLen) != ERROR_SUCCESS)
180 {
181 // No substitute found, then use the old LCID
182 lstrcpy(szLCID, szTempLCID);
183 }
184
185 RegCloseKey(hKey);
186 }
187 else
188 {
189 // Substitutes key couldn't be opened, so use the old LCID
190 lstrcpy(szLCID, szTempLCID);
191 }
192
193 return TRUE;
194 }
195
196 VOID
197 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID)
198 {
199 /*
200 FIXME!!! This way of getting layout ID incorrect!
201 This will not work correctly for 0001040a, 00010410, etc
202 */
203 wsprintf(szLayoutID, _T("%08x"), LOWORD(hKl));
204 }
205
206 static BOOL
207 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName)
208 {
209 HKEY hKey;
210 DWORD dwBufLen;
211 TCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
212 TCHAR szLCID[CCH_LAYOUT_ID + 1];
213 HANDLE hLib;
214 UINT i, j, k;
215
216 if(!GetLayoutID(szLayoutNum, szLCID))
217 return FALSE;
218
219 wsprintf(szBuf, _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID);
220
221 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
222 {
223 dwBufLen = sizeof(szBuf);
224
225 if (RegQueryValueEx(hKey, _T("Layout Display Name"), NULL, NULL, (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
226 {
227 if (szDispName[0] == '@')
228 {
229 for (i = 0; i < _tcslen(szDispName); i++)
230 {
231 if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
232 {
233 for (j = i + 2, k = 0; j < _tcslen(szDispName)+1; j++, k++)
234 {
235 szIndex[k] = szDispName[j];
236 }
237 szDispName[i - 1] = '\0';
238 break;
239 }
240 else szDispName[i] = szDispName[i + 1];
241 }
242
243 if (ExpandEnvironmentStrings(szDispName, szPath, MAX_PATH))
244 {
245 hLib = LoadLibrary(szPath);
246 if (hLib)
247 {
248 if (LoadString(hLib, _ttoi(szIndex), szPath, sizeof(szPath) / sizeof(TCHAR)) != 0)
249 {
250 _tcscpy(szName, szPath);
251 RegCloseKey(hKey);
252 FreeLibrary(hLib);
253 return TRUE;
254 }
255 FreeLibrary(hLib);
256 }
257 }
258 }
259 }
260
261 dwBufLen = sizeof(szBuf);
262
263 if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL, (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
264 {
265 RegCloseKey(hKey);
266 return TRUE;
267 }
268
269 RegCloseKey(hKey);
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 LRESULT CALLBACK
390 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
391 {
392 static HMENU hRightPopupMenu;
393 static TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
394
395 switch (Message)
396 {
397 case WM_CREATE:
398 {
399 SetHooks();
400 AddTrayIcon(hwnd);
401 hRightPopupMenu = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)), 0);
402
403 ActivateLayout(hwnd, ulCurrentLayoutNum);
404
405 return 0;
406 }
407
408 case WM_LANG_CHANGED:
409 {
410 GetLayoutIDByHkl((HKL)lParam, szLCID);
411 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, sizeof(szLangName) / sizeof(TCHAR));
412 UpdateTrayIcon(hwnd, szLCID, szLangName);
413
414 return 0;
415 }
416
417 case WM_LOAD_LAYOUT:
418 {
419 ActivateLayout(hwnd, GetNextLayout());
420
421 return 0;
422 }
423
424 case WM_WINDOW_ACTIVATE:
425 {
426 GetLayoutIDByHkl(GetKeyboardLayout(GetWindowThreadProcessId((HWND)wParam, 0)), szLCID);
427 GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, sizeof(szLangName) / sizeof(TCHAR));
428 UpdateTrayIcon(hwnd, szLCID, szLangName);
429
430 return 0;
431 }
432
433 case WM_NOTIFYICONMSG:
434 switch (lParam)
435 {
436 case WM_RBUTTONDOWN:
437 case WM_LBUTTONDOWN:
438 {
439 POINT pt;
440
441 GetCursorPos(&pt);
442 SetForegroundWindow(hwnd);
443
444 if (lParam == WM_LBUTTONDOWN)
445 {
446 HMENU hLeftPopupMenu;
447
448 /* Rebuild the left popup menu on every click to take care of keyboard layout changes */
449 hLeftPopupMenu = BuildLeftPopupMenu();
450 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
451 DestroyMenu(hLeftPopupMenu);
452 }
453 else
454 {
455 TrackPopupMenu(hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
456 }
457
458 PostMessage(hwnd, WM_NULL, 0, 0);
459
460 return 0;
461 }
462 }
463 break;
464
465 case WM_COMMAND:
466 switch (LOWORD(wParam))
467 {
468 case ID_EXIT:
469 SendMessage(hwnd, WM_CLOSE, 0, 0);
470 return 0;
471
472 case ID_PREFERENCES:
473 {
474 SHELLEXECUTEINFO shInputDll = {0};
475
476 shInputDll.cbSize = sizeof(shInputDll);
477 shInputDll.hwnd = hwnd;
478 shInputDll.lpVerb = _T("open");
479 shInputDll.lpFile = _T("rundll32.exe");
480 shInputDll.lpParameters = _T("shell32.dll,Control_RunDLL input.dll");
481
482 if (!ShellExecuteEx(&shInputDll))
483 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_OK | MB_ICONERROR);
484 }
485
486 default:
487 ActivateLayout(hwnd, LOWORD(wParam));
488 return 0;
489 }
490 break;
491
492 case WM_SETTINGCHANGE:
493 {
494 if (wParam == SPI_SETDEFAULTINPUTLANG)
495 {
496 //FIXME: Should detect default language changes by CPL applet or by other tools and update UI
497 }
498 }
499 break;
500
501 case WM_DESTROY:
502 {
503 DeleteHooks();
504 DestroyMenu(hRightPopupMenu);
505 DelTrayIcon(hwnd);
506 PostQuitMessage(0);
507
508 return 0;
509 }
510 }
511
512 return DefWindowProc(hwnd, Message, wParam, lParam);
513 }
514
515 INT WINAPI
516 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
517 {
518 WNDCLASS WndClass = {0};
519 MSG msg;
520 HANDLE hMutex;
521
522 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
523 if ((!hMutex) || (GetLastError() == ERROR_ALREADY_EXISTS))
524 return 1;
525
526 hInst = hInstance;
527 hProcessHeap = GetProcessHeap();
528
529 WndClass.style = 0;
530 WndClass.lpfnWndProc = (WNDPROC)WndProc;
531 WndClass.cbClsExtra = 0;
532 WndClass.cbWndExtra = 0;
533 WndClass.hInstance = hInstance;
534 WndClass.hIcon = NULL;
535 WndClass.hCursor = NULL;
536 WndClass.hbrBackground = NULL;
537 WndClass.lpszMenuName = NULL;
538 WndClass.lpszClassName = szKbSwitcherName;
539
540 if (!RegisterClass(&WndClass))
541 return 1;
542
543 CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
544
545 while(GetMessage(&msg,NULL,0,0))
546 {
547 TranslateMessage(&msg);
548 DispatchMessage(&msg);
549 }
550
551 if (hMutex) CloseHandle(hMutex);
552
553 return 0;
554 }