1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Win32k subsystem 4 * PURPOSE: HotKey support 5 * FILE: win32ss/user/ntuser/hotkey.c 6 * PROGRAMER: Eric Kohl 7 */ 8 9 /* 10 * FIXME: Hotkey notifications are triggered by keyboard input (physical or programatically) 11 * and since only desktops on WinSta0 can receive input in seems very wrong to allow 12 * windows/threads on destops not belonging to WinSta0 to set hotkeys (receive notifications). 13 * -- Gunnar 14 */ 15 16 #include <win32k.h> 17 DBG_DEFAULT_CHANNEL(UserHotkey); 18 19 /* GLOBALS *******************************************************************/ 20 21 /* 22 * Hardcoded hotkeys. See http://ivanlef0u.fr/repo/windoz/VI20051005.html 23 * or http://repo.meh.or.id/Windows/VI20051005.html . 24 * 25 * NOTE: The (Shift-)F12 keys are used only for the "UserDebuggerHotKey" setting 26 * which enables setting a key shortcut which, when pressed, establishes a 27 * breakpoint in the code being debugged: 28 * see http://technet.microsoft.com/en-us/library/cc786263(v=ws.10).aspx 29 * and http://flylib.com/books/en/4.441.1.33/1/ for more details. 30 * By default the key is VK-F12 on a 101-key keyboard, and is VK_SUBTRACT 31 * (hyphen / substract sign) on a 82-key keyboard. 32 */ 33 /* pti pwnd modifiers vk id next */ 34 // HOT_KEY hkF12 = {NULL, 1, 0, VK_F12, IDHK_F12, NULL}; 35 // HOT_KEY hkShiftF12 = {NULL, 1, MOD_SHIFT, VK_F12, IDHK_SHIFTF12, &hkF12}; 36 // HOT_KEY hkWinKey = {NULL, 1, MOD_WIN, 0, IDHK_WINKEY, &hkShiftF12}; 37 38 PHOT_KEY gphkFirst = NULL; 39 UINT gfsModOnlyCandidate; 40 41 /* FUNCTIONS *****************************************************************/ 42 43 VOID FASTCALL 44 StartDebugHotKeys(VOID) 45 { 46 UINT vk = VK_F12; 47 UserUnregisterHotKey(PWND_BOTTOM, IDHK_F12); 48 UserUnregisterHotKey(PWND_BOTTOM, IDHK_SHIFTF12); 49 if (!ENHANCED_KEYBOARD(gKeyboardInfo.KeyboardIdentifier)) 50 { 51 vk = VK_SUBTRACT; 52 } 53 UserRegisterHotKey(PWND_BOTTOM, IDHK_SHIFTF12, MOD_SHIFT, vk); 54 UserRegisterHotKey(PWND_BOTTOM, IDHK_F12, 0, vk); 55 TRACE("Start up the debugger hotkeys!! If you see this you eneabled debugprints. Congrats!\n"); 56 } 57 58 /* 59 * IntGetModifiers 60 * 61 * Returns a value that indicates if the key is a modifier key, and 62 * which one. 63 */ 64 static 65 UINT FASTCALL 66 IntGetModifiers(PBYTE pKeyState) 67 { 68 UINT fModifiers = 0; 69 70 if (IS_KEY_DOWN(pKeyState, VK_SHIFT)) 71 fModifiers |= MOD_SHIFT; 72 73 if (IS_KEY_DOWN(pKeyState, VK_CONTROL)) 74 fModifiers |= MOD_CONTROL; 75 76 if (IS_KEY_DOWN(pKeyState, VK_MENU)) 77 fModifiers |= MOD_ALT; 78 79 if (IS_KEY_DOWN(pKeyState, VK_LWIN) || IS_KEY_DOWN(pKeyState, VK_RWIN)) 80 fModifiers |= MOD_WIN; 81 82 return fModifiers; 83 } 84 85 /* 86 * UnregisterWindowHotKeys 87 * 88 * Removes hotkeys registered by specified window on its cleanup 89 */ 90 VOID FASTCALL 91 UnregisterWindowHotKeys(PWND pWnd) 92 { 93 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst; 94 95 while (pHotKey) 96 { 97 /* Save next ptr for later use */ 98 phkNext = pHotKey->pNext; 99 100 /* Should we delete this hotkey? */ 101 if (pHotKey->pWnd == pWnd) 102 { 103 /* Update next ptr for previous hotkey and free memory */ 104 *pLink = phkNext; 105 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY); 106 } 107 else /* This hotkey will stay, use its next ptr */ 108 pLink = &pHotKey->pNext; 109 110 /* Move to the next entry */ 111 pHotKey = phkNext; 112 } 113 } 114 115 /* 116 * UnregisterThreadHotKeys 117 * 118 * Removes hotkeys registered by specified thread on its cleanup 119 */ 120 VOID FASTCALL 121 UnregisterThreadHotKeys(PTHREADINFO pti) 122 { 123 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst; 124 125 while (pHotKey) 126 { 127 /* Save next ptr for later use */ 128 phkNext = pHotKey->pNext; 129 130 /* Should we delete this hotkey? */ 131 if (pHotKey->pti == pti) 132 { 133 /* Update next ptr for previous hotkey and free memory */ 134 *pLink = phkNext; 135 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY); 136 } 137 else /* This hotkey will stay, use its next ptr */ 138 pLink = &pHotKey->pNext; 139 140 /* Move to the next entry */ 141 pHotKey = phkNext; 142 } 143 } 144 145 /* 146 * IsHotKey 147 * 148 * Checks if given key and modificators have corresponding hotkey 149 */ 150 static PHOT_KEY FASTCALL 151 IsHotKey(UINT fsModifiers, WORD wVk) 152 { 153 PHOT_KEY pHotKey = gphkFirst; 154 155 while (pHotKey) 156 { 157 if (pHotKey->fsModifiers == fsModifiers && 158 pHotKey->vk == wVk) 159 { 160 /* We have found it */ 161 return pHotKey; 162 } 163 164 /* Move to the next entry */ 165 pHotKey = pHotKey->pNext; 166 } 167 168 return NULL; 169 } 170 171 /* 172 * co_UserProcessHotKeys 173 * 174 * Sends WM_HOTKEY message if given keys are hotkey 175 */ 176 BOOL NTAPI 177 co_UserProcessHotKeys(WORD wVk, BOOL bIsDown) 178 { 179 UINT fModifiers; 180 PHOT_KEY pHotKey; 181 PWND pWnd; 182 BOOL DoNotPostMsg = FALSE; 183 BOOL IsModifier = FALSE; 184 185 if (wVk == VK_SHIFT || wVk == VK_CONTROL || wVk == VK_MENU || 186 wVk == VK_LWIN || wVk == VK_RWIN) 187 { 188 /* Remember that this was a modifier */ 189 IsModifier = TRUE; 190 } 191 192 fModifiers = IntGetModifiers(gafAsyncKeyState); 193 194 if (bIsDown) 195 { 196 if (IsModifier) 197 { 198 /* Modifier key down -- no hotkey trigger, but remember this */ 199 gfsModOnlyCandidate = fModifiers; 200 return FALSE; 201 } 202 else 203 { 204 /* Regular key down -- check for hotkey, and reset mod candidates */ 205 pHotKey = IsHotKey(fModifiers, wVk); 206 gfsModOnlyCandidate = 0; 207 } 208 } 209 else 210 { 211 if (IsModifier) 212 { 213 /* Modifier key up -- modifier-only keys are triggered here */ 214 pHotKey = IsHotKey(gfsModOnlyCandidate, 0); 215 gfsModOnlyCandidate = 0; 216 } 217 else 218 { 219 /* Regular key up -- no hotkey, but reset mod-only candidates */ 220 gfsModOnlyCandidate = 0; 221 return FALSE; 222 } 223 } 224 225 if (pHotKey) 226 { 227 TRACE("Hot key pressed (pWnd %p, id %d)\n", pHotKey->pWnd, pHotKey->id); 228 229 /* FIXME: See comment about "UserDebuggerHotKey" on top of this file. */ 230 if (pHotKey->id == IDHK_SHIFTF12 || pHotKey->id == IDHK_F12) 231 { 232 if (bIsDown) 233 { 234 ERR("Hot key pressed for Debug Activation! ShiftF12 = %d or F12 = %d\n",pHotKey->id == IDHK_SHIFTF12 , pHotKey->id == IDHK_F12); 235 //DoNotPostMsg = co_ActivateDebugger(); // FIXME 236 } 237 return DoNotPostMsg; 238 } 239 240 /* WIN and F12 keys are not hardcoded here. See comments on top of this file. */ 241 if (pHotKey->id == IDHK_WINKEY) 242 { 243 ASSERT(!bIsDown); 244 pWnd = ValidateHwndNoErr(InputWindowStation->ShellWindow); 245 if (pWnd) 246 { 247 TRACE("System Hot key Id %d Key %u\n", pHotKey->id, wVk ); 248 UserPostMessage(UserHMGetHandle(pWnd), WM_SYSCOMMAND, SC_TASKLIST, 0); 249 co_IntShellHookNotify(HSHELL_TASKMAN, 0, 0); 250 return FALSE; 251 } 252 } 253 254 if (pHotKey->id == IDHK_SNAP_LEFT || 255 pHotKey->id == IDHK_SNAP_RIGHT || 256 pHotKey->id == IDHK_SNAP_UP || 257 pHotKey->id == IDHK_SNAP_DOWN) 258 { 259 HWND topWnd = UserGetForegroundWindow(); 260 if (topWnd) 261 { 262 UserPostMessage(topWnd, WM_KEYDOWN, wVk, 0); 263 } 264 return TRUE; 265 } 266 267 if (!pHotKey->pWnd) 268 { 269 TRACE("UPTM Hot key Id %d Key %u\n", pHotKey->id, wVk ); 270 UserPostThreadMessage(pHotKey->pti, WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk)); 271 //ptiLastInput = pHotKey->pti; 272 return TRUE; /* Don't send any message */ 273 } 274 else 275 { 276 pWnd = pHotKey->pWnd; 277 if (pWnd == PWND_BOTTOM) 278 { 279 if (gpqForeground == NULL) 280 return FALSE; 281 282 pWnd = gpqForeground->spwndFocus; 283 } 284 285 if (pWnd) 286 { 287 // pWnd->head.rpdesk->pDeskInfo->spwndShell needs testing. 288 if (pWnd == ValidateHwndNoErr(InputWindowStation->ShellWindow) && pHotKey->id == SC_TASKLIST) 289 { 290 UserPostMessage(UserHMGetHandle(pWnd), WM_SYSCOMMAND, SC_TASKLIST, 0); 291 co_IntShellHookNotify(HSHELL_TASKMAN, 0, 0); 292 } 293 else 294 { 295 TRACE("UPM Hot key Id %d Key %u\n", pHotKey->id, wVk ); 296 UserPostMessage(UserHMGetHandle(pWnd), WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk)); 297 } 298 //ptiLastInput = pWnd->head.pti; 299 return TRUE; /* Don't send any message */ 300 } 301 } 302 } 303 return FALSE; 304 } 305 306 307 /* 308 * DefWndGetHotKey 309 * 310 * GetHotKey message support 311 */ 312 UINT FASTCALL 313 DefWndGetHotKey(PWND pWnd) 314 { 315 PHOT_KEY pHotKey = gphkFirst; 316 317 WARN("DefWndGetHotKey\n"); 318 319 while (pHotKey) 320 { 321 if (pHotKey->pWnd == pWnd && pHotKey->id == IDHK_REACTOS) 322 { 323 /* We have found it */ 324 return MAKELONG(pHotKey->vk, pHotKey->fsModifiers); 325 } 326 327 /* Move to the next entry */ 328 pHotKey = pHotKey->pNext; 329 } 330 331 return 0; 332 } 333 334 /* 335 * DefWndSetHotKey 336 * 337 * SetHotKey message support 338 */ 339 INT FASTCALL 340 DefWndSetHotKey(PWND pWnd, WPARAM wParam) 341 { 342 UINT fsModifiers, vk; 343 PHOT_KEY pHotKey, *pLink; 344 INT iRet = 1; 345 346 WARN("DefWndSetHotKey wParam 0x%x\n", wParam); 347 348 // A hot key cannot be associated with a child window. 349 if (pWnd->style & WS_CHILD) 350 return 0; 351 352 // VK_ESCAPE, VK_SPACE, and VK_TAB are invalid hot keys. 353 if (LOWORD(wParam) == VK_ESCAPE || 354 LOWORD(wParam) == VK_SPACE || 355 LOWORD(wParam) == VK_TAB) 356 { 357 return -1; 358 } 359 360 vk = LOWORD(wParam); 361 fsModifiers = HIWORD(wParam); 362 363 if (wParam) 364 { 365 pHotKey = gphkFirst; 366 while (pHotKey) 367 { 368 if (pHotKey->fsModifiers == fsModifiers && 369 pHotKey->vk == vk && 370 pHotKey->id == IDHK_REACTOS) 371 { 372 if (pHotKey->pWnd != pWnd) 373 iRet = 2; // Another window already has the same hot key. 374 break; 375 } 376 377 /* Move to the next entry */ 378 pHotKey = pHotKey->pNext; 379 } 380 } 381 382 pHotKey = gphkFirst; 383 pLink = &gphkFirst; 384 while (pHotKey) 385 { 386 if (pHotKey->pWnd == pWnd && 387 pHotKey->id == IDHK_REACTOS) 388 { 389 /* This window has already hotkey registered */ 390 break; 391 } 392 393 /* Move to the next entry */ 394 pLink = &pHotKey->pNext; 395 pHotKey = pHotKey->pNext; 396 } 397 398 if (wParam) 399 { 400 if (!pHotKey) 401 { 402 /* Create new hotkey */ 403 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY); 404 if (pHotKey == NULL) 405 return 0; 406 407 pHotKey->pWnd = pWnd; 408 pHotKey->id = IDHK_REACTOS; // Don't care, these hot keys are unrelated to the hot keys set by RegisterHotKey 409 pHotKey->pNext = gphkFirst; 410 gphkFirst = pHotKey; 411 } 412 413 /* A window can only have one hot key. If the window already has a 414 hot key associated with it, the new hot key replaces the old one. */ 415 pHotKey->pti = NULL; 416 pHotKey->fsModifiers = fsModifiers; 417 pHotKey->vk = vk; 418 } 419 else if (pHotKey) 420 { 421 /* Remove hotkey */ 422 *pLink = pHotKey->pNext; 423 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY); 424 } 425 426 return iRet; 427 } 428 429 430 BOOL FASTCALL 431 UserRegisterHotKey(PWND pWnd, 432 int id, 433 UINT fsModifiers, 434 UINT vk) 435 { 436 PHOT_KEY pHotKey; 437 PTHREADINFO pHotKeyThread; 438 439 /* Find hotkey thread */ 440 if (pWnd == NULL || pWnd == PWND_BOTTOM) 441 { 442 pHotKeyThread = PsGetCurrentThreadWin32Thread(); 443 } 444 else 445 { 446 pHotKeyThread = pWnd->head.pti; 447 } 448 449 /* Check for existing hotkey */ 450 if (IsHotKey(fsModifiers, vk)) 451 { 452 EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED); 453 WARN("Hotkey already exists\n"); 454 return FALSE; 455 } 456 457 /* Create new hotkey */ 458 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY); 459 if (pHotKey == NULL) 460 { 461 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY); 462 return FALSE; 463 } 464 465 pHotKey->pti = pHotKeyThread; 466 pHotKey->pWnd = pWnd; 467 pHotKey->fsModifiers = fsModifiers; 468 pHotKey->vk = vk; 469 pHotKey->id = id; 470 471 /* Insert hotkey to the global list */ 472 pHotKey->pNext = gphkFirst; 473 gphkFirst = pHotKey; 474 475 return TRUE; 476 } 477 478 BOOL FASTCALL 479 UserUnregisterHotKey(PWND pWnd, int id) 480 { 481 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst; 482 BOOL bRet = FALSE; 483 484 while (pHotKey) 485 { 486 /* Save next ptr for later use */ 487 phkNext = pHotKey->pNext; 488 489 /* Should we delete this hotkey? */ 490 if (pHotKey->pWnd == pWnd && pHotKey->id == id) 491 { 492 /* Update next ptr for previous hotkey and free memory */ 493 *pLink = phkNext; 494 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY); 495 496 bRet = TRUE; 497 } 498 else /* This hotkey will stay, use its next ptr */ 499 pLink = &pHotKey->pNext; 500 501 /* Move to the next entry */ 502 pHotKey = phkNext; 503 } 504 return bRet; 505 } 506 507 508 /* SYSCALLS *****************************************************************/ 509 510 511 BOOL APIENTRY 512 NtUserRegisterHotKey(HWND hWnd, 513 int id, 514 UINT fsModifiers, 515 UINT vk) 516 { 517 PHOT_KEY pHotKey; 518 PWND pWnd = NULL; 519 PTHREADINFO pHotKeyThread; 520 BOOL bRet = FALSE; 521 522 TRACE("Enter NtUserRegisterHotKey\n"); 523 524 if (fsModifiers & ~(MOD_ALT|MOD_CONTROL|MOD_SHIFT|MOD_WIN)) // FIXME: Does Win2k3 support MOD_NOREPEAT? 525 { 526 WARN("Invalid modifiers: %x\n", fsModifiers); 527 EngSetLastError(ERROR_INVALID_FLAGS); 528 return 0; 529 } 530 531 UserEnterExclusive(); 532 533 /* Find hotkey thread */ 534 if (hWnd == NULL) 535 { 536 pHotKeyThread = gptiCurrent; 537 } 538 else 539 { 540 pWnd = UserGetWindowObject(hWnd); 541 if (!pWnd) 542 goto cleanup; 543 544 pHotKeyThread = pWnd->head.pti; 545 546 /* Fix wine msg "Window on another thread" test_hotkey */ 547 if (pWnd->head.pti != gptiCurrent) 548 { 549 EngSetLastError(ERROR_WINDOW_OF_OTHER_THREAD); 550 WARN("Must be from the same Thread.\n"); 551 goto cleanup; 552 } 553 } 554 555 /* Check for existing hotkey */ 556 if (IsHotKey(fsModifiers, vk)) 557 { 558 EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED); 559 WARN("Hotkey already exists\n"); 560 goto cleanup; 561 } 562 563 /* Create new hotkey */ 564 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY); 565 if (pHotKey == NULL) 566 { 567 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY); 568 goto cleanup; 569 } 570 571 pHotKey->pti = pHotKeyThread; 572 pHotKey->pWnd = pWnd; 573 pHotKey->fsModifiers = fsModifiers; 574 pHotKey->vk = vk; 575 pHotKey->id = id; 576 577 /* Insert hotkey to the global list */ 578 pHotKey->pNext = gphkFirst; 579 gphkFirst = pHotKey; 580 581 bRet = TRUE; 582 583 cleanup: 584 TRACE("Leave NtUserRegisterHotKey, ret=%i\n", bRet); 585 UserLeave(); 586 return bRet; 587 } 588 589 590 BOOL APIENTRY 591 NtUserUnregisterHotKey(HWND hWnd, int id) 592 { 593 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst; 594 BOOL bRet = FALSE; 595 PWND pWnd = NULL; 596 597 TRACE("Enter NtUserUnregisterHotKey\n"); 598 UserEnterExclusive(); 599 600 /* Fail if given window is invalid */ 601 if (hWnd && !(pWnd = UserGetWindowObject(hWnd))) 602 goto cleanup; 603 604 while (pHotKey) 605 { 606 /* Save next ptr for later use */ 607 phkNext = pHotKey->pNext; 608 609 /* Should we delete this hotkey? */ 610 if (pHotKey->pWnd == pWnd && pHotKey->id == id) 611 { 612 /* Update next ptr for previous hotkey and free memory */ 613 *pLink = phkNext; 614 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY); 615 616 bRet = TRUE; 617 } 618 else /* This hotkey will stay, use its next ptr */ 619 pLink = &pHotKey->pNext; 620 621 /* Move to the next entry */ 622 pHotKey = phkNext; 623 } 624 625 cleanup: 626 TRACE("Leave NtUserUnregisterHotKey, ret=%i\n", bRet); 627 UserLeave(); 628 return bRet; 629 } 630 631 /* EOF */ 632