xref: /reactos/base/applications/osk/main.c (revision 34593d93)
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  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "precomp.h"
12 
13 /* GLOBALS ********************************************************************/
14 
15 OSK_GLOBALS Globals;
16 
17 OSK_KEYLEDINDICATOR LedKey[] =
18 {
19     {VK_NUMLOCK, IDC_LED_NUM, 0x0145, FALSE},
20     {VK_CAPITAL, IDC_LED_CAPS, 0x013A, FALSE},
21     {VK_SCROLL, IDC_LED_SCROLL, 0x0146, FALSE}
22 };
23 
24 /* FUNCTIONS ******************************************************************/
25 
26 /***********************************************************************
27  *
28  *           OSK_SetImage
29  *
30  *  Set an image on a button
31  */
32 int OSK_SetImage(int IdDlgItem, int IdResource)
33 {
34     HICON hIcon;
35     HWND hWndItem;
36 
37     hIcon = (HICON)LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IdResource),
38                               IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
39     if (hIcon == NULL)
40         return FALSE;
41 
42     hWndItem = GetDlgItem(Globals.hMainWnd, IdDlgItem);
43     if (hWndItem == NULL)
44     {
45         DestroyIcon(hIcon);
46         return FALSE;
47     }
48 
49     SendMessageW(hWndItem, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
50 
51     /* The system automatically deletes these resources when the process that created them terminates (MSDN) */
52 
53     return TRUE;
54 }
55 
56 /***********************************************************************
57  *
58  *          OSK_WarningProc
59  *
60  *  Function handler for the warning dialog box on startup
61  */
62 INT_PTR CALLBACK OSK_WarningProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
63 {
64     UNREFERENCED_PARAMETER(lParam);
65 
66     switch (Msg)
67     {
68         case WM_INITDIALOG:
69         {
70             return TRUE;
71         }
72 
73         case WM_COMMAND:
74         {
75             switch (LOWORD(wParam))
76             {
77                 case IDC_SHOWWARNINGCHECK:
78                 {
79                     Globals.bShowWarning = !IsDlgButtonChecked(hDlg, IDC_SHOWWARNINGCHECK);
80                     return TRUE;
81                 }
82 
83                 case IDOK:
84                 case IDCANCEL:
85                 {
86                     EndDialog(hDlg, LOWORD(wParam));
87                     return TRUE;
88                 }
89             }
90             break;
91         }
92     }
93 
94     return FALSE;
95 }
96 
97 /***********************************************************************
98  *
99  *          OSK_WarningDlgThread
100  *
101  *  Thread procedure routine for the warning dialog box
102  */
103 DWORD WINAPI OSK_WarningDlgThread(LPVOID lpParameter)
104 {
105     HINSTANCE hInstance = (HINSTANCE)lpParameter;
106 
107     DialogBoxW(hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
108     return 0;
109 }
110 
111 /***********************************************************************
112  *
113  *          OSK_About
114  *
115  *  Initializes the "About" dialog box
116  */
117 VOID OSK_About(VOID)
118 {
119     WCHAR szTitle[MAX_BUFF];
120     WCHAR szAuthors[MAX_BUFF];
121     HICON OSKIcon;
122 
123     /* Load the icon */
124     OSKIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
125 
126     /* Load the strings into the "About" dialog */
127     LoadStringW(Globals.hInstance, STRING_OSK, szTitle, countof(szTitle));
128     LoadStringW(Globals.hInstance, STRING_AUTHORS, szAuthors, countof(szAuthors));
129 
130     /* Finally, execute the "About" dialog by using the Shell routine */
131     ShellAboutW(Globals.hMainWnd, szTitle, szAuthors, OSKIcon);
132 
133     /* Once done, destroy the icon */
134     DestroyIcon(OSKIcon);
135 }
136 
137 
138 /***********************************************************************
139  *
140  *           OSK_DlgInitDialog
141  *
142  *  Handling of WM_INITDIALOG
143  */
144 int OSK_DlgInitDialog(HWND hDlg)
145 {
146     HICON hIcon, hIconSm;
147     HMONITOR monitor;
148     MONITORINFO info;
149     POINT Pt;
150     RECT rcWindow, rcDlgIntersect;
151 
152     /* Save handle */
153     Globals.hMainWnd = hDlg;
154 
155     /* Check the checked menu item before displaying the modal box */
156     if (Globals.bIsEnhancedKeyboard)
157     {
158         /* Enhanced keyboard dialog chosen, set the respective menu item as checked */
159         CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
160         CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
161     }
162     else
163     {
164         /* Standard keyboard dialog chosen, set the respective menu item as checked */
165         CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
166         CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
167     }
168 
169     /* Check if the "Click Sound" option was chosen before (and if so, then tick the menu item) */
170     if (Globals.bSoundClick)
171     {
172         CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
173     }
174 
175     /* Set the application's icon */
176     hIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
177     hIconSm = CopyImage(hIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_COPYFROMRESOURCE);
178     if (hIcon || hIconSm)
179     {
180         /* Set the window icons (they are deleted when the process terminates) */
181         SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
182         SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
183     }
184 
185     /* Get screen info */
186     memset(&Pt, 0, sizeof(Pt));
187     monitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
188     info.cbSize = sizeof(info);
189     GetMonitorInfoW(monitor, &info);
190     GetWindowRect(hDlg, &rcWindow);
191 
192     /*
193         If the coordination values are default then re-initialize using the specific formulas
194         to move the dialog at the bottom of the screen.
195     */
196     if (Globals.PosX == CW_USEDEFAULT && Globals.PosY == CW_USEDEFAULT)
197     {
198         Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
199         Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
200     }
201 
202     /*
203         Calculate the intersection of two rectangle sources (dialog and work desktop area).
204         If such sources do not intersect, then the dialog is deemed as "off screen".
205     */
206     if (IntersectRect(&rcDlgIntersect, &rcWindow, &info.rcWork) == 0)
207     {
208         Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
209         Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
210     }
211     else
212     {
213         /*
214             There's still some intersection but we're not for sure if it is sufficient (the dialog could also be partially hidden).
215             Therefore, check the remaining intersection if it's enough.
216         */
217         if (rcWindow.top < info.rcWork.top || rcWindow.left < info.rcWork.left || rcWindow.right > info.rcWork.right || rcWindow.bottom > info.rcWork.bottom)
218         {
219             Globals.PosX = (info.rcMonitor.left + info.rcMonitor.right - (rcWindow.right - rcWindow.left)) / 2;
220             Globals.PosY = info.rcMonitor.bottom - (rcWindow.bottom - rcWindow.top);
221         }
222     }
223 
224     /*
225         Place the window (with respective placement coordinates) as topmost, above
226         every window which are not on top or are at the bottom of the Z order.
227     */
228     if (Globals.bAlwaysOnTop)
229     {
230         CheckMenuItem(GetMenu(hDlg), IDM_ON_TOP, MF_BYCOMMAND | MF_CHECKED);
231         SetWindowPos(hDlg, HWND_TOPMOST, Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
232     }
233     else
234     {
235         CheckMenuItem(GetMenu(hDlg), IDM_ON_TOP, MF_BYCOMMAND | MF_UNCHECKED);
236         SetWindowPos(hDlg, HWND_NOTOPMOST, Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
237     }
238 
239     /* Set icon on visual buttons */
240     OSK_SetImage(SCAN_CODE_15, IDI_BACK);
241     OSK_SetImage(SCAN_CODE_16, IDI_TAB);
242     OSK_SetImage(SCAN_CODE_30, IDI_CAPS_LOCK);
243     OSK_SetImage(SCAN_CODE_43, IDI_RETURN);
244     OSK_SetImage(SCAN_CODE_44, IDI_SHIFT);
245     OSK_SetImage(SCAN_CODE_57, IDI_SHIFT);
246     OSK_SetImage(SCAN_CODE_127, IDI_REACTOS);
247     OSK_SetImage(SCAN_CODE_128, IDI_REACTOS);
248     OSK_SetImage(SCAN_CODE_129, IDI_MENU);
249     OSK_SetImage(SCAN_CODE_80, IDI_HOME);
250     OSK_SetImage(SCAN_CODE_85, IDI_PG_UP);
251     OSK_SetImage(SCAN_CODE_86, IDI_PG_DOWN);
252     OSK_SetImage(SCAN_CODE_79, IDI_LEFT);
253     OSK_SetImage(SCAN_CODE_83, IDI_TOP);
254     OSK_SetImage(SCAN_CODE_84, IDI_BOTTOM);
255     OSK_SetImage(SCAN_CODE_89, IDI_RIGHT);
256 
257     /* Create a green brush for leds */
258     Globals.hBrushGreenLed = CreateSolidBrush(RGB(0, 255, 0));
259 
260     /* Set a timer for periodics tasks */
261     Globals.iTimer = SetTimer(hDlg, 0, 50, NULL);
262 
263     return TRUE;
264 }
265 
266 /***********************************************************************
267  *
268  *           OSK_RestoreDlgPlacement
269  *
270  *  Restores the dialog placement
271  */
272 VOID OSK_RestoreDlgPlacement(HWND hDlg)
273 {
274     LoadSettings();
275     SetWindowPos(hDlg, (Globals.bAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST), Globals.PosX, Globals.PosY, 0, 0, SWP_NOSIZE);
276 }
277 
278 /***********************************************************************
279  *
280  *           OSK_DlgClose
281  *
282  *  Handling of WM_CLOSE
283  */
284 int OSK_DlgClose(void)
285 {
286     KillTimer(Globals.hMainWnd, Globals.iTimer);
287 
288     /* Release Ctrl, Shift, Alt keys */
289     OSK_ReleaseKey(SCAN_CODE_44); // Left shift
290     OSK_ReleaseKey(SCAN_CODE_57); // Right shift
291     OSK_ReleaseKey(SCAN_CODE_58); // Left ctrl
292     OSK_ReleaseKey(SCAN_CODE_60); // Left alt
293     OSK_ReleaseKey(SCAN_CODE_62); // Right alt
294     OSK_ReleaseKey(SCAN_CODE_64); // Right ctrl
295 
296     /* delete GDI objects */
297     if (Globals.hBrushGreenLed) DeleteObject(Globals.hBrushGreenLed);
298 
299     /* Save the application's settings on registry */
300     SaveSettings();
301 
302     return TRUE;
303 }
304 
305 /***********************************************************************
306  *
307  *           OSK_RefreshLEDKeys
308  *
309  *  Updates (invalidates) the LED icon resources then the respective
310  *  keys (Caps Lock, Scroll Lock or Num Lock) are being held down
311  */
312 VOID OSK_RefreshLEDKeys(VOID)
313 {
314     INT i;
315     BOOL bKeyIsPressed;
316 
317     for (i = 0; i < _countof(LedKey); i++)
318     {
319         bKeyIsPressed = (GetAsyncKeyState(LedKey[i].vKey) & 0x8000) != 0;
320         if (LedKey[i].bWasKeyPressed != bKeyIsPressed)
321         {
322             LedKey[i].bWasKeyPressed = bKeyIsPressed;
323             InvalidateRect(GetDlgItem(Globals.hMainWnd, LedKey[i].DlgResource), NULL, FALSE);
324         }
325     }
326 }
327 
328 /***********************************************************************
329  *
330  *           OSK_DlgTimer
331  *
332  *  Handling of WM_TIMER
333  */
334 int OSK_DlgTimer(void)
335 {
336     /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
337     HWND hWndActiveWindow;
338 
339     hWndActiveWindow = GetForegroundWindow();
340     if (hWndActiveWindow != NULL && hWndActiveWindow != Globals.hMainWnd)
341     {
342         Globals.hActiveWnd = hWndActiveWindow;
343     }
344 
345     /*
346         Update the LED key indicators accordingly to their state (if one
347         of the specific keys is held down).
348     */
349     OSK_RefreshLEDKeys();
350 
351     return TRUE;
352 }
353 
354 /***********************************************************************
355  *
356  *           OSK_DlgCommand
357  *
358  *  All handling of dialog command
359  */
360 BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl)
361 {
362     WORD ScanCode;
363     INPUT Input;
364     BOOL bExtendedKey;
365     BOOL bKeyDown;
366     BOOL bKeyUp;
367     LONG WindowStyle;
368     INT i;
369 
370     /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
371     if (Globals.hActiveWnd)
372     {
373         MSG msg;
374 
375         SetForegroundWindow(Globals.hActiveWnd);
376         while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
377         {
378             TranslateMessage(&msg);
379             DispatchMessageW(&msg);
380         }
381     }
382 
383     /* KeyDown and/or KeyUp ? */
384     WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
385     if ((WindowStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX)
386     {
387         /* 2-states key like Shift, Alt, Ctrl, ... */
388         if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
389         {
390             bKeyDown = TRUE;
391             bKeyUp = FALSE;
392         }
393         else
394         {
395             bKeyDown = FALSE;
396             bKeyUp = TRUE;
397         }
398     }
399     else
400     {
401         /* Other key */
402         bKeyDown = TRUE;
403         bKeyUp = TRUE;
404     }
405 
406     /* Get the key from dialog control key command */
407     ScanCode = wCommand;
408 
409     /*
410         The user could've pushed one of the key buttons of the dialog that
411         can trigger particular function toggling (Caps Lock, Num Lock or Scroll Lock). Update
412         (invalidate) the LED icon resources accordingly.
413     */
414     for (i = 0; i < _countof(LedKey); i++)
415     {
416         if (LedKey[i].wScanCode == ScanCode)
417         {
418             InvalidateRect(GetDlgItem(Globals.hMainWnd, LedKey[i].DlgResource), NULL, FALSE);
419         }
420     }
421 
422     /* Extended key ? */
423     if (ScanCode & 0x0200)
424         bExtendedKey = TRUE;
425     else
426         bExtendedKey = FALSE;
427     ScanCode &= 0xFF;
428 
429     /* Press and release the key */
430     if (bKeyDown)
431     {
432         Input.type = INPUT_KEYBOARD;
433         Input.ki.wVk = 0;
434         Input.ki.wScan = ScanCode;
435         Input.ki.time = GetTickCount();
436         Input.ki.dwExtraInfo = GetMessageExtraInfo();
437         Input.ki.dwFlags = KEYEVENTF_SCANCODE;
438         if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
439         SendInput(1, &Input, sizeof(Input));
440     }
441 
442     if (bKeyUp)
443     {
444         Input.type = INPUT_KEYBOARD;
445         Input.ki.wVk = 0;
446         Input.ki.wScan = ScanCode;
447         Input.ki.time = GetTickCount();
448         Input.ki.dwExtraInfo = GetMessageExtraInfo();
449         Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
450         if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
451         SendInput(1, &Input, sizeof(Input));
452     }
453 
454     /* Play the sound during clicking event (only if "Use Click Sound" menu option is ticked) */
455     if (Globals.bSoundClick)
456     {
457         PlaySoundW(MAKEINTRESOURCEW(IDI_SOUNDCLICK), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
458     }
459 
460     return TRUE;
461 }
462 
463 /***********************************************************************
464  *
465  *           OSK_ReleaseKey
466  *
467  *  Release the key of ID wCommand
468  */
469 BOOL OSK_ReleaseKey(WORD ScanCode)
470 {
471     INPUT Input;
472     BOOL bExtendedKey;
473     LONG WindowStyle;
474     HWND hWndControl;
475 
476     /* Is it a 2-states key ? */
477     hWndControl = GetDlgItem(Globals.hMainWnd, ScanCode);
478     WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
479     if ((WindowStyle & BS_AUTOCHECKBOX) != BS_AUTOCHECKBOX) return FALSE;
480 
481     /* Is the key down ? */
482     if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
483 
484     /* Extended key ? */
485     if (ScanCode & 0x0200)
486         bExtendedKey = TRUE;
487     else
488         bExtendedKey = FALSE;
489     ScanCode &= 0xFF;
490 
491     /* Release the key */
492     Input.type = INPUT_KEYBOARD;
493     Input.ki.wVk = 0;
494     Input.ki.wScan = ScanCode;
495     Input.ki.time = GetTickCount();
496     Input.ki.dwExtraInfo = GetMessageExtraInfo();
497     Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
498     if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
499     SendInput(1, &Input, sizeof(Input));
500 
501     return TRUE;
502 }
503 
504 /***********************************************************************
505  *
506  *           OSK_ThemeHandler
507  *
508  *  Function helper which handles theme drawing of controls
509  */
510 LRESULT APIENTRY OSK_ThemeHandler(HWND hDlg, NMCUSTOMDRAW *pNmDraw)
511 {
512     HTHEME hTheme;
513     HWND hDlgButtonCtrl;
514     LRESULT Ret;
515     INT iState = PBS_NORMAL;
516 
517     /* Retrieve the theme handle for the button controls */
518     hDlgButtonCtrl = pNmDraw->hdr.hwndFrom;
519     hTheme = GetWindowTheme(hDlgButtonCtrl);
520 
521     /*
522         Begin the painting procedures if we retrieved
523         the theme for control buttons of the dialog.
524     */
525     if (hTheme)
526     {
527         /* Obtain CDDS drawing stages */
528         switch (pNmDraw->dwDrawStage)
529         {
530             case CDDS_PREPAINT:
531             {
532                 /*
533                     The button could be either in normal state or pushed.
534                     Retrieve its state and save to a variable.
535                 */
536                 if (pNmDraw->uItemState & CDIS_DEFAULT)
537                 {
538                     iState = PBS_DEFAULTED;
539                 }
540                 else if (pNmDraw->uItemState & CDIS_SELECTED)
541                 {
542                     iState = PBS_PRESSED;
543                 }
544                 else if (pNmDraw->uItemState & CDIS_HOT)
545                 {
546                     iState = PBS_HOT;
547                 }
548 
549                 if (IsThemeBackgroundPartiallyTransparent(hTheme, BP_PUSHBUTTON, iState))
550                 {
551                     /* Draw the application if the theme is transparent */
552                     DrawThemeParentBackground(hDlgButtonCtrl, pNmDraw->hdc, &pNmDraw->rc);
553                 }
554 
555                 /* Draw it */
556                 DrawThemeBackground(hTheme, pNmDraw->hdc, BP_PUSHBUTTON, iState, &pNmDraw->rc, NULL);
557 
558                 Ret = CDRF_SKIPDEFAULT;
559                 break;
560             }
561 
562             case CDDS_PREERASE:
563             {
564                 Ret = CDRF_DODEFAULT;
565                 break;
566             }
567 
568             default:
569                 Ret = CDRF_SKIPDEFAULT;
570                 break;
571         }
572     }
573     else
574     {
575         /* hTheme is NULL so bail right away */
576         Ret = CDRF_DODEFAULT;
577     }
578 
579     return Ret;
580 }
581 
582 /***********************************************************************
583  *
584  *       OSK_DlgProc
585  */
586 INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
587 {
588     switch (msg)
589     {
590         case WM_INITDIALOG:
591             OSK_DlgInitDialog(hDlg);
592             return TRUE;
593 
594         case WM_TIMER:
595             OSK_DlgTimer();
596             return TRUE;
597 
598         case WM_NOTIFY:
599             return OSK_ThemeHandler(hDlg, (LPNMCUSTOMDRAW)lParam);
600 
601         case WM_CTLCOLORSTATIC:
602             if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_NUM))
603             {
604                 if (GetKeyState(VK_NUMLOCK) & 0x0001)
605                     return (INT_PTR)Globals.hBrushGreenLed;
606                 else
607                     return (INT_PTR)GetStockObject(BLACK_BRUSH);
608             }
609             if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_CAPS))
610             {
611                 if (GetKeyState(VK_CAPITAL) & 0x0001)
612                     return (INT_PTR)Globals.hBrushGreenLed;
613                 else
614                     return (INT_PTR)GetStockObject(BLACK_BRUSH);
615             }
616             if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_SCROLL))
617             {
618                 if (GetKeyState(VK_SCROLL) & 0x0001)
619                     return (INT_PTR)Globals.hBrushGreenLed;
620                 else
621                     return (INT_PTR)GetStockObject(BLACK_BRUSH);
622             }
623             break;
624 
625         case WM_COMMAND:
626             switch (LOWORD(wParam))
627             {
628                 case IDCANCEL:
629                 {
630                     EndDialog(hDlg, FALSE);
631                     break;
632                 }
633 
634                 case IDM_EXIT:
635                 {
636                     EndDialog(hDlg, FALSE);
637                     break;
638                 }
639 
640                 case IDM_ENHANCED_KB:
641                 {
642                     if (!Globals.bIsEnhancedKeyboard)
643                     {
644                         /*
645                             The user attempted to switch to enhanced keyboard dialog type.
646                             Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
647                         */
648                         Globals.bIsEnhancedKeyboard = TRUE;
649                         EndDialog(hDlg, FALSE);
650                         SaveSettings();
651 
652                         /* Change the condition of enhanced keyboard item menu to checked */
653                         CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
654                         CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
655 
656                         /*
657                             Before creating the dialog box restore the coordinates. The user can
658                             move the dialog around before choosing a different dialog layout therefore
659                             we must create the dialog with the new coordinates.
660                         */
661                         OSK_RestoreDlgPlacement(hDlg);
662 
663                         /* Finally, display the dialog modal box with the enhanced keyboard dialog */
664                         DialogBoxW(Globals.hInstance,
665                                    MAKEINTRESOURCEW(MAIN_DIALOG_ENHANCED_KB),
666                                    GetDesktopWindow(),
667                                    OSK_DlgProc);
668                     }
669 
670                     break;
671                 }
672 
673                 case IDM_STANDARD_KB:
674                 {
675                     if (Globals.bIsEnhancedKeyboard)
676                     {
677                         /*
678                             The user attempted to switch to standard keyboard dialog type.
679                             Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
680                         */
681                         Globals.bIsEnhancedKeyboard = FALSE;
682                         EndDialog(hDlg, FALSE);
683                         SaveSettings();
684 
685                         /* Change the condition of standard keyboard item menu to checked */
686                         CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
687                         CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
688 
689                         /*
690                             Before creating the dialog box restore the coordinates. The user can
691                             move the dialog around before choosing a different dialog layout therefore
692                             we must create the dialog with the new coordinates.
693                         */
694                         OSK_RestoreDlgPlacement(hDlg);
695 
696                         /* Finally, display the dialog modal box with the standard keyboard dialog */
697                         DialogBoxW(Globals.hInstance,
698                                    MAKEINTRESOURCEW(MAIN_DIALOG_STANDARD_KB),
699                                    GetDesktopWindow(),
700                                    OSK_DlgProc);
701                     }
702 
703                     break;
704                 }
705 
706                 case IDM_CLICK_SOUND:
707                 {
708                     /*
709                         This case is triggered when the user attempts to click on the menu item. Before doing anything,
710                         we must check the condition state of such menu item so that we can tick/untick the menu item accordingly.
711                     */
712                     if (!Globals.bSoundClick)
713                     {
714                         Globals.bSoundClick = TRUE;
715                         CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
716                     }
717                     else
718                     {
719                         Globals.bSoundClick = FALSE;
720                         CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_UNCHECKED);
721                     }
722 
723                     break;
724                 }
725 
726                 case IDM_ON_TOP:
727                 {
728                     /*
729                         Check the condition state before disabling/enabling the menu
730                         item and change the topmost order.
731                     */
732                     if (!Globals.bAlwaysOnTop)
733                     {
734                         Globals.bAlwaysOnTop = TRUE;
735                         CheckMenuItem(GetMenu(hDlg), IDM_ON_TOP, MF_BYCOMMAND | MF_CHECKED);
736                         SetWindowPos(hDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
737                     }
738                     else
739                     {
740                         Globals.bAlwaysOnTop = FALSE;
741                         CheckMenuItem(GetMenu(hDlg), IDM_ON_TOP, MF_BYCOMMAND | MF_UNCHECKED);
742                         SetWindowPos(hDlg, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
743                     }
744 
745                     break;
746                 }
747 
748                 case IDM_ABOUT:
749                 {
750                     OSK_About();
751                     break;
752                 }
753 
754                 default:
755                     OSK_DlgCommand(wParam, (HWND)lParam);
756                     break;
757             }
758             break;
759 
760         case WM_THEMECHANGED:
761             /* Redraw the dialog (and its control buttons) using the new theme */
762             InvalidateRect(hDlg, NULL, FALSE);
763             break;
764 
765         case WM_CLOSE:
766             OSK_DlgClose();
767             break;
768     }
769 
770     return 0;
771 }
772 
773 /***********************************************************************
774  *
775  *       WinMain
776  */
777 int WINAPI wWinMain(HINSTANCE hInstance,
778                     HINSTANCE prev,
779                     LPWSTR cmdline,
780                     int show)
781 {
782     HANDLE hMutex;
783     DWORD dwError;
784     INT LayoutResource;
785     INITCOMMONCONTROLSEX iccex;
786 
787     UNREFERENCED_PARAMETER(prev);
788     UNREFERENCED_PARAMETER(cmdline);
789     UNREFERENCED_PARAMETER(show);
790 
791     /*
792         Obtain a mutex for the program. This will ensure that
793         the program is launched only once.
794     */
795     hMutex = CreateMutexW(NULL, FALSE, L"OSKRunning");
796 
797     if (hMutex)
798     {
799         /* Check if there's already a mutex for the program */
800         dwError = GetLastError();
801 
802         if (dwError == ERROR_ALREADY_EXISTS)
803         {
804             /*
805                 A mutex with the object name has been created previously.
806                 Therefore, another instance is already running.
807             */
808             DPRINT("wWinMain(): Failed to create a mutex! The program instance is already running.\n");
809             CloseHandle(hMutex);
810             return 0;
811         }
812     }
813 
814     /* Load the common controls */
815     iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
816     iccex.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
817     InitCommonControlsEx(&iccex);
818 
819     ZeroMemory(&Globals, sizeof(Globals));
820     Globals.hInstance = hInstance;
821 
822     /* Load the application's settings from the registry */
823     LoadSettings();
824 
825     /* If the member of the struct (bShowWarning) is set then display the dialog box */
826     if (Globals.bShowWarning)
827     {
828         /* If for whatever reason the thread fails to be created then handle the dialog box in main thread... */
829         if (CreateThread(NULL, 0, OSK_WarningDlgThread, (PVOID)Globals.hInstance, 0, NULL) == NULL)
830         {
831             DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
832         }
833     }
834 
835     /* Before initializing the dialog execution, check if the chosen keyboard type is standard or enhanced */
836     if (Globals.bIsEnhancedKeyboard)
837     {
838         LayoutResource = MAIN_DIALOG_ENHANCED_KB;
839     }
840     else
841     {
842         LayoutResource = MAIN_DIALOG_STANDARD_KB;
843     }
844 
845     /* Create the modal box based on the configuration registry */
846     DialogBoxW(hInstance,
847                MAKEINTRESOURCEW(LayoutResource),
848                GetDesktopWindow(),
849                OSK_DlgProc);
850 
851     /* Delete the mutex */
852     if (hMutex)
853     {
854         CloseHandle(hMutex);
855     }
856 
857     return 0;
858 }
859 
860 /* EOF */
861