xref: /reactos/win32ss/user/ntuser/hotkey.c (revision 2ea03b5b)
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 programmatically)
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
StartDebugHotKeys(VOID)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 enabled 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
IntGetModifiers(PBYTE pKeyState)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
UnregisterWindowHotKeys(PWND pWnd)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
UnregisterThreadHotKeys(PTHREADINFO pti)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
IsHotKey(UINT fsModifiers,WORD wVk)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
co_UserProcessHotKeys(WORD wVk,BOOL bIsDown)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 --- GetHotKey message support
309  *
310  * Win: DWP_GetHotKey
311  */
312 UINT FASTCALL
DefWndGetHotKey(PWND pWnd)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 --- SetHotKey message support
336  *
337  * Win: DWP_SetHotKey
338  */
339 INT FASTCALL
DefWndSetHotKey(PWND pWnd,WPARAM wParam)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
UserRegisterHotKey(PWND pWnd,int id,UINT fsModifiers,UINT vk)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
UserUnregisterHotKey(PWND pWnd,int id)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
NtUserRegisterHotKey(HWND hWnd,int id,UINT fsModifiers,UINT vk)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
NtUserUnregisterHotKey(HWND hWnd,int id)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