[NtUser] Fix Theme Non Client Painting.
[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->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 */