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)
9 /* INCLUDES *******************************************************************/
14 /* GLOBALS ********************************************************************/
19 int OSK_SetImage(int IdDlgItem
, int IdResource
);
20 int OSK_DlgInitDialog(HWND hDlg
);
21 int OSK_DlgClose(void);
22 int OSK_DlgTimer(void);
23 BOOL
OSK_DlgCommand(WPARAM wCommand
, HWND hWndControl
);
24 BOOL
OSK_ReleaseKey(WORD ScanCode
);
26 INT_PTR APIENTRY
OSK_DlgProc(HWND hDlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
);
27 int WINAPI
wWinMain(HINSTANCE
, HINSTANCE
, LPWSTR
, int);
29 /* FUNCTIONS ******************************************************************/
31 /***********************************************************************
35 * Set an image on a button
37 int OSK_SetImage(int IdDlgItem
, int IdResource
)
42 hIcon
= (HICON
)LoadImageW(Globals
.hInstance
, MAKEINTRESOURCEW(IdResource
),
43 IMAGE_ICON
, 16, 16, LR_DEFAULTCOLOR
);
47 hWndItem
= GetDlgItem(Globals
.hMainWnd
, IdDlgItem
);
54 SendMessageW(hWndItem
, BM_SETIMAGE
, (WPARAM
)IMAGE_ICON
, (LPARAM
)hIcon
);
56 /* The system automatically deletes these resources when the process that created them terminates (MSDN) */
61 /***********************************************************************
65 * Function handler for the warning dialog box on startup
67 INT_PTR CALLBACK
OSK_WarningProc(HWND hDlg
, UINT Msg
, WPARAM wParam
, LPARAM lParam
)
69 UNREFERENCED_PARAMETER(lParam
);
80 switch (LOWORD(wParam
))
82 case IDC_SHOWWARNINGCHECK
:
84 Globals
.bShowWarning
= !IsDlgButtonChecked(hDlg
, IDC_SHOWWARNINGCHECK
);
91 EndDialog(hDlg
, LOWORD(wParam
));
102 /***********************************************************************
106 * Initializes the "About" dialog box
110 WCHAR szTitle
[MAX_BUFF
];
111 WCHAR szAuthors
[MAX_BUFF
];
115 OSKIcon
= LoadImageW(Globals
.hInstance
, MAKEINTRESOURCEW(IDI_OSK
), IMAGE_ICON
, 0, 0, LR_DEFAULTSIZE
);
117 /* Load the strings into the "About" dialog */
118 LoadStringW(Globals
.hInstance
, STRING_OSK
, szTitle
, countof(szTitle
));
119 LoadStringW(Globals
.hInstance
, STRING_AUTHORS
, szAuthors
, countof(szAuthors
));
121 /* Finally, execute the "About" dialog by using the Shell routine */
122 ShellAboutW(Globals
.hMainWnd
, szTitle
, szAuthors
, OSKIcon
);
124 /* Once done, destroy the icon */
125 DestroyIcon(OSKIcon
);
129 /***********************************************************************
133 * Handling of WM_INITDIALOG
135 int OSK_DlgInitDialog(HWND hDlg
)
137 HICON hIcon
, hIconSm
;
141 RECT rcWindow
, rcDlgIntersect
;
144 Globals
.hMainWnd
= hDlg
;
146 /* Check the checked menu item before displaying the modal box */
147 if (Globals
.bIsEnhancedKeyboard
)
149 /* Enhanced keyboard dialog chosen, set the respective menu item as checked */
150 CheckMenuItem(GetMenu(hDlg
), IDM_ENHANCED_KB
, MF_BYCOMMAND
| MF_CHECKED
);
151 CheckMenuItem(GetMenu(hDlg
), IDM_STANDARD_KB
, MF_BYCOMMAND
| MF_UNCHECKED
);
155 /* Standard keyboard dialog chosen, set the respective menu item as checked */
156 CheckMenuItem(GetMenu(hDlg
), IDM_STANDARD_KB
, MF_BYCOMMAND
| MF_CHECKED
);
157 CheckMenuItem(GetMenu(hDlg
), IDM_ENHANCED_KB
, MF_BYCOMMAND
| MF_UNCHECKED
);
160 /* Check if the "Click Sound" option was chosen before (and if so, then tick the menu item) */
161 if (Globals
.bSoundClick
)
163 CheckMenuItem(GetMenu(hDlg
), IDM_CLICK_SOUND
, MF_BYCOMMAND
| MF_CHECKED
);
166 /* Set the application's icon */
167 hIcon
= LoadImageW(Globals
.hInstance
, MAKEINTRESOURCEW(IDI_OSK
), IMAGE_ICON
, 0, 0, LR_SHARED
| LR_DEFAULTSIZE
);
168 hIconSm
= CopyImage(hIcon
, IMAGE_ICON
, GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), LR_COPYFROMRESOURCE
);
169 if (hIcon
|| hIconSm
)
171 /* Set the window icons (they are deleted when the process terminates) */
172 SendMessageW(Globals
.hMainWnd
, WM_SETICON
, ICON_BIG
, (LPARAM
)hIcon
);
173 SendMessageW(Globals
.hMainWnd
, WM_SETICON
, ICON_SMALL
, (LPARAM
)hIconSm
);
176 /* Get screen info */
177 memset(&Pt
, 0, sizeof(Pt
));
178 monitor
= MonitorFromPoint(Pt
, MONITOR_DEFAULTTOPRIMARY
);
179 info
.cbSize
= sizeof(info
);
180 GetMonitorInfoW(monitor
, &info
);
181 GetWindowRect(hDlg
, &rcWindow
);
184 If the coordination values are default then re-initialize using the specific formulas
185 to move the dialog at the bottom of the screen.
187 if (Globals
.PosX
== CW_USEDEFAULT
&& Globals
.PosY
== CW_USEDEFAULT
)
189 Globals
.PosX
= (info
.rcMonitor
.left
+ info
.rcMonitor
.right
- (rcWindow
.right
- rcWindow
.left
)) / 2;
190 Globals
.PosY
= info
.rcMonitor
.bottom
- (rcWindow
.bottom
- rcWindow
.top
);
194 Calculate the intersection of two rectangle sources (dialog and work desktop area).
195 If such sources do not intersect, then the dialog is deemed as "off screen".
197 if (IntersectRect(&rcDlgIntersect
, &rcWindow
, &info
.rcWork
) == 0)
199 Globals
.PosX
= (info
.rcMonitor
.left
+ info
.rcMonitor
.right
- (rcWindow
.right
- rcWindow
.left
)) / 2;
200 Globals
.PosY
= info
.rcMonitor
.bottom
- (rcWindow
.bottom
- rcWindow
.top
);
205 There's still some intersection but we're not for sure if it is sufficient (the dialog could also be partially hidden).
206 Therefore, check the remaining intersection if it's enough.
208 if (rcWindow
.top
< info
.rcWork
.top
|| rcWindow
.left
< info
.rcWork
.left
|| rcWindow
.right
> info
.rcWork
.right
|| rcWindow
.bottom
> info
.rcWork
.bottom
)
210 Globals
.PosX
= (info
.rcMonitor
.left
+ info
.rcMonitor
.right
- (rcWindow
.right
- rcWindow
.left
)) / 2;
211 Globals
.PosY
= info
.rcMonitor
.bottom
- (rcWindow
.bottom
- rcWindow
.top
);
215 /* Move the dialog according to the placement coordination */
216 SetWindowPos(hDlg
, HWND_TOP
, Globals
.PosX
, Globals
.PosY
, 0, 0, SWP_NOSIZE
);
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
);
236 /* Create a green brush for leds */
237 Globals
.hBrushGreenLed
= CreateSolidBrush(RGB(0, 255, 0));
239 /* Set a timer for periodics tasks */
240 Globals
.iTimer
= SetTimer(hDlg
, 0, 200, NULL
);
245 /***********************************************************************
249 * Handling of WM_CLOSE
251 int OSK_DlgClose(void)
253 KillTimer(Globals
.hMainWnd
, Globals
.iTimer
);
255 /* Release Ctrl, Shift, Alt keys */
256 OSK_ReleaseKey(SCAN_CODE_44
); // Left shift
257 OSK_ReleaseKey(SCAN_CODE_57
); // Right shift
258 OSK_ReleaseKey(SCAN_CODE_58
); // Left ctrl
259 OSK_ReleaseKey(SCAN_CODE_60
); // Left alt
260 OSK_ReleaseKey(SCAN_CODE_62
); // Right alt
261 OSK_ReleaseKey(SCAN_CODE_64
); // Right ctrl
263 /* delete GDI objects */
264 if (Globals
.hBrushGreenLed
) DeleteObject(Globals
.hBrushGreenLed
);
266 /* Save the settings to the registry hive */
267 SaveDataToRegistry();
272 /***********************************************************************
276 * Handling of WM_TIMER
278 int OSK_DlgTimer(void)
280 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
281 HWND hWndActiveWindow
;
283 hWndActiveWindow
= GetForegroundWindow();
284 if (hWndActiveWindow
!= NULL
&& hWndActiveWindow
!= Globals
.hMainWnd
)
286 Globals
.hActiveWnd
= hWndActiveWindow
;
289 /* Always redraw leds because it can be changed by the real keyboard) */
290 InvalidateRect(GetDlgItem(Globals
.hMainWnd
, IDC_LED_NUM
), NULL
, TRUE
);
291 InvalidateRect(GetDlgItem(Globals
.hMainWnd
, IDC_LED_CAPS
), NULL
, TRUE
);
292 InvalidateRect(GetDlgItem(Globals
.hMainWnd
, IDC_LED_SCROLL
), NULL
, TRUE
);
297 /***********************************************************************
301 * All handling of dialog command
303 BOOL
OSK_DlgCommand(WPARAM wCommand
, HWND hWndControl
)
312 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
313 if (Globals
.hActiveWnd
)
317 SetForegroundWindow(Globals
.hActiveWnd
);
318 while (PeekMessageW(&msg
, 0, 0, 0, PM_REMOVE
))
320 TranslateMessage(&msg
);
321 DispatchMessageW(&msg
);
325 /* KeyDown and/or KeyUp ? */
326 WindowStyle
= GetWindowLongW(hWndControl
, GWL_STYLE
);
327 if ((WindowStyle
& BS_AUTOCHECKBOX
) == BS_AUTOCHECKBOX
)
329 /* 2-states key like Shift, Alt, Ctrl, ... */
330 if (SendMessageW(hWndControl
, BM_GETCHECK
, 0, 0) == BST_CHECKED
)
350 if (ScanCode
& 0x0200)
353 bExtendedKey
= FALSE
;
356 /* Press and release the key */
359 Input
.type
= INPUT_KEYBOARD
;
361 Input
.ki
.wScan
= ScanCode
;
362 Input
.ki
.time
= GetTickCount();
363 Input
.ki
.dwExtraInfo
= GetMessageExtraInfo();
364 Input
.ki
.dwFlags
= KEYEVENTF_SCANCODE
;
365 if (bExtendedKey
) Input
.ki
.dwFlags
|= KEYEVENTF_EXTENDEDKEY
;
366 SendInput(1, &Input
, sizeof(Input
));
371 Input
.type
= INPUT_KEYBOARD
;
373 Input
.ki
.wScan
= ScanCode
;
374 Input
.ki
.time
= GetTickCount();
375 Input
.ki
.dwExtraInfo
= GetMessageExtraInfo();
376 Input
.ki
.dwFlags
= KEYEVENTF_SCANCODE
| KEYEVENTF_KEYUP
;
377 if (bExtendedKey
) Input
.ki
.dwFlags
|= KEYEVENTF_EXTENDEDKEY
;
378 SendInput(1, &Input
, sizeof(Input
));
381 /* Play the sound during clicking event (only if "Use Click Sound" menu option is ticked) */
382 if (Globals
.bSoundClick
)
384 PlaySoundW(MAKEINTRESOURCEW(IDI_SOUNDCLICK
), GetModuleHandle(NULL
), SND_RESOURCE
| SND_ASYNC
);
390 /***********************************************************************
394 * Release the key of ID wCommand
396 BOOL
OSK_ReleaseKey(WORD ScanCode
)
403 /* Is it a 2-states key ? */
404 hWndControl
= GetDlgItem(Globals
.hMainWnd
, ScanCode
);
405 WindowStyle
= GetWindowLongW(hWndControl
, GWL_STYLE
);
406 if ((WindowStyle
& BS_AUTOCHECKBOX
) != BS_AUTOCHECKBOX
) return FALSE
;
408 /* Is the key down ? */
409 if (SendMessageW(hWndControl
, BM_GETCHECK
, 0, 0) != BST_CHECKED
) return TRUE
;
412 if (ScanCode
& 0x0200)
415 bExtendedKey
= FALSE
;
418 /* Release the key */
419 Input
.type
= INPUT_KEYBOARD
;
421 Input
.ki
.wScan
= ScanCode
;
422 Input
.ki
.time
= GetTickCount();
423 Input
.ki
.dwExtraInfo
= GetMessageExtraInfo();
424 Input
.ki
.dwFlags
= KEYEVENTF_SCANCODE
| KEYEVENTF_KEYUP
;
425 if (bExtendedKey
) Input
.ki
.dwFlags
|= KEYEVENTF_EXTENDEDKEY
;
426 SendInput(1, &Input
, sizeof(Input
));
431 /***********************************************************************
435 INT_PTR APIENTRY
OSK_DlgProc(HWND hDlg
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
440 OSK_DlgInitDialog(hDlg
);
447 case WM_CTLCOLORSTATIC
:
448 if ((HWND
)lParam
== GetDlgItem(hDlg
, IDC_LED_NUM
))
450 if (GetKeyState(VK_NUMLOCK
) & 0x0001)
451 return (INT_PTR
)Globals
.hBrushGreenLed
;
453 return (INT_PTR
)GetStockObject(BLACK_BRUSH
);
455 if ((HWND
)lParam
== GetDlgItem(hDlg
, IDC_LED_CAPS
))
457 if (GetKeyState(VK_CAPITAL
) & 0x0001)
458 return (INT_PTR
)Globals
.hBrushGreenLed
;
460 return (INT_PTR
)GetStockObject(BLACK_BRUSH
);
462 if ((HWND
)lParam
== GetDlgItem(hDlg
, IDC_LED_SCROLL
))
464 if (GetKeyState(VK_SCROLL
) & 0x0001)
465 return (INT_PTR
)Globals
.hBrushGreenLed
;
467 return (INT_PTR
)GetStockObject(BLACK_BRUSH
);
472 switch (LOWORD(wParam
))
476 EndDialog(hDlg
, FALSE
);
482 EndDialog(hDlg
, FALSE
);
486 case IDM_ENHANCED_KB
:
488 if (!Globals
.bIsEnhancedKeyboard
)
491 The user attempted to switch to enhanced keyboard dialog type.
492 Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
494 Globals
.bIsEnhancedKeyboard
= TRUE
;
495 EndDialog(hDlg
, FALSE
);
496 SaveDataToRegistry();
498 /* Change the condition of enhanced keyboard item menu to checked */
499 CheckMenuItem(GetMenu(hDlg
), IDM_ENHANCED_KB
, MF_BYCOMMAND
| MF_CHECKED
);
500 CheckMenuItem(GetMenu(hDlg
), IDM_STANDARD_KB
, MF_BYCOMMAND
| MF_UNCHECKED
);
502 /* Finally, display the dialog modal box with the enhanced keyboard dialog */
503 DialogBoxW(Globals
.hInstance
,
504 MAKEINTRESOURCEW(MAIN_DIALOG_ENHANCED_KB
),
512 case IDM_STANDARD_KB
:
514 if (Globals
.bIsEnhancedKeyboard
)
517 The user attempted to switch to standard keyboard dialog type.
518 Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
520 Globals
.bIsEnhancedKeyboard
= FALSE
;
521 EndDialog(hDlg
, FALSE
);
522 SaveDataToRegistry();
524 /* Change the condition of standard keyboard item menu to checked */
525 CheckMenuItem(GetMenu(hDlg
), IDM_ENHANCED_KB
, MF_BYCOMMAND
| MF_UNCHECKED
);
526 CheckMenuItem(GetMenu(hDlg
), IDM_STANDARD_KB
, MF_BYCOMMAND
| MF_CHECKED
);
528 /* Finally, display the dialog modal box with the standard keyboard dialog */
529 DialogBoxW(Globals
.hInstance
,
530 MAKEINTRESOURCEW(MAIN_DIALOG_STANDARD_KB
),
538 case IDM_CLICK_SOUND
:
541 This case is triggered when the user attempts to click on the menu item. Before doing anything,
542 we must check the condition state of such menu item so that we can tick/untick the menu item accordingly.
544 if (!Globals
.bSoundClick
)
546 Globals
.bSoundClick
= TRUE
;
547 CheckMenuItem(GetMenu(hDlg
), IDM_CLICK_SOUND
, MF_BYCOMMAND
| MF_CHECKED
);
551 Globals
.bSoundClick
= FALSE
;
552 CheckMenuItem(GetMenu(hDlg
), IDM_CLICK_SOUND
, MF_BYCOMMAND
| MF_UNCHECKED
);
565 OSK_DlgCommand(wParam
, (HWND
)lParam
);
578 /***********************************************************************
582 int WINAPI
wWinMain(HINSTANCE hInstance
,
590 UNREFERENCED_PARAMETER(prev
);
591 UNREFERENCED_PARAMETER(cmdline
);
592 UNREFERENCED_PARAMETER(show
);
594 ZeroMemory(&Globals
, sizeof(Globals
));
595 Globals
.hInstance
= hInstance
;
597 /* Load the settings from the registry hive */
598 LoadDataFromRegistry();
600 /* If the member of the struct (bShowWarning) is set then display the dialog box */
601 if (Globals
.bShowWarning
)
603 DialogBoxW(Globals
.hInstance
, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK
), Globals
.hMainWnd
, OSK_WarningProc
);
606 /* Before initializing the dialog execution, check if the chosen keyboard type is standard or enhanced */
607 if (Globals
.bIsEnhancedKeyboard
)
609 LayoutResource
= MAIN_DIALOG_ENHANCED_KB
;
613 LayoutResource
= MAIN_DIALOG_STANDARD_KB
;
616 /* Rry to open a mutex for a single instance */
617 hMutex
= OpenMutexW(MUTEX_ALL_ACCESS
, FALSE
, L
"osk");
621 /* Mutex doesn't exist. This is the first instance so create the mutex. */
622 hMutex
= CreateMutexW(NULL
, FALSE
, L
"osk");
624 /* Create the modal box based on the configuration registry */
625 DialogBoxW(hInstance
,
626 MAKEINTRESOURCEW(LayoutResource
),
630 /* Delete the mutex */
631 if (hMutex
) CloseHandle(hMutex
);
635 /* Programme already launched */
637 /* Delete the mutex */