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