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 PKBSWITCHSETHOOKS KbSwitchSetHooks    = NULL;
14 PKBSWITCHDELETEHOOKS KbSwitchDeleteHooks = NULL;
15 UINT ShellHookMessage = 0;
16 
17 
18 static BOOL
19 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID, SIZE_T LCIDLength);
20 
21 static BOOL
22 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName, SIZE_T NameLength);
23 
24 HINSTANCE hInst;
25 HANDLE    hProcessHeap;
26 HMODULE   hDllLib;
27 ULONG     ulCurrentLayoutNum = 1;
28 
29 static HICON
30 CreateTrayIcon(LPTSTR szLCID)
31 {
32     LANGID lId;
33     TCHAR szBuf[3];
34     HDC hdc, hdcsrc;
35     HBITMAP hBitmap, hBmpNew, hBmpOld;
36     RECT rect;
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                       ARRAYSIZE(szBuf)) == 0)
46     {
47         StringCchCopy(szBuf, ARRAYSIZE(szBuf), _T("??"));
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             SetBkColor(hdc, GetSysColor(COLOR_HIGHLIGHT));
67             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, ARRAYSIZE(szLCID));
108     GetLayoutName(_T("1"), szName, ARRAYSIZE(szName));
109 
110     memset(&tnid, 0, sizeof(tnid));
111     tnid.cbSize = sizeof(NOTIFYICONDATA);
112     tnid.hWnd = hwnd;
113     tnid.uID = 1;
114     tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
115     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
116     tnid.hIcon = CreateTrayIcon(szLCID);
117 
118     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
119 
120     Shell_NotifyIcon(NIM_ADD, &tnid);
121 }
122 
123 static VOID
124 DelTrayIcon(HWND hwnd)
125 {
126     NOTIFYICONDATA tnid;
127 
128     memset(&tnid, 0, sizeof(tnid));
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     memset(&tnid, 0, sizeof(tnid));
142     tnid.cbSize = sizeof(NOTIFYICONDATA);
143     tnid.hWnd = hwnd;
144     tnid.uID = 1;
145     tnid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
146     tnid.uCallbackMessage = WM_NOTIFYICONMSG;
147     tnid.hIcon = CreateTrayIcon(szLCID);
148 
149     StringCchCopy(tnid.szTip, ARRAYSIZE(tnid.szTip), szName);
150 
151     Shell_NotifyIcon(NIM_MODIFY, &tnid);
152 }
153 
154 static BOOL
155 GetLayoutID(LPTSTR szLayoutNum, LPTSTR szLCID, SIZE_T LCIDLength)
156 {
157     DWORD dwBufLen;
158     DWORD dwRes;
159     HKEY hKey;
160     TCHAR szTempLCID[CCH_LAYOUT_ID + 1];
161 
162     // Get the Layout ID
163     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
164     {
165         dwBufLen = sizeof(szTempLCID);
166         dwRes = RegQueryValueEx(hKey, szLayoutNum, NULL, NULL, (LPBYTE)szTempLCID, &dwBufLen);
167 
168         if (dwRes != ERROR_SUCCESS)
169         {
170             RegCloseKey(hKey);
171             return FALSE;
172         }
173 
174         RegCloseKey(hKey);
175     }
176 
177     // Look for a substitute of this layout
178     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Substitutes"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
179     {
180         dwBufLen = sizeof(szTempLCID);
181 
182         if (RegQueryValueEx(hKey, szTempLCID, NULL, NULL, (LPBYTE)szLCID, &dwBufLen) != ERROR_SUCCESS)
183         {
184             // No substitute found, then use the old LCID
185             StringCchCopy(szLCID, LCIDLength, szTempLCID);
186         }
187 
188         RegCloseKey(hKey);
189     }
190     else
191     {
192         // Substitutes key couldn't be opened, so use the old LCID
193         StringCchCopy(szLCID, LCIDLength, szTempLCID);
194     }
195 
196     return TRUE;
197 }
198 
199 VOID
200 GetLayoutIDByHkl(HKL hKl, LPTSTR szLayoutID, SIZE_T LayoutIDLength)
201 {
202     /*
203         FIXME!!! This way of getting layout ID incorrect!
204                  This will not work correctly for 0001040a, 00010410, etc
205     */
206     StringCchPrintf(szLayoutID, LayoutIDLength, _T("%08x"), LOWORD(hKl));
207 }
208 
209 static BOOL
210 GetLayoutName(LPTSTR szLayoutNum, LPTSTR szName, SIZE_T NameLength)
211 {
212     HKEY hKey;
213     DWORD dwBufLen;
214     TCHAR szBuf[MAX_PATH], szDispName[MAX_PATH], szIndex[MAX_PATH], szPath[MAX_PATH];
215     TCHAR szLCID[CCH_LAYOUT_ID + 1];
216     HANDLE hLib;
217     UINT i, j, k;
218 
219     if (!GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID)))
220         return FALSE;
221 
222     StringCchPrintf(szBuf, ARRAYSIZE(szBuf), _T("SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s"), szLCID);
223 
224     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR)szBuf, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
225     {
226         dwBufLen = sizeof(szDispName);
227 
228         if (RegQueryValueEx(hKey, _T("Layout Display Name"), NULL, NULL, (LPBYTE)szDispName, &dwBufLen) == ERROR_SUCCESS)
229         {
230             if (szDispName[0] == '@')
231             {
232                 size_t len = _tcslen(szDispName);
233 
234                 for (i = 0; i < len; i++)
235                 {
236                     if ((szDispName[i] == ',') && (szDispName[i + 1] == '-'))
237                     {
238                         for (j = i + 2, k = 0; j < _tcslen(szDispName)+1; j++, k++)
239                         {
240                             szIndex[k] = szDispName[j];
241                         }
242                         szDispName[i - 1] = '\0';
243                         break;
244                     }
245                     else szDispName[i] = szDispName[i + 1];
246                 }
247 
248                 if (ExpandEnvironmentStrings(szDispName, szPath, ARRAYSIZE(szPath)))
249                 {
250                     hLib = LoadLibrary(szPath);
251                     if (hLib)
252                     {
253                         if (LoadString(hLib, _ttoi(szIndex), szPath, ARRAYSIZE(szPath)) != 0)
254                         {
255                             StringCchCopy(szName, NameLength, szPath);
256                             RegCloseKey(hKey);
257                             FreeLibrary(hLib);
258                             return TRUE;
259                         }
260                         FreeLibrary(hLib);
261                     }
262                 }
263             }
264         }
265 
266         dwBufLen = NameLength * sizeof(TCHAR);
267 
268         if (RegQueryValueEx(hKey, _T("Layout Text"), NULL, NULL, (LPBYTE)szName, &dwBufLen) == ERROR_SUCCESS)
269         {
270             RegCloseKey(hKey);
271             return TRUE;
272         }
273 
274         RegCloseKey(hKey);
275     }
276 
277     return FALSE;
278 }
279 
280 BOOL CALLBACK
281 EnumWindowsProc(HWND hwnd, LPARAM lParam)
282 {
283     PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, lParam);
284     return TRUE;
285 }
286 
287 static VOID
288 ActivateLayout(HWND hwnd, ULONG uLayoutNum)
289 {
290     HKL hKl;
291     TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
292     TCHAR szLCID[CCH_LAYOUT_ID + 1];
293     TCHAR szLangName[MAX_PATH];
294 
295     _ultot(uLayoutNum, szLayoutNum, 10);
296     GetLayoutID(szLayoutNum, szLCID, ARRAYSIZE(szLCID));
297 
298     // Switch to the new keyboard layout
299     GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, ARRAYSIZE(szLangName));
300     UpdateTrayIcon(hwnd, szLCID, szLangName);
301     hKl = LoadKeyboardLayout(szLCID, KLF_ACTIVATE);
302 
303     EnumWindows(EnumWindowsProc, (LPARAM) hKl);
304 
305     ulCurrentLayoutNum = uLayoutNum;
306 }
307 
308 static HMENU
309 BuildLeftPopupMenu(VOID)
310 {
311     HMENU hMenu;
312     HKEY hKey;
313     DWORD dwIndex, dwSize;
314     TCHAR szLayoutNum[CCH_ULONG_DEC + 1];
315     TCHAR szName[MAX_PATH];
316 
317     hMenu = CreatePopupMenu();
318 
319     // Add the keyboard layouts to the popup menu
320     if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Keyboard Layout\\Preload"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
321     {
322         for (dwIndex = 0; ; dwIndex++)
323         {
324             dwSize = sizeof(szLayoutNum);
325             if (RegEnumValue(hKey, dwIndex, szLayoutNum, &dwSize, NULL, NULL, NULL, NULL) != ERROR_SUCCESS)
326                 break;
327 
328             if (!GetLayoutName(szLayoutNum, szName, ARRAYSIZE(szName)))
329                 break;
330 
331             AppendMenu(hMenu, MF_STRING, _ttoi(szLayoutNum), szName);
332         }
333 
334         CheckMenuItem(hMenu, ulCurrentLayoutNum, MF_CHECKED);
335 
336         RegCloseKey(hKey);
337     }
338 
339     return hMenu;
340 }
341 
342 BOOL
343 SetHooks(VOID)
344 {
345     hDllLib = LoadLibrary(_T("kbsdll.dll"));
346     if (!hDllLib)
347     {
348         return FALSE;
349     }
350 
351     KbSwitchSetHooks    = (PKBSWITCHSETHOOKS) GetProcAddress(hDllLib, "KbSwitchSetHooks");
352     KbSwitchDeleteHooks = (PKBSWITCHDELETEHOOKS) GetProcAddress(hDllLib, "KbSwitchDeleteHooks");
353 
354     if (KbSwitchSetHooks == NULL || KbSwitchDeleteHooks == NULL)
355     {
356         return FALSE;
357     }
358 
359     return KbSwitchSetHooks();
360 }
361 
362 VOID
363 DeleteHooks(VOID)
364 {
365     if (KbSwitchDeleteHooks) KbSwitchDeleteHooks();
366     if (hDllLib) FreeLibrary(hDllLib);
367 }
368 
369 ULONG
370 GetNextLayout(VOID)
371 {
372     TCHAR szLayoutNum[3 + 1], szLayoutID[CCH_LAYOUT_ID + 1];
373     ULONG Ret = ulCurrentLayoutNum;
374 
375     _ultot(ulCurrentLayoutNum, szLayoutNum, 10);
376     if (!GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
377     {
378         return -1;
379     }
380 
381     _ultot(Ret + 1, szLayoutNum, 10);
382 
383     if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
384     {
385         return (Ret + 1);
386     }
387     else
388     {
389         _ultot(Ret - 1, szLayoutNum, 10);
390         if (GetLayoutID(szLayoutNum, szLayoutID, ARRAYSIZE(szLayoutID)))
391             return (Ret - 1);
392         else
393             return -1;
394     }
395 
396     return -1;
397 }
398 
399 LRESULT
400 UpdateLanguageDisplay(HWND hwnd, HKL hKl)
401 {
402     static TCHAR szLCID[MAX_PATH], szLangName[MAX_PATH];
403 
404     GetLayoutIDByHkl(hKl, szLCID, ARRAYSIZE(szLCID));
405     GetLocaleInfo((LANGID)_tcstoul(szLCID, NULL, 16), LOCALE_SLANGUAGE, (LPTSTR)szLangName, ARRAYSIZE(szLangName));
406     UpdateTrayIcon(hwnd, szLCID, szLangName);
407 
408     return 0;
409 }
410 
411 LRESULT
412 UpdateLanguageDisplayCurrent(HWND hwnd, WPARAM wParam)
413 {
414     return UpdateLanguageDisplay(hwnd, GetKeyboardLayout(GetWindowThreadProcessId((HWND)wParam, 0)));
415 }
416 
417 LRESULT CALLBACK
418 WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
419 {
420     static HMENU hRightPopupMenu;
421     static UINT s_uTaskbarRestart;
422 
423     switch (Message)
424     {
425         case WM_CREATE:
426         {
427             SetHooks();
428             AddTrayIcon(hwnd);
429             hRightPopupMenu = GetSubMenu(LoadMenu(hInst, MAKEINTRESOURCE(IDR_POPUP)), 0);
430 
431             ActivateLayout(hwnd, ulCurrentLayoutNum);
432             s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
433 
434             return 0;
435         }
436 
437         case WM_LANG_CHANGED:
438         {
439             return UpdateLanguageDisplay(hwnd, (HKL)lParam);
440         }
441 
442         case WM_LOAD_LAYOUT:
443         {
444             ActivateLayout(hwnd, GetNextLayout());
445 
446             return 0;
447         }
448 
449         case WM_WINDOW_ACTIVATE:
450         {
451             return UpdateLanguageDisplayCurrent(hwnd, wParam);
452         }
453 
454         case WM_NOTIFYICONMSG:
455             switch (lParam)
456             {
457                 case WM_RBUTTONUP:
458                 case WM_LBUTTONUP:
459                 {
460                     POINT pt;
461 
462                     GetCursorPos(&pt);
463                     SetForegroundWindow(hwnd);
464 
465                     if (lParam == WM_LBUTTONUP)
466                     {
467                         HMENU hLeftPopupMenu;
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                     return 0;
506                 }
507 
508                 default:
509                     ActivateLayout(hwnd, LOWORD(wParam));
510                     return 0;
511             }
512             break;
513 
514         case WM_SETTINGCHANGE:
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             if (wParam == SPI_SETNONCLIENTMETRICS)
521             {
522                 return UpdateLanguageDisplayCurrent(hwnd, wParam);
523             }
524         }
525         break;
526 
527         case WM_DESTROY:
528         {
529             DeleteHooks();
530             DestroyMenu(hRightPopupMenu);
531             DelTrayIcon(hwnd);
532             PostQuitMessage(0);
533 
534             return 0;
535         }
536 
537         default:
538             if(Message == s_uTaskbarRestart)
539                 AddTrayIcon(hwnd);
540             break;
541     }
542 
543     if (Message == ShellHookMessage && wParam == HSHELL_LANGUAGE)
544     {
545         PostMessage(hwnd, WM_LANG_CHANGED, wParam, lParam);
546         return 0;
547     }
548 
549     return DefWindowProc(hwnd, Message, wParam, lParam);
550 }
551 
552 INT WINAPI
553 _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPTSTR lpCmdLine, INT nCmdShow)
554 {
555     WNDCLASS WndClass = {0};
556     MSG msg;
557     HANDLE hMutex;
558     HWND hwnd;
559 
560     switch (GetUserDefaultUILanguage())
561     {
562         case MAKELANGID(LANG_HEBREW, SUBLANG_DEFAULT):
563             SetProcessDefaultLayout(LAYOUT_RTL);
564             break;
565         default:
566             break;
567     }
568 
569     hMutex = CreateMutex(NULL, FALSE, szKbSwitcherName);
570     if (!hMutex)
571         return 1;
572 
573     if (GetLastError() == ERROR_ALREADY_EXISTS)
574     {
575         CloseHandle(hMutex);
576         return 1;
577     }
578 
579     hInst = hInstance;
580     hProcessHeap = GetProcessHeap();
581 
582     WndClass.style = 0;
583     WndClass.lpfnWndProc   = WndProc;
584     WndClass.cbClsExtra    = 0;
585     WndClass.cbWndExtra    = 0;
586     WndClass.hInstance     = hInstance;
587     WndClass.hIcon         = NULL;
588     WndClass.hCursor       = NULL;
589     WndClass.hbrBackground = NULL;
590     WndClass.lpszMenuName  = NULL;
591     WndClass.lpszClassName = szKbSwitcherName;
592 
593     if (!RegisterClass(&WndClass))
594     {
595         CloseHandle(hMutex);
596         return 1;
597     }
598 
599     hwnd = CreateWindow(szKbSwitcherName, NULL, 0, 0, 0, 1, 1, HWND_DESKTOP, NULL, hInstance, NULL);
600     ShellHookMessage = RegisterWindowMessage(L"SHELLHOOK");
601     RegisterShellHookWindow(hwnd);
602 
603     while(GetMessage(&msg,NULL,0,0))
604     {
605         TranslateMessage(&msg);
606         DispatchMessage(&msg);
607     }
608 
609     CloseHandle(hMutex);
610 
611     return 0;
612 }
613