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