/****************************** Module Header ******************************\ * Module Name: hotkeys.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * This module contains the core functions of hotkey processing. * * History: * 12-04-90 DavidPe Created. * 02-12-91 JimA Added access checks * 13-Feb-1991 mikeke Added Revalidation code (None) \***************************************************************************/ #include "precomp.h" #pragma hdrstop static PHOTKEY gphkHashTable[128]; /* * This is the hash function for vks. The vast majority of hotkeys will have * vk values < 128, so we limit our table to that size. Worst case (all vks * > 128) we'll have the same perf (essentially) as the old, linked list * code. */ __inline BYTE HKHashVK( UINT vk) { return (BYTE)(vk & (ARRAY_SIZE(gphkHashTable) - 1)); } /***************************************************************************\ * HKGetHashHead * * This routine returns the start of the bucket keyed by the specified vk. * * History: * 08-13-2002 JasonSch Created. \***************************************************************************/ PHOTKEY HKGetHashHead( UINT vk) { return gphkHashTable[HKHashVK(vk)]; } /***************************************************************************\ * HKInsertHashElement * * Inserts a HOTKEY structure into the hash table. * * History: * 08-13-2002 JasonSch Created. \***************************************************************************/ VOID HKInsertHashElement( PHOTKEY phk) { BYTE index = HKHashVK(phk->vk); PHOTKEY phkT; phkT = gphkHashTable[index]; phk->phkNext = phkT; gphkHashTable[index] = phk; } /***************************************************************************\ * SetDebugHotKeys * * This routine registers the default system hotkeys for debugging. * * History: * 12-04-90 DavidPe Created. \***************************************************************************/ VOID SetDebugHotKeys( VOID) { UINT VkDebug; FastGetProfileDwordW(NULL, PMAP_AEDEBUG, L"UserDebuggerHotkey", 0, &VkDebug, 0); if (VkDebug == 0) { if (ENHANCED_KEYBOARD(gKeyboardInfo.KeyboardIdentifier)) { VkDebug = VK_F12; } else { VkDebug = VK_SUBTRACT; } } else { UserAssert((0xFFFFFF00 & VkDebug) == 0); } _UnregisterHotKey(PWND_INPUTOWNER, IDHOT_DEBUG); _UnregisterHotKey(PWND_INPUTOWNER, IDHOT_DEBUGSERVER); _RegisterHotKey(PWND_INPUTOWNER, IDHOT_DEBUG, 0, VkDebug); _RegisterHotKey(PWND_INPUTOWNER, IDHOT_DEBUGSERVER, MOD_SHIFT, VkDebug); } /***************************************************************************\ * DestroyThreadsHotKeys * * History: * 26-Feb-1991 mikeke Created. \***************************************************************************/ VOID DestroyThreadsHotKeys( VOID) { PHOTKEY *pphk, phk; PTHREADINFO ptiCurrent = PtiCurrent(); int i = 0; for (; i < ARRAY_SIZE(gphkHashTable); ++i) { pphk = &gphkHashTable[i]; while (*pphk) { if ((*pphk)->pti == ptiCurrent) { phk = *pphk; *pphk = (*pphk)->phkNext; /* * Unlock the object stored here. */ if (phk->spwnd != PWND_FOCUS && phk->spwnd != PWND_INPUTOWNER) { Unlock(&phk->spwnd); } UserFreePool(phk); } else { pphk = &((*pphk)->phkNext); } } } } /***************************************************************************\ * DestroyWindowsHotKeys * * Frees hotkeys associated with the specified pwnd that were not explicitly * unregistered by the app. * * History: * 23-Sep-1992 IanJa Created. \***************************************************************************/ VOID DestroyWindowsHotKeys( PWND pwnd) { PHOTKEY *pphk, phk; int i = 0; for (; i < ARRAY_SIZE(gphkHashTable); ++i) { pphk = &gphkHashTable[i]; while (*pphk) { if ((*pphk)->spwnd == pwnd) { phk = *pphk; *pphk = (*pphk)->phkNext; Unlock(&phk->spwnd); UserFreePool(phk); } else { pphk = &((*pphk)->phkNext); } } } } /***************************************************************************\ * _RegisterHotKey (API) * * This API registers the hotkey specified. If the specified key sequence has * already been registered we return FALSE. If the specified hwnd and id have * already been registered, fsModifiers and vk are reset for the HOTKEY. * * History: * 12-04-90 DavidPe Created. * 02-12-91 JimA Added access check \***************************************************************************/ BOOL _RegisterHotKey( PWND pwnd, int id, UINT fsModifiers, UINT vk) { PHOTKEY phk; BOOL fKeysExist, bSAS; PTHREADINFO ptiCurrent; WORD wFlags; wFlags = fsModifiers & MOD_SAS; fsModifiers &= ~MOD_SAS; ptiCurrent = PtiCurrent(); /* * Blow it off if the caller is not the windowstation init thread * and doesn't have the proper access rights */ if (PsGetCurrentProcess() != gpepCSRSS) { if (grpWinStaList && !CheckWinstaWriteAttributesAccess()) { return FALSE; } } /* * If VK_PACKET is specified, just bail out, since VK_PACKET is * not a real keyboard input. */ if (vk == VK_PACKET) { return FALSE; } /* * If this is the SAS check that winlogon is the one registering it. */ if ((wFlags & MOD_SAS) != 0 && PsGetCurrentProcessId() == gpidLogon) { bSAS = TRUE; } else { bSAS = FALSE; } /* * Can't register hotkey for a window of another queue. */ if (pwnd != PWND_FOCUS && pwnd != PWND_INPUTOWNER) { if (GETPTI(pwnd) != ptiCurrent) { RIPERR1(ERROR_WINDOW_OF_OTHER_THREAD, RIP_WARNING, "hwnd 0x%x belongs to a different thread", HWq(pwnd)); return FALSE; } } phk = FindHotKey(ptiCurrent, pwnd, id, fsModifiers, vk, FALSE, &fKeysExist); /* * If the keys have already been registered, return FALSE. */ if (fKeysExist) { RIPERR0(ERROR_HOTKEY_ALREADY_REGISTERED, RIP_WARNING, "Hotkey already exists"); return FALSE; } if (phk == NULL) { /* * This hotkey doesn't exist yet. */ phk = (PHOTKEY)UserAllocPool(sizeof(HOTKEY), TAG_HOTKEY); if (phk == NULL) { return FALSE; } phk->pti = ptiCurrent; if (pwnd != PWND_FOCUS && pwnd != PWND_INPUTOWNER) { phk->spwnd = NULL; Lock(&phk->spwnd, pwnd); } else { phk->spwnd = pwnd; } phk->fsModifiers = (WORD)fsModifiers; phk->wFlags = wFlags; phk->vk = vk; phk->id = id; /* * Add the new hotkey to our global hash. */ HKInsertHashElement(phk); } else { /* * Hotkey already exists, reset the keys. */ phk->fsModifiers = (WORD)fsModifiers; phk->wFlags = wFlags; phk->vk = vk; } if (bSAS) { /* * Store the SAS on the terminal. */ gvkSAS = vk; gfsSASModifiers = fsModifiers; } return TRUE; } /***************************************************************************\ * _UnregisterHotKey (API) * * This API will unregister the specified hwnd/id hotkey so that the * WM_HOTKEY message will not be generated for it. * * History: * 12-04-90 DavidPe Created. \***************************************************************************/ BOOL _UnregisterHotKey( PWND pwnd, int id) { PHOTKEY phk; BOOL fKeysExist; PTHREADINFO ptiCurrent = PtiCurrent(); phk = FindHotKey(ptiCurrent, pwnd, id, 0, 0, TRUE, &fKeysExist); if (phk == NULL) { RIPERR2(ERROR_HOTKEY_NOT_REGISTERED, (pwnd == PWND_INPUTOWNER) ? RIP_VERBOSE : RIP_WARNING, "Hotkey 0x%x on pwnd 0x%p does not exist", id, pwnd); return FALSE; } return TRUE; } /***************************************************************************\ * FindHotKey * * Both RegisterHotKey() and UnregisterHotKey() call this function to search * for hotkeys that already exist. If a HOTKEY is found that matches * fsModifiers and vk, *pfKeysExist is set to TRUE. If a HOTKEY is found that * matches pwnd and id, a pointer to it is returned. * * If fUnregister is TRUE, we remove the HOTKEY from the list if we find * one that matches pwnd and id and return (PHOTKEY)1. * * History: * 12-04-90 DavidPe Created. \***************************************************************************/ PHOTKEY FindHotKey( PTHREADINFO ptiCurrent, PWND pwnd, int id, UINT fsModifiers, UINT vk, BOOL fUnregister, PBOOL pfKeysExist) { PHOTKEY phk, phkRet, phkPrev; BYTE index = HKHashVK(vk); UserAssert(!fUnregister || vk == 0); /* * Initialize out 'return' values. */ *pfKeysExist = FALSE; phkRet = NULL; phk = gphkHashTable[index]; hashloop: while (phk) { /* * If all this matches up then we've found it. */ if (phk->pti == ptiCurrent && phk->spwnd == pwnd && phk->id == id) { if (fUnregister) { /* * Unlink the HOTKEY from the list. */ if (phk == gphkHashTable[index]) { gphkHashTable[index] = phk->phkNext; } else { phkPrev->phkNext = phk->phkNext; } if (pwnd != PWND_FOCUS && pwnd != PWND_INPUTOWNER) { Unlock(&phk->spwnd); } UserFreePool(phk); return (PHOTKEY)1; } phkRet = phk; } /* * If the key is already registered, set the exists flag so the app * knows it can't use this hotkey sequence. */ if (phk->fsModifiers == (WORD)fsModifiers && phk->vk == vk) { /* * In the case of PWND_FOCUS, we need to check that the queues * are the same since PWND_FOCUS is local to the queue it was * registered under. */ if (phk->spwnd == PWND_FOCUS) { if (phk->pti == ptiCurrent) { *pfKeysExist = TRUE; } } else { *pfKeysExist = TRUE; } } phkPrev = phk; phk = phk->phkNext; } /* * This is needed because when called from unregister we specify 0 as * the VK so the hash is always 0 and we need to index through the * entire hash table to try to find it. */ if (fUnregister && ++index < ARRAY_SIZE(gphkHashTable)) { phk = gphkHashTable[index]; goto hashloop; } return phkRet; } /***************************************************************************\ * IsSAS * * Checks the physical state of keyboard modifiers that would effect SAS. \***************************************************************************/ BOOL IsSAS( BYTE vk, UINT *pfsModifiers) { CheckCritIn(); if (gvkSAS != vk) { return FALSE; } /* * Special case for SAS - examine real physical modifier-key state! * * An evil daemon process can fool convincingly pretend to be winlogon * by registering Alt+Del as a hotkey, and spinning another thread that * continually calls keybd_event() to send the Ctrl key up: when the * user types Ctrl+Alt+Del, only Alt+Del will be seen by the system, * the evil daemon will get woken by WM_HOTKEY and can pretend to be * winlogon. So look at gfsSASModifiersDown in this case, to see what keys * were physically pressed. * NOTE: If hotkeys are ever made to work under journal playback, make * sure they don't affect the gfsSASModifiersDown! - IanJa. */ if (gfsSASModifiersDown == gfsSASModifiers) { *pfsModifiers = gfsSASModifiersDown; return TRUE; } return FALSE; } /* * The below two states are used by xxxDoHotKeyStuff(). * Originally function-static variables, but as it's required * to clear those flags after the system wakes up from hybernation, * they are made global, */ UINT gfsModifiers; UINT gfsModOnlyCandidate; VOID ClearCachedHotkeyModifiers( VOID) { /* * Clear the cached modifiers. */ gfsModifiers = 0; gfsModOnlyCandidate = 0; /* * Clear the special modifier cache for the Ctrl+Alt+Del recognition. * (See comments in IsSAS()). */ gfsSASModifiersDown = 0; } /***************************************************************************\ * xxxDoHotKeyStuff * * This function gets called for every key event from low-level input * processing. It keeps track of the current state of modifier keys * and when gfsModifiers and vk match up with one of the registered * hotkeys, a WM_HOTKEY message is generated. DoHotKeyStuff() will * tell the input system to eat both the make and break for the 'vk' * event. This prevents apps from getting input that wasn't really * intended for them. DoHotKeyStuff() returns TRUE if it wants to 'eat' * the event, FALSE if the system can pass on the event like it normally * would. * * A Note on Modifier-Only Hotkeys * Some hotkeys involve VK_SHIFT, VK_CONTROL, VK_MENU and/or VK_WINDOWS only. * These are called Modifier-Only hotkeys. * In order to distinguish hotkeys such as Alt-Shift-S and and Alt-Shift alone, * modifier-only hotkeys must operate on a break, not a make. * In order to prevent Alt-Shift-S from activating the Alt-Shift hotkey when * the keys are released, modifier-only hotkeys are only activated when a * modifier keyup (break) was immediately preceded by a modifier keydown (break) * This also lets Alt-Shift,Shift,Shift activate the Alt-Shift hotkey 3 times. * * History: * 12-05-90 DavidPe Created. * 4-15-93 Sanfords Added code to return TRUE for Ctrl-Alt-Del events. \***************************************************************************/ BOOL xxxDoHotKeyStuff( UINT vk, BOOL fBreak, DWORD fsReserveKeys) { UINT fsModOnlyHotkey; UINT fs; PHOTKEY phk; BOOL fCancel; BOOL fEatDebugKeyBreak; PWND pwnd; BOOL bSAS; CheckCritIn(); UserAssert(IsWinEventNotifyDeferredOK()); if (gfInNumpadHexInput & NUMPAD_HEXMODE_LL) { RIPMSGF0(RIP_VERBOSE, "Since we're in gfInNumpadHexInput, just bail out."); return FALSE; } /* * Update gfsModifiers. */ fs = 0; fsModOnlyHotkey = 0; switch (vk) { case VK_SHIFT: fs = MOD_SHIFT; break; case VK_CONTROL: fs = MOD_CONTROL; break; case VK_MENU: fs = MOD_ALT; break; case VK_LWIN: case VK_RWIN: fs = MOD_WIN; break; default: /* * A non-modifier key rules out Modifier-Only hotkeys */ gfsModOnlyCandidate = 0; break; } if (fBreak) { gfsModifiers &= ~fs; /* * If a modifier key is coming up, the current modifier only hotkey * candidate must be tested to see if it is a hotkey. Store this * in fsModOnlyHotkey, and prevent the next key release from * being a candidate by clearing fsModOnlyCandidate. */ if (fs != 0) { fsModOnlyHotkey = gfsModOnlyCandidate; gfsModOnlyCandidate = 0; } } else { gfsModifiers |= fs; /* * If a modifier key is going down, we have a modifier-only hotkey * candidate. Save current modifier state until the following break. */ if (fs != 0) { gfsModOnlyCandidate = gfsModifiers; } } /* * We look at the physical state for the modifiers because they cannot be * manipulated and this prevents someone from writing a trojan winlogon * look alike (see comment in AreModifiersIndicatingSAS). */ bSAS = IsSAS((BYTE)vk, &gfsModifiers); /* * If the key is not a hotkey then we're done but first check if the * key is an Alt-Escape if so we need to cancel journalling. * * NOTE: Support for Alt+Esc to cancel journalling dropped in NT 4.0 */ if (fsModOnlyHotkey && fBreak) { /* * A hotkey involving only VK_SHIFT, VK_CONTROL, VK_MENU or VK_WINDOWS * must only operate on a key release. */ if ((phk = IsHotKey(fsModOnlyHotkey, VK_NONE)) == NULL) { return FALSE; } } else if ((phk = IsHotKey(gfsModifiers, vk)) == NULL) { return FALSE; } /* * If we tripped a SAS hotkey, but it's not really the SAS, don't do it. */ if ((phk->wFlags & MOD_SAS) && !bSAS) { return FALSE; } #ifdef GENERIC_INPUT if (gpqForeground && TestRawInputMode(PtiKbdFromQ(gpqForeground), NoHotKeys) && (phk->wFlags & MOD_SAS) == 0) { /* * NOTE: * If the foreground thread does not want the hotkey handling, * just bail out. * * Exception: Ctrl+Alt+Del should be strictly handled by the system. */ return FALSE; } #endif if (phk->id == IDHOT_WINDOWS) { pwnd = GETDESKINFO(PtiCurrent())->spwndShell; if (pwnd != NULL) { gfsModOnlyCandidate = 0; /* Make it return TRUE */ goto PostTaskListSysCmd; } } if (phk->id == IDHOT_DEBUG || phk->id == IDHOT_DEBUGSERVER) { if (!fBreak) { /* * The DEBUG key has been pressed. Break the appropriate thread * into the debugger. We won't need phk after this callback * because we return immediately. */ fEatDebugKeyBreak = xxxActivateDebugger(phk->fsModifiers); } else { fEatDebugKeyBreak = FALSE; } /* * This'll eat the debug key down and break if we broke into the * debugger on the server only on the down. */ return fEatDebugKeyBreak; } /* * Don't allow hotkeys (except for ones owned by the logon process) if * the window station is locked. */ if (((grpdeskRitInput->rpwinstaParent->dwWSF_Flags & WSF_SWITCHLOCK) != 0) && (PsGetThreadProcessId(phk->pti->pEThread) != gpidLogon)) { RIPMSG0(RIP_WARNING, "Ignoring hotkey because Workstation locked"); return FALSE; } if (fsModOnlyHotkey == 0 && fBreak) { /* * Do Modifier-Only hotkeys on break events, else return here. */ return FALSE; } /* * Unhook hooks if a control-escape, alt-escape, or control-alt-del * comes through, so the user can cancel if the system seems hung. * * Note the hook may be locked so even if the unhook succeeds it * won't remove the hook from the global asphkStart array. So * we have to walk the list manually. This code works because * we are in the critical section and we know other hooks won't * be deleted. * * Once we've unhooked, post a WM_CANCELJOURNAL message to the app * that set the hook so it knows we did this. * * NOTE: Support for Alt+Esc to cancel journalling dropped in NT 4.0 */ fCancel = FALSE; if (vk == VK_ESCAPE && (gfsModifiers == MOD_CONTROL)) { fCancel = TRUE; } if (bSAS) { fCancel = TRUE; } if (fCancel) { zzzCancelJournalling(); // BUG BUG phk might go away IANJA } /* * See if the key is reserved by a console window. If it is, * return FALSE so the key will be passed to the console. */ if (fsReserveKeys != 0) { switch (vk) { case VK_TAB: if ((fsReserveKeys & CONSOLE_ALTTAB) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_ALT)) { return FALSE; } break; case VK_ESCAPE: if ((fsReserveKeys & CONSOLE_ALTESC) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_ALT)) { return FALSE; } if ((fsReserveKeys & CONSOLE_CTRLESC) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_CONTROL)) { return FALSE; } break; case VK_RETURN: if ((fsReserveKeys & CONSOLE_ALTENTER) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_ALT)) { return FALSE; } break; case VK_SNAPSHOT: if ((fsReserveKeys & CONSOLE_PRTSC) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == 0)) { return FALSE; } if ((fsReserveKeys & CONSOLE_ALTPRTSC) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_ALT)) { return FALSE; } break; case VK_SPACE: if ((fsReserveKeys & CONSOLE_ALTSPACE) && ((gfsModifiers & (MOD_CONTROL | MOD_ALT)) == MOD_ALT)) { return FALSE; } break; } } /* * If this is the task-list hotkey, go ahead and set foreground * status to the task-list queue right now. This prevents problems * where the user hits ctrl-esc and types-ahead before the task-list * processes the hotkey and brings up the task-list window. */ if ((gfsModifiers == MOD_CONTROL) && (vk == VK_ESCAPE) && !fBreak) { PWND pwndSwitch; TL tlpwndSwitch; if (ghwndSwitch != NULL) { pwndSwitch = PW(ghwndSwitch); ThreadLock(pwndSwitch, &tlpwndSwitch); xxxSetForegroundWindow2(pwndSwitch, NULL, 0); // BUG BUG phk might go away IANJA ThreadUnlock(&tlpwndSwitch); } } /* * Get the hot key contents. */ if (phk->spwnd == NULL) { _PostThreadMessage(phk->pti, WM_HOTKEY, phk->id, MAKELONG(gfsModifiers, vk)); /* * Since this hotkey is for this guy, he owns the last input. */ glinp.ptiLastWoken = phk->pti; } else { if (phk->spwnd == PWND_INPUTOWNER) { if (gpqForeground != NULL) { pwnd = gpqForeground->spwndFocus; } else { return FALSE; } } else { pwnd = phk->spwnd; } if (pwnd) { if (pwnd == pwnd->head.rpdesk->pDeskInfo->spwndShell && phk->id == SC_TASKLIST) { PostTaskListSysCmd: _PostMessage(pwnd, WM_SYSCOMMAND, SC_TASKLIST, 0); } else { _PostMessage(pwnd, WM_HOTKEY, phk->id, MAKELONG(gfsModifiers, vk)); } /* * Since this hotkey is for this guy, he owns the last input. */ glinp.ptiLastWoken = GETPTI(pwnd); } } /* * If this is a Modifier-Only hotkey, let the modifier break through * by returning FALSE, otherwise we will have modifier keys stuck down. */ return (fsModOnlyHotkey == 0); } /***************************************************************************\ * IsHotKey * * * History: * 03-10-91 DavidPe Created. \***************************************************************************/ PHOTKEY IsHotKey( UINT fsModifiers, UINT vk) { PHOTKEY phk; CheckCritIn(); phk = HKGetHashHead(vk); while (phk != NULL) { /* * Do the modifiers and vk for this hotkey match the current state? */ if (phk->fsModifiers == fsModifiers && phk->vk == vk) { return phk; } phk = phk->phkNext; } return phk; }