[OSK] Implement "Use Click Sound" feature
[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 /* Check if the "Click Sound" option was chosen before (and if so, then tick the menu item) */
161 if (Globals.bSoundClick)
162 {
163 CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
164 }
165
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)
170 {
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);
174 }
175
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
182 /* Move the dialog on the bottom of main screen */
183 GetWindowRect(hDlg, &rcWindow);
184 MoveWindow(hDlg,
185 (info.rcMonitor.left + info.rcMonitor.right) / 2 - // Center of screen
186 (rcWindow.right - rcWindow.left) / 2, // - half size of dialog
187 info.rcMonitor.bottom - // Bottom of screen
188 (rcWindow.bottom - rcWindow.top), // - size of window
189 rcWindow.right - rcWindow.left, // Width
190 rcWindow.bottom - rcWindow.top, // Height
191 TRUE);
192
193 /* Set icon on visual buttons */
194 OSK_SetImage(SCAN_CODE_15, IDI_BACK);
195 OSK_SetImage(SCAN_CODE_16, IDI_TAB);
196 OSK_SetImage(SCAN_CODE_30, IDI_CAPS_LOCK);
197 OSK_SetImage(SCAN_CODE_43, IDI_RETURN);
198 OSK_SetImage(SCAN_CODE_44, IDI_SHIFT);
199 OSK_SetImage(SCAN_CODE_57, IDI_SHIFT);
200 OSK_SetImage(SCAN_CODE_127, IDI_REACTOS);
201 OSK_SetImage(SCAN_CODE_128, IDI_REACTOS);
202 OSK_SetImage(SCAN_CODE_129, IDI_MENU);
203 OSK_SetImage(SCAN_CODE_80, IDI_HOME);
204 OSK_SetImage(SCAN_CODE_85, IDI_PG_UP);
205 OSK_SetImage(SCAN_CODE_86, IDI_PG_DOWN);
206 OSK_SetImage(SCAN_CODE_79, IDI_LEFT);
207 OSK_SetImage(SCAN_CODE_83, IDI_TOP);
208 OSK_SetImage(SCAN_CODE_84, IDI_BOTTOM);
209 OSK_SetImage(SCAN_CODE_89, IDI_RIGHT);
210
211 /* Create a green brush for leds */
212 Globals.hBrushGreenLed = CreateSolidBrush(RGB(0, 255, 0));
213
214 /* Set a timer for periodics tasks */
215 Globals.iTimer = SetTimer(hDlg, 0, 200, NULL);
216
217 return TRUE;
218 }
219
220 /***********************************************************************
221 *
222 * OSK_DlgClose
223 *
224 * Handling of WM_CLOSE
225 */
226 int OSK_DlgClose(void)
227 {
228 KillTimer(Globals.hMainWnd, Globals.iTimer);
229
230 /* Release Ctrl, Shift, Alt keys */
231 OSK_ReleaseKey(SCAN_CODE_44); // Left shift
232 OSK_ReleaseKey(SCAN_CODE_57); // Right shift
233 OSK_ReleaseKey(SCAN_CODE_58); // Left ctrl
234 OSK_ReleaseKey(SCAN_CODE_60); // Left alt
235 OSK_ReleaseKey(SCAN_CODE_62); // Right alt
236 OSK_ReleaseKey(SCAN_CODE_64); // Right ctrl
237
238 /* delete GDI objects */
239 if (Globals.hBrushGreenLed) DeleteObject(Globals.hBrushGreenLed);
240
241 /* Save the settings to the registry hive */
242 SaveDataToRegistry();
243
244 return TRUE;
245 }
246
247 /***********************************************************************
248 *
249 * OSK_DlgTimer
250 *
251 * Handling of WM_TIMER
252 */
253 int OSK_DlgTimer(void)
254 {
255 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
256 HWND hWndActiveWindow;
257
258 hWndActiveWindow = GetForegroundWindow();
259 if (hWndActiveWindow != NULL && hWndActiveWindow != Globals.hMainWnd)
260 {
261 Globals.hActiveWnd = hWndActiveWindow;
262 }
263
264 /* Always redraw leds because it can be changed by the real keyboard) */
265 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_NUM), NULL, TRUE);
266 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_CAPS), NULL, TRUE);
267 InvalidateRect(GetDlgItem(Globals.hMainWnd, IDC_LED_SCROLL), NULL, TRUE);
268
269 return TRUE;
270 }
271
272 /***********************************************************************
273 *
274 * OSK_DlgCommand
275 *
276 * All handling of dialog command
277 */
278 BOOL OSK_DlgCommand(WPARAM wCommand, HWND hWndControl)
279 {
280 WORD ScanCode;
281 INPUT Input;
282 BOOL bExtendedKey;
283 BOOL bKeyDown;
284 BOOL bKeyUp;
285 LONG WindowStyle;
286
287 /* FIXME: To be deleted when ReactOS will support WS_EX_NOACTIVATE */
288 if (Globals.hActiveWnd)
289 {
290 MSG msg;
291
292 SetForegroundWindow(Globals.hActiveWnd);
293 while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE))
294 {
295 TranslateMessage(&msg);
296 DispatchMessageW(&msg);
297 }
298 }
299
300 /* KeyDown and/or KeyUp ? */
301 WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
302 if ((WindowStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX)
303 {
304 /* 2-states key like Shift, Alt, Ctrl, ... */
305 if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) == BST_CHECKED)
306 {
307 bKeyDown = TRUE;
308 bKeyUp = FALSE;
309 }
310 else
311 {
312 bKeyDown = FALSE;
313 bKeyUp = TRUE;
314 }
315 }
316 else
317 {
318 /* Other key */
319 bKeyDown = TRUE;
320 bKeyUp = TRUE;
321 }
322
323 /* Extended key ? */
324 ScanCode = wCommand;
325 if (ScanCode & 0x0200)
326 bExtendedKey = TRUE;
327 else
328 bExtendedKey = FALSE;
329 ScanCode &= 0xFF;
330
331 /* Press and release the key */
332 if (bKeyDown)
333 {
334 Input.type = INPUT_KEYBOARD;
335 Input.ki.wVk = 0;
336 Input.ki.wScan = ScanCode;
337 Input.ki.time = GetTickCount();
338 Input.ki.dwExtraInfo = GetMessageExtraInfo();
339 Input.ki.dwFlags = KEYEVENTF_SCANCODE;
340 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
341 SendInput(1, &Input, sizeof(Input));
342 }
343
344 if (bKeyUp)
345 {
346 Input.type = INPUT_KEYBOARD;
347 Input.ki.wVk = 0;
348 Input.ki.wScan = ScanCode;
349 Input.ki.time = GetTickCount();
350 Input.ki.dwExtraInfo = GetMessageExtraInfo();
351 Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
352 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
353 SendInput(1, &Input, sizeof(Input));
354 }
355
356 /* Play the sound during clicking event (only if "Use Click Sound" menu option is ticked) */
357 if (Globals.bSoundClick)
358 {
359 PlaySoundW(MAKEINTRESOURCEW(IDI_SOUNDCLICK), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC);
360 }
361
362 return TRUE;
363 }
364
365 /***********************************************************************
366 *
367 * OSK_ReleaseKey
368 *
369 * Release the key of ID wCommand
370 */
371 BOOL OSK_ReleaseKey(WORD ScanCode)
372 {
373 INPUT Input;
374 BOOL bExtendedKey;
375 LONG WindowStyle;
376 HWND hWndControl;
377
378 /* Is it a 2-states key ? */
379 hWndControl = GetDlgItem(Globals.hMainWnd, ScanCode);
380 WindowStyle = GetWindowLongW(hWndControl, GWL_STYLE);
381 if ((WindowStyle & BS_AUTOCHECKBOX) != BS_AUTOCHECKBOX) return FALSE;
382
383 /* Is the key down ? */
384 if (SendMessageW(hWndControl, BM_GETCHECK, 0, 0) != BST_CHECKED) return TRUE;
385
386 /* Extended key ? */
387 if (ScanCode & 0x0200)
388 bExtendedKey = TRUE;
389 else
390 bExtendedKey = FALSE;
391 ScanCode &= 0xFF;
392
393 /* Release the key */
394 Input.type = INPUT_KEYBOARD;
395 Input.ki.wVk = 0;
396 Input.ki.wScan = ScanCode;
397 Input.ki.time = GetTickCount();
398 Input.ki.dwExtraInfo = GetMessageExtraInfo();
399 Input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
400 if (bExtendedKey) Input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
401 SendInput(1, &Input, sizeof(Input));
402
403 return TRUE;
404 }
405
406 /***********************************************************************
407 *
408 * OSK_DlgProc
409 */
410 INT_PTR APIENTRY OSK_DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
411 {
412 switch (msg)
413 {
414 case WM_INITDIALOG:
415 OSK_DlgInitDialog(hDlg);
416 return TRUE;
417
418 case WM_TIMER:
419 OSK_DlgTimer();
420 return TRUE;
421
422 case WM_CTLCOLORSTATIC:
423 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_NUM))
424 {
425 if (GetKeyState(VK_NUMLOCK) & 0x0001)
426 return (INT_PTR)Globals.hBrushGreenLed;
427 else
428 return (INT_PTR)GetStockObject(BLACK_BRUSH);
429 }
430 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_CAPS))
431 {
432 if (GetKeyState(VK_CAPITAL) & 0x0001)
433 return (INT_PTR)Globals.hBrushGreenLed;
434 else
435 return (INT_PTR)GetStockObject(BLACK_BRUSH);
436 }
437 if ((HWND)lParam == GetDlgItem(hDlg, IDC_LED_SCROLL))
438 {
439 if (GetKeyState(VK_SCROLL) & 0x0001)
440 return (INT_PTR)Globals.hBrushGreenLed;
441 else
442 return (INT_PTR)GetStockObject(BLACK_BRUSH);
443 }
444 break;
445
446 case WM_COMMAND:
447 switch (LOWORD(wParam))
448 {
449 case IDCANCEL:
450 {
451 EndDialog(hDlg, FALSE);
452 break;
453 }
454
455 case IDM_EXIT:
456 {
457 EndDialog(hDlg, FALSE);
458 break;
459 }
460
461 case IDM_ENHANCED_KB:
462 {
463 if (!Globals.bIsEnhancedKeyboard)
464 {
465 /*
466 The user attempted to switch to enhanced keyboard dialog type.
467 Set the member value as TRUE, destroy the dialog and save the data configuration into the registry.
468 */
469 Globals.bIsEnhancedKeyboard = TRUE;
470 EndDialog(hDlg, FALSE);
471 SaveDataToRegistry();
472
473 /* Change the condition of enhanced keyboard item menu to checked */
474 CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_CHECKED);
475 CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_UNCHECKED);
476
477 /* Finally, display the dialog modal box with the enhanced keyboard dialog */
478 DialogBoxW(Globals.hInstance,
479 MAKEINTRESOURCEW(MAIN_DIALOG_ENHANCED_KB),
480 GetDesktopWindow(),
481 OSK_DlgProc);
482 }
483
484 break;
485 }
486
487 case IDM_STANDARD_KB:
488 {
489 if (Globals.bIsEnhancedKeyboard)
490 {
491 /*
492 The user attempted to switch to standard keyboard dialog type.
493 Set the member value as FALSE, destroy the dialog and save the data configuration into the registry.
494 */
495 Globals.bIsEnhancedKeyboard = FALSE;
496 EndDialog(hDlg, FALSE);
497 SaveDataToRegistry();
498
499 /* Change the condition of standard keyboard item menu to checked */
500 CheckMenuItem(GetMenu(hDlg), IDM_ENHANCED_KB, MF_BYCOMMAND | MF_UNCHECKED);
501 CheckMenuItem(GetMenu(hDlg), IDM_STANDARD_KB, MF_BYCOMMAND | MF_CHECKED);
502
503 /* Finally, display the dialog modal box with the standard keyboard dialog */
504 DialogBoxW(Globals.hInstance,
505 MAKEINTRESOURCEW(MAIN_DIALOG_STANDARD_KB),
506 GetDesktopWindow(),
507 OSK_DlgProc);
508 }
509
510 break;
511 }
512
513 case IDM_CLICK_SOUND:
514 {
515 /*
516 This case is triggered when the user attempts to click on the menu item. Before doing anything,
517 we must check the condition state of such menu item so that we can tick/untick the menu item accordingly.
518 */
519 if (!Globals.bSoundClick)
520 {
521 Globals.bSoundClick = TRUE;
522 CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_CHECKED);
523 }
524 else
525 {
526 Globals.bSoundClick = FALSE;
527 CheckMenuItem(GetMenu(hDlg), IDM_CLICK_SOUND, MF_BYCOMMAND | MF_UNCHECKED);
528 }
529
530 break;
531 }
532
533 case IDM_ABOUT:
534 {
535 OSK_About();
536 break;
537 }
538
539 default:
540 OSK_DlgCommand(wParam, (HWND)lParam);
541 break;
542 }
543 break;
544
545 case WM_CLOSE:
546 OSK_DlgClose();
547 break;
548 }
549
550 return 0;
551 }
552
553 /***********************************************************************
554 *
555 * WinMain
556 */
557 int WINAPI wWinMain(HINSTANCE hInstance,
558 HINSTANCE prev,
559 LPWSTR cmdline,
560 int show)
561 {
562 HANDLE hMutex;
563 INT LayoutResource;
564
565 UNREFERENCED_PARAMETER(prev);
566 UNREFERENCED_PARAMETER(cmdline);
567 UNREFERENCED_PARAMETER(show);
568
569 ZeroMemory(&Globals, sizeof(Globals));
570 Globals.hInstance = hInstance;
571
572 /* Load the settings from the registry hive */
573 LoadDataFromRegistry();
574
575 /* If the member of the struct (bShowWarning) is set then display the dialog box */
576 if (Globals.bShowWarning)
577 {
578 DialogBoxW(Globals.hInstance, MAKEINTRESOURCEW(IDD_WARNINGDIALOG_OSK), Globals.hMainWnd, OSK_WarningProc);
579 }
580
581 /* Before initializing the dialog execution, check if the chosen keyboard type is standard or enhanced */
582 if (Globals.bIsEnhancedKeyboard)
583 {
584 LayoutResource = MAIN_DIALOG_ENHANCED_KB;
585 }
586 else
587 {
588 LayoutResource = MAIN_DIALOG_STANDARD_KB;
589 }
590
591 /* Rry to open a mutex for a single instance */
592 hMutex = OpenMutexW(MUTEX_ALL_ACCESS, FALSE, L"osk");
593
594 if (!hMutex)
595 {
596 /* Mutex doesn\92t exist. This is the first instance so create the mutex. */
597 hMutex = CreateMutexW(NULL, FALSE, L"osk");
598
599 /* Create the modal box based on the configuration registry */
600 DialogBoxW(hInstance,
601 MAKEINTRESOURCEW(LayoutResource),
602 GetDesktopWindow(),
603 OSK_DlgProc);
604
605 /* Delete the mutex */
606 if (hMutex) CloseHandle(hMutex);
607 }
608 else
609 {
610 /* Programme already launched */
611
612 /* Delete the mutex */
613 CloseHandle(hMutex);
614
615 ExitProcess(0);
616 }
617
618 return 0;
619 }
620
621 /* EOF */