- Add language name tooltip to kbswitch systray icon
[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 TCHAR szKbSwitcherName[] = _T("kbswitcher");
14
15
16 static BOOL
17 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID);
18
19 static BOOL
20 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName);
21
22 HINSTANCE hInst;
23 HANDLE hProcessHeap;
24
25 static HICON
26 CreateTrayIcon(LPTSTR szLCID)
27 {
28 LANGID lId;
29 TCHAR szBuf[3];
30 HDC hdc, hdcsrc;
31 HBITMAP hBitmap, hBmpNew, hBmpOld;
32 RECT rect;
33 DWORD bkColor, bkText;
34 HFONT hFont = NULL;
35 ICONINFO IconInfo;
36 HICON hIcon = NULL;
37
38 lId = (LANGID)_tcstoul(szLCID, NULL, 16);
39 if (GetLocaleInfo(lId,
40 LOCALE_SISO639LANGNAME,
41 szBuf,
42 sizeof(szBuf) / sizeof(TCHAR)) == 0)
43 {
44 lstrcpy(szBuf, _T("??\0"));
45 }
46
47 hdcsrc = GetDC(NULL);
48 hdc = CreateCompatibleDC(hdcsrc);
49 hBitmap = CreateCompatibleBitmap(hdcsrc, 16, 16);
50 ReleaseDC(NULL, hdcsrc);
51
52 if (hdc && hBitmap)
53 {
54 hBmpNew = CreateBitmap(16, 16, 1, 1, NULL);
55 if (hBmpNew)
56 {
57 hBmpOld = SelectObject(hdc, hBitmap);
58 rect.right = 16;
59 rect.left = 0;
60 rect.bottom = 16;
61 rect.top = 0;
62
63 bkColor = SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
64 bkText = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
65
66 ExtTextOut(hdc, rect.left, rect.top, ETO_OPAQUE, &rect, _T(""), 0, NULL);
67
68 hFont = CreateFont(-11, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
69 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
70 DEFAULT_QUALITY, FF_DONTCARE, _T("Tahoma"));
71
72 SelectObject(hdc, hFont);
73 DrawText(hdc, _tcsupr(szBuf), 2, &rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
74 SelectObject(hdc, hBmpNew);
75 PatBlt(hdc, 0, 0, 16, 16, BLACKNESS);
76 SelectObject(hdc, hBmpOld);
77
78 IconInfo.hbmColor = hBitmap;
79 IconInfo.hbmMask = hBmpNew;
80 IconInfo.fIcon = TRUE;
81
82 hIcon = CreateIconIndirect(&IconInfo);
83
84 DeleteObject(hBmpNew);
85 DeleteObject(hBmpOld);
86 DeleteObject(hFont);
87 }
88 }
89
90 DeleteDC(hdc);
91 DeleteObject(hBitmap);
92
93 return hIcon;
94 }
95
96 static VOID
97 AddTrayIcon(HWND hwnd)
98 {
99 NOTIFYICONDATA tnid;
100 TCHAR szLCID[CCH_LAYOUT_ID + 1];
101 TCHAR szName[MAX_PATH];
102
103 GetLayoutID(_T("1"), szLCID);
104 GetLayoutName(_T("1"), szName);
105
106 tnid.cbSize = sizeof(NOTIFYICONDATA);
107 tnid.hWnd = hwnd;
108 tnid.uID = 1;
109 tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
110 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
111 tnid.hIcon = CreateTrayIcon(szLCID);
112
113 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
114
115 Shell_NotifyIcon(NIM_ADD, &tnid);
116 }
117
118 static VOID
119 DelTrayIcon(HWND hwnd)
120 {
121 NOTIFYICONDATA tnid;
122
123 tnid.cbSize = sizeof(NOTIFYICONDATA);
124 tnid.hWnd = hwnd;
125 tnid.uID = 1;
126
127 Shell_NotifyIcon(NIM_DELETE, &tnid);
128 }
129
130 static VOID
131 UpdateTrayIcon(HWND hwnd, LPTSTR szLCID, LPTSTR szName)
132 {
133 NOTIFYICONDATA tnid;
134
135 tnid.cbSize = sizeof(NOTIFYICONDATA);
136 tnid.hWnd = hwnd;
137 tnid.uID = 1;
138 tnid.uFlags = NIF_ICON | NIF_MESSAGE |NIF_TIP;
139 tnid.uCallbackMessage = WM_NOTIFYICONMSG;
140 tnid.hIcon = CreateTrayIcon(szLCID);
141
142 lstrcpyn(tnid.szTip, szName, sizeof(tnid.szTip));
143
144 Shell_NotifyIcon(NIM_MODIFY, &tnid);
145 }
146
147 static BOOL
148 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID)
149 {
150 DWORD dwBufLen;
151 DWORD dwRes;
152 HKEY hKey;
153 TCHAR szTempLCID[CCH_LAYOUT_ID + 1];
154
155 // Get the Layout ID
156 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
157 {
158 dwBufLen = sizeof(szTempLCID);
159 dwRes = RegQueryValueEx(hKey, szLayoutNum, NULL, NULL, (LPBYTE)szTempLCID, &dwBufLen);
160
161 if (dwRes != ERROR_SUCCESS)
162 {
163 RegCloseKey(hKey);
164 return FALSE;
165 }
166
167 RegCloseKey(hKey);
168 }
169
170 // Look for a substitude of this layout
171 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Substitutes"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
172 {
173 dwBufLen = sizeof(szTempLCID);
174
175 if (RegQueryValueEx(hKey, szTempLCID, NULL, NULL, (LPBYTE)szLCID, &dwBufLen) != ERROR_SUCCESS)
176 {
177 // No substitute found, then use the old LCID
178 lstrcpy(szLCID, szTempLCID);
179 }
180
181 RegCloseKey(hKey);
182 }
183 else
184 {
185 // Substitutes key couldn't be opened, so use the old LCID
186 lstrcpy(szLCID, szTempLCID);
187 }
188
189 return TRUE;
190 }
191
192 static BOOL
193 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName)
194 {
195 HKEY hKey;
196 DWORD dwBufLen;
197 TCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
198 TCHAR szLCID[CCH_LAYOUT_ID + 1];
199 HANDLE hLib;
200 int i, j, k;
201
202 if(!GetLayoutID(szLayoutNum, szLCID))
203 return FALSE;
204
205 wsprintf(szBuf, _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID);
206
207 if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
208 {
209 dwBufLen = sizeof(szBuf);
210
211 if (RegQueryValueEx(hKey, _T("Layout Display Name"), NULL, NULL, (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
212 {
213 if (szDispName[0] == '@')
214 {
215 for (i = 0; i < _tcslen(szDispName); i++)
216 {
217 if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
218 {
219 for (j = i + 2, k = 0; j < _tcslen(szDispName)+1; j++, k++)
220 {
221 szIndex[k] = szDispName[j];
222 }
223 szDispName[i - 1] = '\0';
224 break;
225 }
226 else szDispName[i] = szDispName[i + 1];
227 }
228
229 if (ExpandEnvironmentStrings(szDispName, szPath, MAX_PATH))
230 {
231 hLib = LoadLibrary(szPath);
232 if (hLib)
233 {
234 if (LoadString(hLib, _ttoi(szIndex), szPath, sizeof(szPath) / sizeof(TCHAR)) != 0)
235 {
236 _tcscpy(szName, szPath);
237 RegCloseKey(hKey);
238 return TRUE;
239 }
240 FreeLibrary(hLib);
241 }
242 }
243 }
244 }
245
246 dwBufLen = sizeof(szBuf);
247
248 if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL, (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
249 {
250 RegCloseKey(hKey);
251 return TRUE;
252 }
253 }
254
255 return FALSE;
256 }
257
258 BOOL CALLBACK
259 EnumWindowsProc(HWND hwnd, LPARAM lParam)
260 {
261 SendMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, lParam);
262 return TRUE;
263 }
264
265 static VOID
266 ActivateLayout(HWND hwnd, ULONG uLayoutNum)
267 {
268 HKL hKl;
269 TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
270 TCHAR szLCID[CCH_LAYOUT_ID + 1];
271 TCHAR szName[MAX_PATH];
272
273 _ultot(uLayoutNum, szLayoutNum, 10);
274 GetLayoutID(szLayoutNum, szLCID);
275 GetLayoutName(szLayoutNum, szName);
276 CreateTrayIcon(szLCID);
277
278 // Switch to the new keyboard layout
279 UpdateTrayIcon(hwnd, szLCID, szName);
280 hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE);
281 SystemParametersInfo(SPI_SETDEFAULTINPUTLANG, 0, &hKl, SPIF_SENDWININICHANGE);
282 EnumWindows(EnumWindowsProc, (LPARAM) hKl);
283 }
284
285 static HMENU
286 BuildLeftPopupMenu()
287 {
288 HMENU hMenu;
289 HKEY hKey;
290 DWORD dwIndex, dwSize;
291 TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
292 TCHAR szName[MAX_PATH];
293
294 hMenu = CreatePopupMenu();
295
296 // Add the keyboard layouts to the popup menu
297 if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
298 {
299 for(dwIndex = 0; ; dwIndex++)
300 {
301 dwSize = sizeof(szLayoutNum);
302 if(RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
303 break;
304
305 if(!GetLayoutName(szLayoutNum, szName))
306 break;
307
308 AppendMenu(hMenu, MF_STRING, _ttoi(szLayoutNum), szName);
309 }
310
311 RegCloseKey(hKey);
312 }
313
314 return hMenu;
315 }
316
317 static HMENU
318 BuildRightPopupMenu()
319 {
320 HMENU hMenu;
321 HMENU hMenuTemplate;
322 DWORD dwIndex;
323 LPTSTR pszMenuItem;
324 MENUITEMINFO mii;
325
326 // Add the keyboard layouts to the popup menu
327 hMenu = BuildLeftPopupMenu();
328
329 // Add the menu items from the popup menu template
330 hMenuTemplate = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)), 0);
331 dwIndex = 0;
332
333 mii.cbSize = sizeof(mii);
334 mii.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID;
335 mii.dwTypeData = NULL;
336
337 while(GetMenuItemInfo(hMenuTemplate, dwIndex, TRUE, &mii))
338 {
339 if(mii.cch > 0)
340 {
341 mii.cch++;
342 pszMenuItem = (LPTSTR)HeapAlloc(hProcessHeap, 0, mii.cch * sizeof(TCHAR));
343
344 mii.dwTypeData = pszMenuItem;
345 GetMenuItemInfo(hMenuTemplate, dwIndex, TRUE, &mii);
346
347 AppendMenu(hMenu, mii.fType, mii.wID, mii.dwTypeData);
348
349 HeapFree(hProcessHeap, 0, pszMenuItem);
350 mii.dwTypeData = NULL;
351 }
352 else
353 {
354 AppendMenu(hMenu, mii.fType, 0, NULL);
355 }
356
357 dwIndex++;
358 }
359
360 return hMenu;
361 }
362
363 LRESULT CALLBACK
364 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
365 {
366 static HMENU hLeftPopupMenu, hRightPopupMenu;
367
368 switch (Message)
369 {
370 case WM_CREATE:
371 AddTrayIcon(hwnd);
372 hLeftPopupMenu = BuildLeftPopupMenu(hwnd);
373 hRightPopupMenu = BuildRightPopupMenu(hwnd);
374 break;
375
376 case WM_NOTIFYICONMSG:
377 switch (lParam)
378 {
379 case WM_RBUTTONDOWN:
380 case WM_LBUTTONDOWN:
381 {
382 POINT pt;
383
384 GetCursorPos(&pt);
385 SetForegroundWindow(hwnd);
386 if (lParam == WM_LBUTTONDOWN)
387 TrackPopupMenu(hLeftPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
388 else
389 TrackPopupMenu(hRightPopupMenu, 0, pt.x, pt.y, 0, hwnd, NULL);
390 PostMessage(hwnd, WM_NULL, 0, 0);
391 break;
392 }
393 }
394 break;
395
396 case WM_COMMAND:
397 switch (LOWORD(wParam))
398 {
399 case ID_EXIT:
400 SendMessage(hwnd, WM_CLOSE, 0, 0);
401 break;
402
403 case ID_PREFERENCES:
404 {
405 SHELLEXECUTEINFO shInputDll = {0};
406
407 shInputDll.cbSize = sizeof(shInputDll);
408 shInputDll.hwnd = hwnd;
409 shInputDll.lpVerb = _T("open");
410 shInputDll.lpFile = _T("rundll32.exe");
411 shInputDll.lpParameters = _T("shell32.dll,Control_RunDLL input.dll");
412
413 if (!ShellExecuteEx(&shInputDll))
414 MessageBox(hwnd, _T("Can't start input.dll"), NULL, MB_OK | MB_ICONERROR);
415
416 break;
417 }
418
419 default:
420 ActivateLayout(hwnd, LOWORD(wParam));
421 break;
422 }
423 break;
424
425 case WM_SETTINGCHANGE:
426 {
427 if (wParam == SPI_SETDEFAULTINPUTLANG)
428 {
429 //FIXME: Should detect default language changes by CPL applet or by other tools and update UI
430 }
431 }
432 break;
433
434 case WM_DESTROY:
435 DestroyMenu(hLeftPopupMenu);
436 DestroyMenu(hRightPopupMenu);
437 DelTrayIcon(hwnd);
438 PostQuitMessage(0);
439 break;
440 }
441
442 return DefWindowProc(hwnd, Message, wParam, lParam);
443 }
444
445 INT WINAPI
446 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
447 {
448 WNDCLASS WndClass = {0};
449 MSG msg;
450 HANDLE hMutex;
451
452 hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
453 if ((!hMutex) || (GetLastError() == ERROR_ALREADY_EXISTS))
454 return 1;
455
456 hInst = hInstance;
457 hProcessHeap = GetProcessHeap();
458
459 WndClass.style = 0;
460 WndClass.lpfnWndProc = (WNDPROC)WndProc;
461 WndClass.cbClsExtra = 0;
462 WndClass.cbWndExtra = 0;
463 WndClass.hInstance = hInstance;
464 WndClass.hIcon = NULL;
465 WndClass.hCursor = NULL;
466 WndClass.hbrBackground = NULL;
467 WndClass.lpszMenuName = NULL;
468 WndClass.lpszClassName = szKbSwitcherName;
469
470 if (!RegisterClass(&WndClass))
471 return 1;
472
473 CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
474
475 while(GetMessage(&msg,NULL,0,0))
476 {
477 TranslateMessage(&msg);
478 DispatchMessage(&msg);
479 }
480
481 if (hMutex) CloseHandle(hMutex);
482
483 return 0;
484 }