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