f393906b20456a469121031756de81503e8ca8aa
[reactos.git] / win32ss / user / ntuser / hotkey.c
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->pWnd)
255 {
256 TRACE("UPTM Hot key Id %d Key %u\n", pHotKey->id, wVk );
257 UserPostThreadMessage(pHotKey->pti, WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk));
258 //ptiLastInput = pHotKey->pti;
259 return TRUE; /* Don't send any message */
260 }
261 else
262 {
263 pWnd = pHotKey->pWnd;
264 if (pWnd == PWND_BOTTOM)
265 {
266 if (gpqForeground == NULL)
267 return FALSE;
268
269 pWnd = gpqForeground->spwndFocus;
270 }
271
272 if (pWnd)
273 {
274 // pWnd->head.rpdesk->pDeskInfo->spwndShell needs testing.
275 if (pWnd == ValidateHwndNoErr(InputWindowStation->ShellWindow) && pHotKey->id == SC_TASKLIST)
276 {
277 UserPostMessage(UserHMGetHandle(pWnd), WM_SYSCOMMAND, SC_TASKLIST, 0);
278 co_IntShellHookNotify(HSHELL_TASKMAN, 0, 0);
279 }
280 else
281 {
282 TRACE("UPM Hot key Id %d Key %u\n", pHotKey->id, wVk );
283 UserPostMessage(UserHMGetHandle(pWnd), WM_HOTKEY, pHotKey->id, MAKELONG(fModifiers, wVk));
284 }
285 //ptiLastInput = pWnd->head.pti;
286 return TRUE; /* Don't send any message */
287 }
288 }
289 }
290 return FALSE;
291 }
292
293
294 /*
295 * DefWndGetHotKey
296 *
297 * GetHotKey message support
298 */
299 UINT FASTCALL
300 DefWndGetHotKey(PWND pWnd)
301 {
302 PHOT_KEY pHotKey = gphkFirst;
303
304 WARN("DefWndGetHotKey\n");
305
306 while (pHotKey)
307 {
308 if (pHotKey->pWnd == pWnd && pHotKey->id == IDHK_REACTOS)
309 {
310 /* We have found it */
311 return MAKELONG(pHotKey->vk, pHotKey->fsModifiers);
312 }
313
314 /* Move to the next entry */
315 pHotKey = pHotKey->pNext;
316 }
317
318 return 0;
319 }
320
321 /*
322 * DefWndSetHotKey
323 *
324 * SetHotKey message support
325 */
326 INT FASTCALL
327 DefWndSetHotKey(PWND pWnd, WPARAM wParam)
328 {
329 UINT fsModifiers, vk;
330 PHOT_KEY pHotKey, *pLink;
331 INT iRet = 1;
332
333 WARN("DefWndSetHotKey wParam 0x%x\n", wParam);
334
335 // A hot key cannot be associated with a child window.
336 if (pWnd->style & WS_CHILD)
337 return 0;
338
339 // VK_ESCAPE, VK_SPACE, and VK_TAB are invalid hot keys.
340 if (LOWORD(wParam) == VK_ESCAPE ||
341 LOWORD(wParam) == VK_SPACE ||
342 LOWORD(wParam) == VK_TAB)
343 {
344 return -1;
345 }
346
347 vk = LOWORD(wParam);
348 fsModifiers = HIWORD(wParam);
349
350 if (wParam)
351 {
352 pHotKey = gphkFirst;
353 while (pHotKey)
354 {
355 if (pHotKey->fsModifiers == fsModifiers &&
356 pHotKey->vk == vk &&
357 pHotKey->id == IDHK_REACTOS)
358 {
359 if (pHotKey->pWnd != pWnd)
360 iRet = 2; // Another window already has the same hot key.
361 break;
362 }
363
364 /* Move to the next entry */
365 pHotKey = pHotKey->pNext;
366 }
367 }
368
369 pHotKey = gphkFirst;
370 pLink = &gphkFirst;
371 while (pHotKey)
372 {
373 if (pHotKey->pWnd == pWnd &&
374 pHotKey->id == IDHK_REACTOS)
375 {
376 /* This window has already hotkey registered */
377 break;
378 }
379
380 /* Move to the next entry */
381 pLink = &pHotKey->pNext;
382 pHotKey = pHotKey->pNext;
383 }
384
385 if (wParam)
386 {
387 if (!pHotKey)
388 {
389 /* Create new hotkey */
390 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
391 if (pHotKey == NULL)
392 return 0;
393
394 pHotKey->pWnd = pWnd;
395 pHotKey->id = IDHK_REACTOS; // Don't care, these hot keys are unrelated to the hot keys set by RegisterHotKey
396 pHotKey->pNext = gphkFirst;
397 gphkFirst = pHotKey;
398 }
399
400 /* A window can only have one hot key. If the window already has a
401 hot key associated with it, the new hot key replaces the old one. */
402 pHotKey->pti = NULL;
403 pHotKey->fsModifiers = fsModifiers;
404 pHotKey->vk = vk;
405 }
406 else if (pHotKey)
407 {
408 /* Remove hotkey */
409 *pLink = pHotKey->pNext;
410 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
411 }
412
413 return iRet;
414 }
415
416
417 BOOL FASTCALL
418 UserRegisterHotKey(PWND pWnd,
419 int id,
420 UINT fsModifiers,
421 UINT vk)
422 {
423 PHOT_KEY pHotKey;
424 PTHREADINFO pHotKeyThread;
425
426 /* Find hotkey thread */
427 if (pWnd == NULL || pWnd == PWND_BOTTOM)
428 {
429 pHotKeyThread = PsGetCurrentThreadWin32Thread();
430 }
431 else
432 {
433 pHotKeyThread = pWnd->head.pti;
434 }
435
436 /* Check for existing hotkey */
437 if (IsHotKey(fsModifiers, vk))
438 {
439 EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED);
440 WARN("Hotkey already exists\n");
441 return FALSE;
442 }
443
444 /* Create new hotkey */
445 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
446 if (pHotKey == NULL)
447 {
448 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
449 return FALSE;
450 }
451
452 pHotKey->pti = pHotKeyThread;
453 pHotKey->pWnd = pWnd;
454 pHotKey->fsModifiers = fsModifiers;
455 pHotKey->vk = vk;
456 pHotKey->id = id;
457
458 /* Insert hotkey to the global list */
459 pHotKey->pNext = gphkFirst;
460 gphkFirst = pHotKey;
461
462 return TRUE;
463 }
464
465 BOOL FASTCALL
466 UserUnregisterHotKey(PWND pWnd, int id)
467 {
468 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;
469 BOOL bRet = FALSE;
470
471 while (pHotKey)
472 {
473 /* Save next ptr for later use */
474 phkNext = pHotKey->pNext;
475
476 /* Should we delete this hotkey? */
477 if (pHotKey->pWnd == pWnd && pHotKey->id == id)
478 {
479 /* Update next ptr for previous hotkey and free memory */
480 *pLink = phkNext;
481 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
482
483 bRet = TRUE;
484 }
485 else /* This hotkey will stay, use its next ptr */
486 pLink = &pHotKey->pNext;
487
488 /* Move to the next entry */
489 pHotKey = phkNext;
490 }
491 return bRet;
492 }
493
494
495 /* SYSCALLS *****************************************************************/
496
497
498 BOOL APIENTRY
499 NtUserRegisterHotKey(HWND hWnd,
500 int id,
501 UINT fsModifiers,
502 UINT vk)
503 {
504 PHOT_KEY pHotKey;
505 PWND pWnd = NULL;
506 PTHREADINFO pHotKeyThread;
507 BOOL bRet = FALSE;
508
509 TRACE("Enter NtUserRegisterHotKey\n");
510
511 if (fsModifiers & ~(MOD_ALT|MOD_CONTROL|MOD_SHIFT|MOD_WIN)) // FIXME: Does Win2k3 support MOD_NOREPEAT?
512 {
513 WARN("Invalid modifiers: %x\n", fsModifiers);
514 EngSetLastError(ERROR_INVALID_FLAGS);
515 return 0;
516 }
517
518 UserEnterExclusive();
519
520 /* Find hotkey thread */
521 if (hWnd == NULL)
522 {
523 pHotKeyThread = gptiCurrent;
524 }
525 else
526 {
527 pWnd = UserGetWindowObject(hWnd);
528 if (!pWnd)
529 goto cleanup;
530
531 pHotKeyThread = pWnd->head.pti;
532
533 /* Fix wine msg "Window on another thread" test_hotkey */
534 if (pWnd->head.pti != gptiCurrent)
535 {
536 EngSetLastError(ERROR_WINDOW_OF_OTHER_THREAD);
537 WARN("Must be from the same Thread.\n");
538 goto cleanup;
539 }
540 }
541
542 /* Check for existing hotkey */
543 if (IsHotKey(fsModifiers, vk))
544 {
545 EngSetLastError(ERROR_HOTKEY_ALREADY_REGISTERED);
546 WARN("Hotkey already exists\n");
547 goto cleanup;
548 }
549
550 /* Create new hotkey */
551 pHotKey = ExAllocatePoolWithTag(PagedPool, sizeof(HOT_KEY), USERTAG_HOTKEY);
552 if (pHotKey == NULL)
553 {
554 EngSetLastError(ERROR_NOT_ENOUGH_MEMORY);
555 goto cleanup;
556 }
557
558 pHotKey->pti = pHotKeyThread;
559 pHotKey->pWnd = pWnd;
560 pHotKey->fsModifiers = fsModifiers;
561 pHotKey->vk = vk;
562 pHotKey->id = id;
563
564 /* Insert hotkey to the global list */
565 pHotKey->pNext = gphkFirst;
566 gphkFirst = pHotKey;
567
568 bRet = TRUE;
569
570 cleanup:
571 TRACE("Leave NtUserRegisterHotKey, ret=%i\n", bRet);
572 UserLeave();
573 return bRet;
574 }
575
576
577 BOOL APIENTRY
578 NtUserUnregisterHotKey(HWND hWnd, int id)
579 {
580 PHOT_KEY pHotKey = gphkFirst, phkNext, *pLink = &gphkFirst;
581 BOOL bRet = FALSE;
582 PWND pWnd = NULL;
583
584 TRACE("Enter NtUserUnregisterHotKey\n");
585 UserEnterExclusive();
586
587 /* Fail if given window is invalid */
588 if (hWnd && !(pWnd = UserGetWindowObject(hWnd)))
589 goto cleanup;
590
591 while (pHotKey)
592 {
593 /* Save next ptr for later use */
594 phkNext = pHotKey->pNext;
595
596 /* Should we delete this hotkey? */
597 if (pHotKey->pWnd == pWnd && pHotKey->id == id)
598 {
599 /* Update next ptr for previous hotkey and free memory */
600 *pLink = phkNext;
601 ExFreePoolWithTag(pHotKey, USERTAG_HOTKEY);
602
603 bRet = TRUE;
604 }
605 else /* This hotkey will stay, use its next ptr */
606 pLink = &pHotKey->pNext;
607
608 /* Move to the next entry */
609 pHotKey = phkNext;
610 }
611
612 cleanup:
613 TRACE("Leave NtUserUnregisterHotKey, ret=%i\n", bRet);
614 UserLeave();
615 return bRet;
616 }
617
618 /* EOF */