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