xref: /reactos/base/applications/osk/main.c (revision 016acd17)
1 /*
2  * PROJECT:         ReactOS On-Screen Keyboard
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * PURPOSE:         On-screen keyboard.
5  * COPYRIGHT:       Denis ROBERT
6  *                  Copyright 2019-2020 George Bișoc (george.bisoc@reactos.org)
7  *                  Baruch Rutman (peterooch at gmail dot com)
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "precomp.h"
13 
14 /* GLOBALS ********************************************************************/
15 
16 OSK_GLOBALS Globals;
17 
18 OSK_KEYLEDINDICATOR LedKey[] =
19 {
20     {VK_NUMLOCK, IDC_LED_NUM, 0x0145, FALSE},
21     {VK_CAPITAL, IDC_LED_CAPS, 0x013A, FALSE},
22     {VK_SCROLL, IDC_LED_SCROLL, 0x0146, FALSE}
23 };
24 
25 /* FUNCTIONS ******************************************************************/
26 
27 /***********************************************************************
28  *
29  *           OSK_SetImage
30  *
31  *  Set an image on a button
32  */
OSK_SetImage(int IdDlgItem,int IdResource)33 int OSK_SetImage(int IdDlgItem, int IdResource)
34 {
35     HICON hIcon;
36     HWND hWndItem;
37 
38     hIcon = (HICON)LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IdResource),
39                               IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
40     if (hIcon == NULL)
41         return FALSE;
42 
43     hWndItem = GetDlgItem(Globals.hMainWnd, IdDlgItem);
44     if (hWndItem == NULL)
45     {
46         DestroyIcon(hIcon);
47         return FALSE;
48     }
49 
50     SendMessageW(hWndItem, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
51 
52     /* The system automatically deletes these resources when the process that created them terminates (MSDN) */
53 
54     return TRUE;
55 }
56 
57 /***********************************************************************
58  *
59  *           OSK_SetText
60  *
61  *  Update the text of a button according to the relevant language resource
62  */
OSK_SetText(int IdDlgItem,int IdResource)63 void OSK_SetText(int IdDlgItem, int IdResource)
64 {
65     WCHAR szText[MAX_PATH];
66     HWND hWndItem;
67 
68     hWndItem = GetDlgItem(Globals.hMainWnd, IdDlgItem);
69 
70     if (hWndItem == NULL)
71         return;
72 
73     LoadStringW(Globals.hInstance, IdResource, szText, _countof(szText));
74 
75     SetWindowTextW(hWndItem, szText);
76 }
77 /***********************************************************************
78  *
79  *          OSK_WarningProc
80  *
81  *  Function handler for the warning dialog box on startup
82  */
OSK_WarningProc(HWND hDlg,UINT Msg,WPARAM wParam,LPARAM lParam)83 INT_PTR CALLBACK OSK_WarningProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
84 {
85     UNREFERENCED_PARAMETER(lParam);
86 
87     switch (Msg)
88     {
89         case WM_INITDIALOG:
90         {
91             return TRUE;
92         }
93 
94         case WM_COMMAND:
95         {
96             switch (LOWORD(wParam))
97             {
98                 case IDC_SHOWWARNINGCHECK:
99                 {
100                     Globals.bShowWarning = !IsDlgButtonChecked(hDlg, IDC_SHOWWARNINGCHECK);
101                     return TRUE;
102                 }
103 
104                 case IDOK:
105                 case IDCANCEL:
106                 {
107                     EndDialog(hDlg, LOWORD(wParam));
108                     return TRUE;
109                 }
110             }
111             break;
112         }
113     }
114 
115     return FALSE;
116 }
117 
118 /***********************************************************************
119  *
120  *          OSK_WarningDlgThread
121  *
122  *  Thread procedure routine for the warning dialog box
123  */
OSK_WarningDlgThread(LPVOID lpParameter)124 DWORD WINAPI OSK_WarningDlgThread(LPVOID lpParameter)
125 {
126     HINSTANCE hInstance = (HINSTANCE)lpParameter;
127 
128     DialogBoxW(hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
129     return 0;
130 }
131 
132 /***********************************************************************
133  *
134  *          OSK_About
135  *
136  *  Initializes the "About" dialog box
137  */
OSK_About(VOID)138 VOID OSK_About(VOID)
139 {
140     WCHAR szAuthors[MAX_PATH];
141 
142     /* Load the strings into the "About" dialog */
143     LoadStringW(Globals.hInstance, IDS_AUTHORS, szAuthors, _countof(szAuthors));
144 
145     /* Load the icon */
146     /* Finally, execute the "About" dialog by using the Shell routine */
147     ShellAboutW(Globals.hMainWnd, Globals.szTitle, szAuthors,
148                 LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED));
149 }
150 
151 /***********************************************************************
152  *
153  *           OSK_DestroyKeys
154  *
155  *  Used in layout change or in shutdown
156  */
OSK_DestroyKeys(VOID)157 VOID OSK_DestroyKeys(VOID)
158 {
159     int i;
160     /* Hide before destroying child controls */
161     ShowWindow(Globals.hMainWnd, SW_HIDE);
162 
163     for (i = 0; i < Globals.Keyboard->KeyCount; i++)
164     {
165         DestroyWindow(Globals.hKeys[i]);
166     }
167     for (i = 0; i < _countof(LedKey); i++)
168     {
169         DestroyWindow(GetDlgItem(Globals.hMainWnd, LedKey[i].DlgResource));
170     }
171 
172     HeapFree(GetProcessHeap(), 0, Globals.hKeys);
173     Globals.hKeys = NULL;
174     Globals.Keyboard = NULL;
175 }
176 
177 /***********************************************************************
178  *
179  *           OSK_SetKeys
180  *
181  *  Create/Update button controls with the relevant keyboard values
182  */
OSK_SetKeys(int reason)183 LRESULT OSK_SetKeys(int reason)
184 {
185     WCHAR wKey[2];
186     BYTE bKeyStates[256];
187     LPCWSTR szKey;
188     PKEY Keys;
189     UINT uVirtKey;
190     POINT LedPos;
191     SIZE LedSize;
192     int i, yPad;
193 
194     /* Get key states before doing anything */
195     if (!GetKeyboardState(bKeyStates))
196     {
197         DPRINT("OSK_SetKeys(): GetKeyboardState() call failed.\n");
198         return -1;
199     }
200 
201     switch (reason)
202     {
203         case SETKEYS_LANG:
204         {
205             /* Keyboard language/caps change, just update the button texts */
206             Keys = Globals.Keyboard->Keys;
207             for (i = 0; i < Globals.Keyboard->KeyCount; i++)
208             {
209                 if (!Keys[i].translate)
210                     continue;
211 
212                 uVirtKey = MapVirtualKeyW(Keys[i].scancode & SCANCODE_MASK, MAPVK_VSC_TO_VK);
213 
214                 if (ToUnicode(uVirtKey, Keys[i].scancode & SCANCODE_MASK, bKeyStates, wKey, _countof(wKey), 0) >= 1)
215                 {
216                     szKey = wKey;
217                 }
218                 else
219                 {
220                     szKey = Keys[i].name;
221                 }
222 
223                 /* Only one & the button will try to underline the next character... */
224                 if (wcsncmp(szKey, L"&", 1) == 0)
225                     szKey = L"&&";
226 
227                 SetWindowTextW(Globals.hKeys[i], szKey);
228             }
229             return 0;
230         }
231         case SETKEYS_LAYOUT:
232         {
233             /* Clear up current layout before applying a different one */
234             OSK_DestroyKeys();
235         }
236         /* Fallthrough */
237         case SETKEYS_INIT:
238         {
239             if (Globals.bIsEnhancedKeyboard)
240             {
241                 Globals.Keyboard = &EnhancedKeyboard;
242             }
243             else
244             {
245                 Globals.Keyboard = &StandardKeyboard;
246             }
247 
248             Globals.hKeys = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HWND) * Globals.Keyboard->KeyCount);
249 
250             if (!Globals.hKeys)
251             {
252                 DPRINT("OSK_SetKeys(): Failed to allocate memory for button handles.\n");
253                 return -1;
254             }
255 
256             Keys = Globals.Keyboard->Keys;
257 
258             /* Create key buttons */
259             for (i = 0; i < Globals.Keyboard->KeyCount; i++)
260             {
261                 uVirtKey = MapVirtualKeyW(Keys[i].scancode & SCANCODE_MASK, MAPVK_VSC_TO_VK);
262 
263                 if (Keys[i].translate && ToUnicode(uVirtKey, Keys[i].scancode & SCANCODE_MASK, bKeyStates, wKey, _countof(wKey), 0) >= 1)
264                 {
265                     szKey = wKey;
266                 }
267                 else
268                 {
269                     szKey = Keys[i].name;
270                 }
271 
272                 Globals.hKeys[i] = CreateWindowW(WC_BUTTONW,
273                                                  szKey,
274                                                  WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | Keys[i].flags,
275                                                  Keys[i].x,
276                                                  Keys[i].y,
277                                                  Keys[i].cx,
278                                                  Keys[i].cy,
279                                                  Globals.hMainWnd,
280                                                  (HMENU)Keys[i].scancode,
281                                                  Globals.hInstance,
282                                                  NULL);
283                 if (Globals.hFont)
284                     SendMessageW(Globals.hKeys[i], WM_SETFONT, (WPARAM)Globals.hFont, 0);
285             }
286 
287             /* Add additional padding for caption and menu */
288             yPad = GetSystemMetrics(SM_CYSIZE) + GetSystemMetrics(SM_CYMENU);
289             /* Size window according to layout */
290             SetWindowPos(Globals.hMainWnd,
291                          (Globals.bAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST),
292                          0,
293                          0,
294                          Globals.Keyboard->Size.cx,
295                          Globals.Keyboard->Size.cy + yPad,
296                          SWP_NOMOVE);
297 
298             /* Create LEDs */
299             LedPos  = Globals.Keyboard->LedStart;
300             LedSize = Globals.Keyboard->LedSize;
301 
302             CreateWindowW(WC_STATICW, L"", WS_VISIBLE | WS_CHILD | SS_CENTER | SS_NOTIFY,
303                 LedPos.x, LedPos.y, LedSize.cx, LedSize.cy, Globals.hMainWnd,
304                 (HMENU)IDC_LED_NUM, Globals.hInstance, NULL);
305 
306             LedPos.x += Globals.Keyboard->LedGap;
307 
308             CreateWindowW(WC_STATICW, L"", WS_VISIBLE | WS_CHILD | SS_CENTER | SS_NOTIFY,
309                 LedPos.x, LedPos.y, LedSize.cx, LedSize.cy, Globals.hMainWnd,
310                 (HMENU)IDC_LED_CAPS, Globals.hInstance, NULL);
311 
312             LedPos.x += Globals.Keyboard->LedGap;
313 
314             CreateWindowW(WC_STATICW, L"", WS_VISIBLE | WS_CHILD | SS_CENTER | SS_NOTIFY,
315                 LedPos.x, LedPos.y, LedSize.cx, LedSize.cy, Globals.hMainWnd,
316                 (HMENU)IDC_LED_SCROLL, Globals.hInstance, NULL);
317 
318             /* Set system keys text */
319             OSK_SetText(SCAN_CODE_110, IDS_ESCAPE);
320             OSK_SetText(SCAN_CODE_124, IDS_PRN);
321             OSK_SetText(SCAN_CODE_125, IDS_STOP);
322             OSK_SetText(SCAN_CODE_126, IDS_ATTN);
323             OSK_SetText(SCAN_CODE_90, IDS_NUMLOCKKEY);
324             OSK_SetText(SCAN_CODE_75, IDS_INSERT);
325             OSK_SetText(SCAN_CODE_76, IDS_DELETE);
326             OSK_SetText(SCAN_CODE_81, IDS_END);
327             OSK_SetText(SCAN_CODE_58, IDS_CTRL);     /* Left ctrl */
328             OSK_SetText(SCAN_CODE_64, IDS_CTRL);     /* Right ctrl */
329             OSK_SetText(SCAN_CODE_60, IDS_LEFTALT);
330             OSK_SetText(SCAN_CODE_62, IDS_RIGHTALT);
331 
332             /* Set icon on visual buttons */
333             OSK_SetImage(SCAN_CODE_15, IDI_BACK);
334             OSK_SetImage(SCAN_CODE_16, IDI_TAB);
335             OSK_SetImage(SCAN_CODE_30, IDI_CAPS_LOCK);
336             OSK_SetImage(SCAN_CODE_43, IDI_RETURN);
337             OSK_SetImage(SCAN_CODE_44, IDI_SHIFT);
338             OSK_SetImage(SCAN_CODE_57, IDI_SHIFT);
339             OSK_SetImage(SCAN_CODE_127, IDI_REACTOS);
340             OSK_SetImage(SCAN_CODE_128, IDI_REACTOS);
341             OSK_SetImage(SCAN_CODE_129, IDI_MENU);
342             OSK_SetImage(SCAN_CODE_80, IDI_HOME);
343             OSK_SetImage(SCAN_CODE_85, IDI_PG_UP);
344             OSK_SetImage(SCAN_CODE_86, IDI_PG_DOWN);
345             OSK_SetImage(SCAN_CODE_79, IDI_LEFT);
346             OSK_SetImage(SCAN_CODE_83, IDI_TOP);
347             OSK_SetImage(SCAN_CODE_84, IDI_BOTTOM);
348             OSK_SetImage(SCAN_CODE_89, IDI_RIGHT);
349         }
350     }
351 
352     if (reason != SETKEYS_INIT)
353     {
354         ShowWindow(Globals.hMainWnd, SW_SHOW);
355         UpdateWindow(Globals.hMainWnd);
356     }
357 
358     return 0;
359 }
360 
361 /***********************************************************************
362  *
363  *           OSK_Create
364  *
365  *  Handling of WM_CREATE
366  */
OSK_Create(HWND hwnd)367 LRESULT OSK_Create(HWND hwnd)
368 {
369     HMONITOR monitor;
370     MONITORINFO info;
371     POINT Pt;
372     RECT rcWindow, rcDlgIntersect;
373     LOGFONTW lf = {0};
374 
375     /* Save handle */
376     Globals.hMainWnd = hwnd;
377 
378     /* Init Font */
379     lf.lfHeight = Globals.FontHeight;
380     StringCchCopyW(lf.lfFaceName, _countof(Globals.FontFaceName), Globals.FontFaceName);
381     Globals.hFont = CreateFontIndirectW(&lf);
382 
383     if (OSK_SetKeys(SETKEYS_INIT) == -1)
384         return -1;
385 
386     /* Check the checked menu item before displaying the window */
387     if (Globals.bIsEnhancedKeyboard)
388     {
389         /* Enhanced keyboard dialog chosen, set the respective menu item as checked */
390         CheckMenuItem(GetMenu(hwnd), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
391         CheckMenuItem(GetMenu(hwnd), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
392     }
393     else
394     {
395         /* Standard keyboard dialog chosen, set the respective menu item as checked */
396         CheckMenuItem(GetMenu(hwnd), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
397         CheckMenuItem(GetMenu(hwnd), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
398     }
399 
400     /* Check if the "Click Sound" option was chosen before (and if so, then tick the menu item) */
401     if (Globals.bSoundClick)
402     {
403         CheckMenuItem(GetMenu(hwnd), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
404     }
405 
406     /* Get screen info */
407     memset(&Pt, 0, sizeof(Pt));
408     monitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
409     info.cbSize = sizeof(info);
410     GetMonitorInfoW(monitor, &info);
411     GetWindowRect(hwnd, &rcWindow);
412 
413     /*
414         If the coordination values are default then re-initialize using the specific formulas
415         to move the dialog at the bottom of the screen.
416     */
417     if (Globals.PosX == CW_USEDEFAULT && Globals.PosY == CW_USEDEFAULT)
418     {
419         Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
420         Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
421     }
422 
423     /*
424         Calculate the intersection of two rectangle sources (dialog and work desktop area).
425         If such sources do not intersect, then the dialog is deemed as "off screen".
426     */
427     if (IntersectRect(&rcDlgIntersect, &rcWindow, &info.rcWork) == 0)
428     {
429         Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
430         Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
431     }
432     else
433     {
434         /*
435             There's still some intersection but we're not for sure if it is sufficient (the dialog could also be partially hidden).
436             Therefore, check the remaining intersection if it's enough.
437         */
438         if (rcWindow.top < info.rcWork.top || rcWindow.left < info.rcWork.left || rcWindow.right > info.rcWork.right || rcWindow.bottom > info.rcWork.bottom)
439         {
440             Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
441             Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
442         }
443     }
444 
445     /*
446         Place the window (with respective placement coordinates) as topmost, above
447         every window which are not on top or are at the bottom of the Z order.
448     */
449     if (Globals.bAlwaysOnTop)
450     {
451         CheckMenuItem(GetMenu(hwnd), IDM_ON_TOP, MF_BYCOMMAND | MF_CHECKED);
452         SetWindowPos(hwnd, HWND_TOPMOST, Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
453     }
454     else
455     {
456         CheckMenuItem(GetMenu(hwnd), IDM_ON_TOP, MF_BYCOMMAND | MF_UNCHECKED);
457         SetWindowPos(hwnd, HWND_NOTOPMOST, Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
458     }
459 
460     /* Create a green brush for leds */
461     Globals.hBrushGreenLed = CreateSolidBrush(RGB(0, 255, 0));
462 
463     /* Set a timer for periodic tasks */
464     Globals.iTimer = SetTimer(hwnd, 0, 100, NULL);
465 
466     /* If the member of the struct (bShowWarning) is set then display the dialog box */
467     if (Globals.bShowWarning)
468     {
469         /* If for whatever reason the thread fails to be created then handle the dialog box in main thread... */
470         if (CreateThread(NULL, 0, OSK_WarningDlgThread, (PVOID)Globals.hInstance, 0, NULL) == NULL)
471         {
472             DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
473         }
474     }
475 
476     return 0;
477 }
478 
479 /***********************************************************************
480  *
481  *           OSK_Close
482  *
483  *  Handling of WM_CLOSE
484  */
OSK_Close(void)485 int OSK_Close(void)
486 {
487     KillTimer(Globals.hMainWnd, Globals.iTimer);
488 
489     /* Release Ctrl, Shift, Alt keys */
490     OSK_ReleaseKey(SCAN_CODE_44); // Left shift
491     OSK_ReleaseKey(SCAN_CODE_57); // Right shift
492     OSK_ReleaseKey(SCAN_CODE_58); // Left ctrl
493     OSK_ReleaseKey(SCAN_CODE_60); // Left alt
494     OSK_ReleaseKey(SCAN_CODE_62); // Right alt
495     OSK_ReleaseKey(SCAN_CODE_64); // Right ctrl
496 
497     /* Destroy child controls */
498     OSK_DestroyKeys();
499 
500     /* delete GDI objects */
501     if (Globals.hBrushGreenLed) DeleteObject(Globals.hBrushGreenLed);
502     if (Globals.hFont) DeleteObject(Globals.hFont);
503 
504     /* Save the application's settings on registry */
505     SaveSettings();
506 
507     return TRUE;
508 }
509 
510 /***********************************************************************
511  *
512  *           OSK_RefreshLEDKeys
513  *
514  *  Updates (invalidates) the LED icon resources then the respective
515  *  keys (Caps Lock, Scroll Lock or Num Lock) are being held down
516  */
OSK_RefreshLEDKeys(VOID)517 VOID OSK_RefreshLEDKeys(VOID)
518 {
519     INT i;
520     BOOL bKeyIsPressed;
521 
522     for (i = 0; i < _countof(LedKey); i++)
523     {
524         bKeyIsPressed = (GetAsyncKeyState(LedKey[i].vKey) & 0x8000) != 0;
525         if (LedKey[i].bWasKeyPressed != bKeyIsPressed)
526         {
527             LedKey[i].bWasKeyPressed = bKeyIsPressed;
528             InvalidateRect(GetDlgItem(Globals.hMainWnd, LedKey[i].DlgResource), NULL, FALSE);
529         }
530     }
531 }
532 
533 /***********************************************************************
534  *
535  *           OSK_Timer
536  *
537  *  Handling of WM_TIMER
538  */
OSK_Timer(void)539 int OSK_Timer(void)
540 {
541     HWND hWndActiveWindow;
542     DWORD dwThread;
543     HKL hKeyboardLayout;
544 
545     hWndActiveWindow = GetForegroundWindow();
546     if (hWndActiveWindow != NULL && hWndActiveWindow != Globals.hMainWnd)
547     {
548         /* Grab the current keyboard layout from the foreground window */
549         dwThread = GetWindowThreadProcessId(hWndActiveWindow, NULL);
550         hKeyboardLayout = GetKeyboardLayout(dwThread);
551         /* Activate the layout */
552         ActivateKeyboardLayout(hKeyboardLayout, 0);
553     }
554 
555     /*
556         Update the LED key indicators accordingly to their state (if one
557         of the specific keys is held down).
558     */
559     OSK_RefreshLEDKeys();
560     /* Update the buttons */
561     OSK_SetKeys(SETKEYS_LANG);
562 
563     return TRUE;
564 }
565 
566 /***********************************************************************
567  *
568  *           OSK_ChooseFont
569  *
570  *  Change the font of which the keys are being displayed
571  */
OSK_ChooseFont(VOID)572 VOID OSK_ChooseFont(VOID)
573 {
574     LOGFONTW lf = {0};
575     CHOOSEFONTW cf = {0};
576     HFONT hFont, hOldFont;
577     int i;
578 
579     StringCchCopyW(lf.lfFaceName, _countof(Globals.FontFaceName), Globals.FontFaceName);
580     lf.lfHeight = Globals.FontHeight;
581 
582     cf.lStructSize = sizeof(cf);
583     cf.hwndOwner = Globals.hMainWnd;
584     cf.lpLogFont = &lf;
585     cf.Flags = CF_INITTOLOGFONTSTRUCT | CF_NOSTYLESEL;
586 
587     if (!ChooseFontW(&cf))
588         return;
589 
590     hFont = CreateFontIndirectW(&lf);
591 
592     if (!hFont)
593         return;
594 
595     /* Set font information */
596     StringCchCopyW(Globals.FontFaceName, _countof(Globals.FontFaceName), lf.lfFaceName);
597     Globals.FontHeight = lf.lfHeight;
598 
599     hOldFont = Globals.hFont;
600     Globals.hFont = hFont;
601 
602     for (i = 0; i < Globals.Keyboard->KeyCount; i++)
603         SendMessageW(Globals.hKeys[i], WM_SETFONT, (WPARAM)Globals.hFont, TRUE);
604 
605     DeleteObject(hOldFont);
606 }
607 
608 /***********************************************************************
609  *
610  *           OSK_Command
611  *
612  *  All handling of commands
613  */
OSK_Command(WPARAM wCommand,HWND hWndControl)614 BOOL OSK_Command(WPARAM wCommand, HWND hWndControl)
615 {
616     WORD ScanCode;
617     INPUT Input;
618     BOOL bExtendedKey;
619     BOOL bKeyDown;
620     BOOL bKeyUp;
621     LONG WindowStyle;
622     INT i;
623 
624     /* KeyDown and/or KeyUp ? */
625     WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
626     if ((WindowStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX)
627     {
628         /* 2-states key like Shift, Alt, Ctrl, ... */
629         if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
630         {
631             bKeyDown = TRUE;
632             bKeyUp = FALSE;
633         }
634         else
635         {
636             bKeyDown = FALSE;
637             bKeyUp = TRUE;
638         }
639     }
640     else
641     {
642         /* Other key */
643         bKeyDown = TRUE;
644         bKeyUp = TRUE;
645     }
646 
647     /* Get the key from dialog control key command */
648     ScanCode = wCommand;
649 
650     /*
651         The user could've pushed one of the key buttons of the dialog that
652         can trigger particular function toggling (Caps Lock, Num Lock or Scroll Lock). Update
653         (invalidate) the LED icon resources accordingly.
654     */
655     for (i = 0; i < _countof(LedKey); i++)
656     {
657         if (LedKey[i].wScanCode == ScanCode)
658         {
659             InvalidateRect(GetDlgItem(Globals.hMainWnd, LedKey[i].DlgResource), NULL, FALSE);
660         }
661     }
662 
663     /* Extended key ? */
664     if (ScanCode & 0x0200)
665         bExtendedKey = TRUE;
666     else
667         bExtendedKey = FALSE;
668     ScanCode &= SCANCODE_MASK;
669 
670     /* Press and release the key */
671     if (bKeyDown)
672     {
673         Input.type = INPUT_KEYBOARD;
674         Input.ki.wVk = 0;
675         Input.ki.wScan = ScanCode;
676         Input.ki.time = GetTickCount();
677         Input.ki.dwExtraInfo = GetMessageExtraInfo();
678         Input.ki.dwFlags = KEYEVENTF_SCANCODE;
679         if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
680         SendInput(1, &Input, sizeof(Input));
681     }
682 
683     if (bKeyUp)
684     {
685         Input.type = INPUT_KEYBOARD;
686         Input.ki.wVk = 0;
687         Input.ki.wScan = ScanCode;
688         Input.ki.time = GetTickCount();
689         Input.ki.dwExtraInfo = GetMessageExtraInfo();
690         Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
691         if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
692         SendInput(1, &Input, sizeof(Input));
693     }
694 
695     /* Play the sound during clicking event (only if "Use Click Sound" menu option is ticked) */
696     if (Globals.bSoundClick)
697     {
698         PlaySoundW(MAKEINTRESOURCEW(IDI_SOUNDCLICK), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
699     }
700 
701     return TRUE;
702 }
703 
704 /***********************************************************************
705  *
706  *           OSK_ReleaseKey
707  *
708  *  Release the key of ID wCommand
709  */
OSK_ReleaseKey(WORD ScanCode)710 BOOL OSK_ReleaseKey(WORD ScanCode)
711 {
712     INPUT Input;
713     BOOL bExtendedKey;
714     LONG WindowStyle;
715     HWND hWndControl;
716 
717     /* Is it a 2-states key ? */
718     hWndControl = GetDlgItem(Globals.hMainWnd, ScanCode);
719     WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
720     if ((WindowStyle & BS_AUTOCHECKBOX) != BS_AUTOCHECKBOX) return FALSE;
721 
722     /* Is the key down ? */
723     if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
724 
725     /* Extended key ? */
726     if (ScanCode & 0x0200)
727         bExtendedKey = TRUE;
728     else
729         bExtendedKey = FALSE;
730     ScanCode &= SCANCODE_MASK;
731 
732     /* Release the key */
733     Input.type = INPUT_KEYBOARD;
734     Input.ki.wVk = 0;
735     Input.ki.wScan = ScanCode;
736     Input.ki.time = GetTickCount();
737     Input.ki.dwExtraInfo = GetMessageExtraInfo();
738     Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
739     if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
740     SendInput(1, &Input, sizeof(Input));
741 
742     return TRUE;
743 }
744 
745 /***********************************************************************
746  *
747  *           OSK_Paint
748  *
749  *  Handles WM_PAINT messages
750  */
OSK_Paint(HWND hwnd)751 LRESULT OSK_Paint(HWND hwnd)
752 {
753     PAINTSTRUCT ps;
754     RECT rcText;
755     HFONT hOldFont = NULL;
756     WCHAR szTemp[MAX_PATH];
757 
758     HDC hdc = BeginPaint(hwnd, &ps);
759 
760     if (Globals.hFont)
761         hOldFont = SelectObject(hdc, Globals.hFont);
762 
763     rcText.left   = Globals.Keyboard->LedTextStart.x;
764     rcText.top    = Globals.Keyboard->LedTextStart.y;
765     rcText.right  = rcText.left + Globals.Keyboard->LedTextSize.cx;
766     rcText.bottom = rcText.top + Globals.Keyboard->LedTextSize.cy;
767 
768     LoadStringW(Globals.hInstance, IDS_NUMLOCK, szTemp, _countof(szTemp));
769     DrawTextW(hdc, szTemp, -1, &rcText, DT_NOCLIP);
770 
771     OffsetRect(&rcText, Globals.Keyboard->LedTextOffset, 0);
772 
773     LoadStringW(Globals.hInstance, IDS_CAPSLOCK, szTemp, _countof(szTemp));
774     DrawTextW(hdc, szTemp, -1, &rcText, DT_NOCLIP);
775 
776     OffsetRect(&rcText, Globals.Keyboard->LedTextOffset, 0);
777 
778     LoadStringW(Globals.hInstance, IDS_SCROLLLOCK, szTemp, _countof(szTemp));
779     DrawTextW(hdc, szTemp, -1, &rcText, DT_NOCLIP);
780 
781     if (hOldFont)
782         SelectObject(hdc, hOldFont);
783 
784     EndPaint(hwnd, &ps);
785 
786     return 0;
787 }
788 /***********************************************************************
789  *
790  *       OSK_WndProc
791  */
OSK_WndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)792 LRESULT APIENTRY OSK_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
793 {
794     switch (msg)
795     {
796         case WM_CREATE:
797             return OSK_Create(hwnd);
798 
799         case WM_PAINT:
800             return OSK_Paint(hwnd);
801 
802         case WM_TIMER:
803             return OSK_Timer();
804 
805         case WM_CTLCOLORSTATIC:
806             if ((HWND)lParam == GetDlgItem(hwnd, IDC_LED_NUM))
807             {
808                 if (GetKeyState(VK_NUMLOCK) & 0x0001)
809                     return (LRESULT)Globals.hBrushGreenLed;
810                 else
811                     return (LRESULT)GetStockObject(BLACK_BRUSH);
812             }
813             if ((HWND)lParam == GetDlgItem(hwnd, IDC_LED_CAPS))
814             {
815                 if (GetKeyState(VK_CAPITAL) & 0x0001)
816                     return (LRESULT)Globals.hBrushGreenLed;
817                 else
818                     return (LRESULT)GetStockObject(BLACK_BRUSH);
819             }
820             if ((HWND)lParam == GetDlgItem(hwnd, IDC_LED_SCROLL))
821             {
822                 if (GetKeyState(VK_SCROLL) & 0x0001)
823                     return (LRESULT)Globals.hBrushGreenLed;
824                 else
825                     return (LRESULT)GetStockObject(BLACK_BRUSH);
826             }
827             break;
828 
829         case WM_COMMAND:
830             switch (LOWORD(wParam))
831             {
832                 case IDM_EXIT:
833                 {
834                     PostMessageW(hwnd, WM_CLOSE, 0, 0);
835                     break;
836                 }
837 
838                 case IDM_ENHANCED_KB:
839                 {
840                     if (!Globals.bIsEnhancedKeyboard)
841                     {
842                         /*
843                             The user attempted to switch to enhanced keyboard dialog type.
844                             Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
845                         */
846                         Globals.bIsEnhancedKeyboard = TRUE;
847                         SaveSettings();
848 
849                         /* Change the condition of enhanced keyboard item menu to checked */
850                         CheckMenuItem(GetMenu(hwnd), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
851                         CheckMenuItem(GetMenu(hwnd), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
852 
853                         /* Finally, update the key layout */
854                         LoadSettings();
855                         OSK_SetKeys(SETKEYS_LAYOUT);
856                     }
857 
858                     break;
859                 }
860 
861                 case IDM_STANDARD_KB:
862                 {
863                     if (Globals.bIsEnhancedKeyboard)
864                     {
865                         /*
866                             The user attempted to switch to standard keyboard dialog type.
867                             Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
868                         */
869                         Globals.bIsEnhancedKeyboard = FALSE;
870                         SaveSettings();
871 
872                         /* Change the condition of standard keyboard item menu to checked */
873                         CheckMenuItem(GetMenu(hwnd), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
874                         CheckMenuItem(GetMenu(hwnd), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
875 
876                         /* Finally, update the key layout */
877                         LoadSettings();
878                         OSK_SetKeys(SETKEYS_LAYOUT);
879                     }
880 
881                     break;
882                 }
883 
884                 case IDM_CLICK_SOUND:
885                 {
886                     /*
887                         This case is triggered when the user attempts to click on the menu item. Before doing anything,
888                         we must check the condition state of such menu item so that we can tick/untick the menu item accordingly.
889                     */
890                     if (!Globals.bSoundClick)
891                     {
892                         Globals.bSoundClick = TRUE;
893                         CheckMenuItem(GetMenu(hwnd), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
894                     }
895                     else
896                     {
897                         Globals.bSoundClick = FALSE;
898                         CheckMenuItem(GetMenu(hwnd), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_UNCHECKED);
899                     }
900 
901                     break;
902                 }
903 
904                 case IDM_ON_TOP:
905                 {
906                     /*
907                         Check the condition state before disabling/enabling the menu
908                         item and change the topmost order.
909                     */
910                     if (!Globals.bAlwaysOnTop)
911                     {
912                         Globals.bAlwaysOnTop = TRUE;
913                         CheckMenuItem(GetMenu(hwnd), IDM_ON_TOP, MF_BYCOMMAND | MF_CHECKED);
914                         SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
915                     }
916                     else
917                     {
918                         Globals.bAlwaysOnTop = FALSE;
919                         CheckMenuItem(GetMenu(hwnd), IDM_ON_TOP, MF_BYCOMMAND | MF_UNCHECKED);
920                         SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
921                     }
922 
923                     break;
924                 }
925 
926                 case IDM_FONT:
927                 {
928                     OSK_ChooseFont();
929                     break;
930                 }
931 
932                 case IDM_ABOUT:
933                 {
934                     OSK_About();
935                     break;
936                 }
937 
938                 default:
939                     OSK_Command(wParam, (HWND)lParam);
940                     break;
941             }
942             return 0;
943 
944         case WM_THEMECHANGED:
945             /* Redraw the dialog (and its control buttons) using the new theme */
946             InvalidateRect(hwnd, NULL, FALSE);
947             return 0;
948 
949         case WM_CLOSE:
950             OSK_Close();
951             PostQuitMessage(0);
952             return 0;
953     }
954     return DefWindowProcW(hwnd, msg, wParam, lParam);
955 }
956 
957 /***********************************************************************
958  *
959  *       WinMain
960  */
wWinMain(HINSTANCE hInstance,HINSTANCE prev,LPWSTR cmdline,int show)961 int WINAPI wWinMain(HINSTANCE hInstance,
962                     HINSTANCE prev,
963                     LPWSTR cmdline,
964                     int show)
965 {
966     DWORD dwError;
967     HANDLE hMutex;
968     INITCOMMONCONTROLSEX iccex;
969     WNDCLASSEXW wc = {0};
970     MSG msg;
971     HWND hwnd;
972 
973     UNREFERENCED_PARAMETER(prev);
974     UNREFERENCED_PARAMETER(cmdline);
975     UNREFERENCED_PARAMETER(show);
976 
977     /*
978         Obtain a mutex for the program. This will ensure that
979         the program is launched only once.
980     */
981     hMutex = CreateMutexW(NULL, FALSE, L"OSKRunning");
982 
983     if (hMutex)
984     {
985         /* Check if there's already a mutex for the program */
986         dwError = GetLastError();
987 
988         if (dwError == ERROR_ALREADY_EXISTS)
989         {
990             /*
991                 A mutex with the object name has been created previously.
992                 Therefore, another instance is already running.
993             */
994             DPRINT("wWinMain(): Failed to create a mutex! The program instance is already running.\n");
995             CloseHandle(hMutex);
996             return 0;
997         }
998     }
999 
1000     /* Load the common controls */
1001     iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1002     iccex.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
1003     InitCommonControlsEx(&iccex);
1004 
1005     ZeroMemory(&Globals, sizeof(Globals));
1006     Globals.hInstance = hInstance;
1007 
1008     /* Load the application's settings from the registry */
1009     LoadSettings();
1010 
1011     /* Define the window class */
1012     wc.cbSize        = sizeof(wc);
1013     wc.hInstance     = Globals.hInstance;
1014     wc.lpfnWndProc   = OSK_WndProc;
1015     wc.lpszMenuName  = MAKEINTRESOURCEW(IDR_OSK_MENU);
1016     wc.lpszClassName = OSK_CLASS;
1017     wc.style         = CS_HREDRAW | CS_VREDRAW;
1018     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
1019     /* Set the application's icon */
1020     wc.hIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
1021     wc.hIconSm = CopyImage(wc.hIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_COPYFROMRESOURCE);
1022 
1023     if (!RegisterClassExW(&wc))
1024         goto quit;
1025 
1026     /* Load window title */
1027     LoadStringW(Globals.hInstance, IDS_OSK, Globals.szTitle, _countof(Globals.szTitle));
1028 
1029     hwnd = CreateWindowExW(WS_EX_TOPMOST | WS_EX_APPWINDOW | WS_EX_NOACTIVATE,
1030                            OSK_CLASS,
1031                            Globals.szTitle,
1032                            WS_SYSMENU | WS_MINIMIZEBOX,
1033                            CW_USEDEFAULT,
1034                            CW_USEDEFAULT,
1035                            CW_USEDEFAULT,
1036                            CW_USEDEFAULT,
1037                            NULL,
1038                            NULL,
1039                            Globals.hInstance,
1040                            NULL);
1041 
1042     if (!hwnd)
1043         goto quit;
1044 
1045     ShowWindow(hwnd, SW_SHOW);
1046     UpdateWindow(hwnd);
1047 
1048     while (GetMessageW(&msg, NULL, 0, 0))
1049     {
1050         TranslateMessage(&msg);
1051         DispatchMessageW(&msg);
1052     }
1053 
1054 quit:
1055     /* Delete the mutex */
1056     if (hMutex)
1057     {
1058         CloseHandle(hMutex);
1059     }
1060 
1061     return 0;
1062 }
1063 
1064 /* EOF */
1065