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