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