[OSK] Implement standard/enhanced keyboard handler (#1338)
[reactos.git] / base / applications / osk / main.c
1 /*
2 * PROJECT: ReactOS On-Screen Keyboard
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: base/applications/osk/main.c
5 * PURPOSE: On-screen keyboard.
6 * PROGRAMMERS: Denis ROBERT
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "osk.h"
12 #include "settings.h"
13
14 /* GLOBALS ********************************************************************/
15
16 OSK_GLOBALS Globals;
17
18 /* Functions */
19 int OSK_SetImage(int IdDlgItem, int IdResource);
20 int OSK_DlgInitDialog(HWND hDlg);
21 int OSK_DlgClose(void);
22 int OSK_DlgTimer(void);
23 BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl);
24 BOOL OSK_ReleaseKey(WORD ScanCode);
25
26 INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);
27 int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int);
28
29 /* FUNCTIONS ******************************************************************/
30
31 /***********************************************************************
32 *
33 * OSK_SetImage
34 *
35 * Set an image on a button
36 */
37 int OSK_SetImage(int IdDlgItem, int IdResource)
38 {
39 HICON hIcon;
40 HWND hWndItem;
41
42 hIcon = (HICON)LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IdResource),
43 IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
44 if (hIcon == NULL)
45 return FALSE;
46
47 hWndItem = GetDlgItem(Globals.hMainWnd, IdDlgItem);
48 if (hWndItem == NULL)
49 {
50 DestroyIcon(hIcon);
51 return FALSE;
52 }
53
54 SendMessageW(hWndItem, BM_SETIMAGE, (WPARAM)IMAGE_ICON, (LPARAM)hIcon);
55
56 /* The system automatically deletes these resources when the process that created them terminates (MSDN) */
57
58 return TRUE;
59 }
60
61 /***********************************************************************
62 *
63 * OSK_WarningProc
64 *
65 * Function handler for the warning dialog box on startup
66 */
67 INT_PTR CALLBACK OSK_WarningProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
68 {
69 UNREFERENCED_PARAMETER(lParam);
70
71 switch (Msg)
72 {
73 case WM_INITDIALOG:
74 {
75 return TRUE;
76 }
77
78 case WM_COMMAND:
79 {
80 switch (LOWORD(wParam))
81 {
82 case IDC_SHOWWARNINGCHECK:
83 {
84 Globals.bShowWarning = !IsDlgButtonChecked(hDlg, IDC_SHOWWARNINGCHECK);
85 return TRUE;
86 }
87
88 case IDOK:
89 case IDCANCEL:
90 {
91 EndDialog(hDlg, LOWORD(wParam));
92 return TRUE;
93 }
94 }
95 break;
96 }
97 }
98
99 return FALSE;
100 }
101
102 /***********************************************************************
103 *
104 * OSK_About
105 *
106 * Initializes the "About" dialog box
107 */
108 VOID OSK_About(VOID)
109 {
110 WCHAR szTitle[MAX_BUFF];
111 WCHAR szAuthors[MAX_BUFF];
112 HICON OSKIcon;
113
114 /* Load the icon */
115 OSKIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE);
116
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));
120
121 /* Finally, execute the "About" dialog by using the Shell routine */
122 ShellAboutW(Globals.hMainWnd, szTitle, szAuthors, OSKIcon);
123
124 /* Once done, destroy the icon */
125 DestroyIcon(OSKIcon);
126 }
127
128
129 /***********************************************************************
130 *
131 * OSK_DlgInitDialog
132 *
133 * Handling of WM_INITDIALOG
134 */
135 int OSK_DlgInitDialog(HWND hDlg)
136 {
137 HICON hIcon, hIconSm;
138 HMONITOR monitor;
139 MONITORINFO info;
140 POINT Pt;
141 RECT rcWindow;
142
143 /* Save handle */
144 Globals.hMainWnd = hDlg;
145
146 /* Check the checked menu item before displaying the modal box */
147 if (Globals.bIsEnhancedKeyboard)
148 {
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);
152 }
153 else
154 {
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);
158 }
159
160 /* Set the application's icon */
161 hIcon = LoadImageW(Globals.hInstance, MAKEINTRESOURCEW(IDI_OSK), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
162 hIconSm = CopyImage(hIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_COPYFROMRESOURCE);
163 if (hIcon || hIconSm)
164 {
165 /* Set the window icons (they are deleted when the process terminates) */
166 SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
167 SendMessageW(Globals.hMainWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm);
168 }
169
170 /* Get screen info */
171 memset(&Pt, 0, sizeof(Pt));
172 monitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
173 info.cbSize = sizeof(info);
174 GetMonitorInfoW(monitor, &info);
175
176 /* Move the dialog on the bottom of main screen */
177 GetWindowRect(hDlg, &rcWindow);
178 MoveWindow(hDlg,
179 (info.rcMonitor.left + info.rcMonitor.right) / 2 - // Center of screen
180 (rcWindow.right - rcWindow.left) / 2, // - half size of dialog
181 info.rcMonitor.bottom - // Bottom of screen
182 (rcWindow.bottom - rcWindow.top), // - size of window
183 rcWindow.right - rcWindow.left, // Width
184 rcWindow.bottom - rcWindow.top, // Height
185 TRUE);
186
187 /* Set icon on visual buttons */
188 OSK_SetImage(SCAN_CODE_15, IDI_BACK);
189 OSK_SetImage(SCAN_CODE_16, IDI_TAB);
190 OSK_SetImage(SCAN_CODE_30, IDI_CAPS_LOCK);
191 OSK_SetImage(SCAN_CODE_43, IDI_RETURN);
192 OSK_SetImage(SCAN_CODE_44, IDI_SHIFT);
193 OSK_SetImage(SCAN_CODE_57, IDI_SHIFT);
194 OSK_SetImage(SCAN_CODE_127, IDI_REACTOS);
195 OSK_SetImage(SCAN_CODE_128, IDI_REACTOS);
196 OSK_SetImage(SCAN_CODE_129, IDI_MENU);
197 OSK_SetImage(SCAN_CODE_80, IDI_HOME);
198 OSK_SetImage(SCAN_CODE_85, IDI_PG_UP);
199 OSK_SetImage(SCAN_CODE_86, IDI_PG_DOWN);
200 OSK_SetImage(SCAN_CODE_79, IDI_LEFT);
201 OSK_SetImage(SCAN_CODE_83, IDI_TOP);
202 OSK_SetImage(SCAN_CODE_84, IDI_BOTTOM);
203 OSK_SetImage(SCAN_CODE_89, IDI_RIGHT);
204
205 /* Create a green brush for leds */
206 Globals.hBrushGreenLed = CreateSolidBrush(RGB(0, 255, 0));
207
208 /* Set a timer for periodics tasks */
209 Globals.iTimer = SetTimer(hDlg, 0, 200, NULL);
210
211 return TRUE;
212 }
213
214 /***********************************************************************
215 *
216 * OSK_DlgClose
217 *
218 * Handling of WM_CLOSE
219 */
220 int OSK_DlgClose(void)
221 {
222 KillTimer(Globals.hMainWnd, Globals.iTimer);
223
224 /* Release Ctrl, Shift, Alt keys */
225 OSK_ReleaseKey(SCAN_CODE_44); // Left shift
226 OSK_ReleaseKey(SCAN_CODE_57); // Right shift
227 OSK_ReleaseKey(SCAN_CODE_58); // Left ctrl
228 OSK_ReleaseKey(SCAN_CODE_60); // Left alt
229 OSK_ReleaseKey(SCAN_CODE_62); // Right alt
230 OSK_ReleaseKey(SCAN_CODE_64); // Right ctrl
231
232 /* delete GDI objects */
233 if (Globals.hBrushGreenLed) DeleteObject(Globals.hBrushGreenLed);
234
235 /* Save the settings to the registry hive */
236 SaveDataToRegistry();
237
238 return TRUE;
239 }
240
241 /***********************************************************************
242 *
243 * OSK_DlgTimer
244 *
245 * Handling of WM_TIMER
246 */
247 int OSK_DlgTimer(void)
248 {
249 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
250 HWND hWndActiveWindow;
251
252 hWndActiveWindow = GetForegroundWindow();
253 if (hWndActiveWindow != NULL && hWndActiveWindow != Globals.hMainWnd)
254 {
255 Globals.hActiveWnd = hWndActiveWindow;
256 }
257
258 /* Always redraw leds because it can be changed by the real keyboard) */
259 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_NUM), NULL, TRUE);
260 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_CAPS), NULL, TRUE);
261 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_SCROLL), NULL, TRUE);
262
263 return TRUE;
264 }
265
266 /***********************************************************************
267 *
268 * OSK_DlgCommand
269 *
270 * All handling of dialog command
271 */
272 BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl)
273 {
274 WORD ScanCode;
275 INPUT Input;
276 BOOL bExtendedKey;
277 BOOL bKeyDown;
278 BOOL bKeyUp;
279 LONG WindowStyle;
280
281 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
282 if (Globals.hActiveWnd)
283 {
284 MSG msg;
285
286 SetForegroundWindow(Globals.hActiveWnd);
287 while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
288 {
289 TranslateMessage(&msg);
290 DispatchMessageW(&msg);
291 }
292 }
293
294 /* KeyDown and/or KeyUp ? */
295 WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
296 if ((WindowStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX)
297 {
298 /* 2-states key like Shift, Alt, Ctrl, ... */
299 if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
300 {
301 bKeyDown = TRUE;
302 bKeyUp = FALSE;
303 }
304 else
305 {
306 bKeyDown = FALSE;
307 bKeyUp = TRUE;
308 }
309 }
310 else
311 {
312 /* Other key */
313 bKeyDown = TRUE;
314 bKeyUp = TRUE;
315 }
316
317 /* Extended key ? */
318 ScanCode = wCommand;
319 if (ScanCode & 0x0200)
320 bExtendedKey = TRUE;
321 else
322 bExtendedKey = FALSE;
323 ScanCode &= 0xFF;
324
325 /* Press and release the key */
326 if (bKeyDown)
327 {
328 Input.type = INPUT_KEYBOARD;
329 Input.ki.wVk = 0;
330 Input.ki.wScan = ScanCode;
331 Input.ki.time = GetTickCount();
332 Input.ki.dwExtraInfo = GetMessageExtraInfo();
333 Input.ki.dwFlags = KEYEVENTF_SCANCODE;
334 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
335 SendInput(1, &Input, sizeof(Input));
336 }
337
338 if (bKeyUp)
339 {
340 Input.type = INPUT_KEYBOARD;
341 Input.ki.wVk = 0;
342 Input.ki.wScan = ScanCode;
343 Input.ki.time = GetTickCount();
344 Input.ki.dwExtraInfo = GetMessageExtraInfo();
345 Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
346 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
347 SendInput(1, &Input, sizeof(Input));
348 }
349
350 return TRUE;
351 }
352
353 /***********************************************************************
354 *
355 * OSK_ReleaseKey
356 *
357 * Release the key of ID wCommand
358 */
359 BOOL OSK_ReleaseKey(WORD ScanCode)
360 {
361 INPUT Input;
362 BOOL bExtendedKey;
363 LONG WindowStyle;
364 HWND hWndControl;
365
366 /* Is it a 2-states key ? */
367 hWndControl = GetDlgItem(Globals.hMainWnd, ScanCode);
368 WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
369 if ((WindowStyle & BS_AUTOCHECKBOX) != BS_AUTOCHECKBOX) return FALSE;
370
371 /* Is the key down ? */
372 if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
373
374 /* Extended key ? */
375 if (ScanCode & 0x0200)
376 bExtendedKey = TRUE;
377 else
378 bExtendedKey = FALSE;
379 ScanCode &= 0xFF;
380
381 /* Release the key */
382 Input.type = INPUT_KEYBOARD;
383 Input.ki.wVk = 0;
384 Input.ki.wScan = ScanCode;
385 Input.ki.time = GetTickCount();
386 Input.ki.dwExtraInfo = GetMessageExtraInfo();
387 Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
388 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
389 SendInput(1, &Input, sizeof(Input));
390
391 return TRUE;
392 }
393
394 /***********************************************************************
395 *
396 * OSK_DlgProc
397 */
398 INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
399 {
400 switch (msg)
401 {
402 case WM_INITDIALOG:
403 OSK_DlgInitDialog(hDlg);
404 return TRUE;
405
406 case WM_TIMER:
407 OSK_DlgTimer();
408 return TRUE;
409
410 case WM_CTLCOLORSTATIC:
411 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_NUM))
412 {
413 if (GetKeyState(VK_NUMLOCK) & 0x0001)
414 return (INT_PTR)Globals.hBrushGreenLed;
415 else
416 return (INT_PTR)GetStockObject(BLACK_BRUSH);
417 }
418 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_CAPS))
419 {
420 if (GetKeyState(VK_CAPITAL) & 0x0001)
421 return (INT_PTR)Globals.hBrushGreenLed;
422 else
423 return (INT_PTR)GetStockObject(BLACK_BRUSH);
424 }
425 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_SCROLL))
426 {
427 if (GetKeyState(VK_SCROLL) & 0x0001)
428 return (INT_PTR)Globals.hBrushGreenLed;
429 else
430 return (INT_PTR)GetStockObject(BLACK_BRUSH);
431 }
432 break;
433
434 case WM_COMMAND:
435 switch (LOWORD(wParam))
436 {
437 case IDCANCEL:
438 {
439 EndDialog(hDlg, FALSE);
440 break;
441 }
442
443 case IDM_EXIT:
444 {
445 EndDialog(hDlg, FALSE);
446 break;
447 }
448
449 case IDM_ENHANCED_KB:
450 {
451 if (!Globals.bIsEnhancedKeyboard)
452 {
453 /*
454 The user attempted to switch to enhanced keyboard dialog type.
455 Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
456 */
457 Globals.bIsEnhancedKeyboard = TRUE;
458 EndDialog(hDlg, FALSE);
459 SaveDataToRegistry();
460
461 /* Change the condition of enhanced keyboard item menu to checked */
462 CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
463 CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
464
465 /* Finally, display the dialog modal box with the enhanced keyboard dialog */
466 DialogBoxW(Globals.hInstance,
467 MAKEINTRESOURCEW(MAIN_DIALOG_ENHANCED_KB),
468 GetDesktopWindow(),
469 OSK_DlgProc);
470 }
471
472 break;
473 }
474
475 case IDM_STANDARD_KB:
476 {
477 if (Globals.bIsEnhancedKeyboard)
478 {
479 /*
480 The user attempted to switch to standard keyboard dialog type.
481 Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
482 */
483 Globals.bIsEnhancedKeyboard = FALSE;
484 EndDialog(hDlg, FALSE);
485 SaveDataToRegistry();
486
487 /* Change the condition of standard keyboard item menu to checked */
488 CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
489 CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
490
491 /* Finally, display the dialog modal box with the standard keyboard dialog */
492 DialogBoxW(Globals.hInstance,
493 MAKEINTRESOURCEW(MAIN_DIALOG_STANDARD_KB),
494 GetDesktopWindow(),
495 OSK_DlgProc);
496 }
497
498 break;
499 }
500
501 case IDM_ABOUT:
502 {
503 OSK_About();
504 break;
505 }
506
507 default:
508 OSK_DlgCommand(wParam, (HWND)lParam);
509 break;
510 }
511 break;
512
513 case WM_CLOSE:
514 OSK_DlgClose();
515 break;
516 }
517
518 return 0;
519 }
520
521 /***********************************************************************
522 *
523 * WinMain
524 */
525 int WINAPI wWinMain(HINSTANCE hInstance,
526 HINSTANCE prev,
527 LPWSTR cmdline,
528 int show)
529 {
530 HANDLE hMutex;
531 INT LayoutResource;
532
533 UNREFERENCED_PARAMETER(prev);
534 UNREFERENCED_PARAMETER(cmdline);
535 UNREFERENCED_PARAMETER(show);
536
537 ZeroMemory(&Globals, sizeof(Globals));
538 Globals.hInstance = hInstance;
539
540 /* Load the settings from the registry hive */
541 LoadDataFromRegistry();
542
543 /* If the member of the struct (bShowWarning) is set then display the dialog box */
544 if (Globals.bShowWarning)
545 {
546 DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
547 }
548
549 /* Before initializing the dialog execution, check if the chosen keyboard type is standard or enhanced */
550 if (Globals.bIsEnhancedKeyboard)
551 {
552 LayoutResource = MAIN_DIALOG_ENHANCED_KB;
553 }
554 else
555 {
556 LayoutResource = MAIN_DIALOG_STANDARD_KB;
557 }
558
559 /* Rry to open a mutex for a single instance */
560 hMutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, L"osk");
561
562 if (!hMutex)
563 {
564 /* Mutex doesn\92t exist. This is the first instance so create the mutex. */
565 hMutex = CreateMutexW(NULL, FALSE, L"osk");
566
567 /* Create the modal box based on the configuration registry */
568 DialogBoxW(hInstance,
569 MAKEINTRESOURCEW(LayoutResource),
570 GetDesktopWindow(),
571 OSK_DlgProc);
572
573 /* Delete the mutex */
574 if (hMutex) CloseHandle(hMutex);
575 }
576 else
577 {
578 /* Programme already launched */
579
580 /* Delete the mutex */
581 CloseHandle(hMutex);
582
583 ExitProcess(0);
584 }
585
586 return 0;
587 }
588
589 /* EOF */