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