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