87fadc9763162da09b4c1f73c12d993f679adedb
[reactos.git] / reactos / subsys / win32k / ntuser / hook.c
1 /*
2 * ReactOS W32 Subsystem
3 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 /* $Id$
20 *
21 * COPYRIGHT: See COPYING in the top level directory
22 * PROJECT: ReactOS kernel
23 * PURPOSE: Window hooks
24 * FILE: subsys/win32k/ntuser/hook.c
25 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
26 * REVISION HISTORY:
27 * 06-06-2001 CSH Created
28 * NOTE: Most of this code was adapted from Wine,
29 * Copyright (C) 2002 Alexandre Julliard
30 */
31
32 #include <w32k.h>
33
34 #define NDEBUG
35 #include <win32k/debug1.h>
36
37 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
38
39 STATIC PHOOKTABLE GlobalHooks;
40
41 /* create a new hook table */
42 STATIC FASTCALL PHOOKTABLE
43 IntAllocHookTable(void)
44 {
45 PHOOKTABLE Table;
46 UINT i;
47
48 Table = ExAllocatePoolWithTag(PagedPool, sizeof(HOOKTABLE), TAG_HOOK);
49 if (NULL != Table)
50 {
51 ExInitializeFastMutex(&Table->Lock);
52 for (i = 0; i < NB_HOOKS; i++)
53 {
54 InitializeListHead(&Table->Hooks[i]);
55 Table->Counts[i] = 0;
56 }
57 }
58
59 return Table;
60 }
61
62 /* create a new hook and add it to the specified table */
63 STATIC FASTCALL PHOOK
64 IntAddHook(PETHREAD Thread, int HookId, BOOLEAN Global, PWINSTATION_OBJECT WinStaObj)
65 {
66 PHOOK Hook;
67 PHOOKTABLE Table = Global ? GlobalHooks : MsqGetHooks(Thread->Tcb.Win32Thread->MessageQueue);
68 HANDLE Handle;
69
70 if (NULL == Table)
71 {
72 Table = IntAllocHookTable();
73 if (NULL == Table)
74 {
75 return NULL;
76 }
77 if (Global)
78 {
79 GlobalHooks = Table;
80 }
81 else
82 {
83 MsqSetHooks(Thread->Tcb.Win32Thread->MessageQueue, Table);
84 }
85 }
86
87 Hook = ObmCreateObject(WinStaObj->HandleTable, &Handle,
88 otHookProc, sizeof(HOOK));
89 if (NULL == Hook)
90 {
91 return NULL;
92 }
93
94 Hook->Self = Handle;
95 Hook->Thread = Thread;
96 Hook->HookId = HookId;
97 RtlInitUnicodeString(&Hook->ModuleName, NULL);
98
99 IntLockHookTable(Table);
100 InsertHeadList(&Table->Hooks[HOOKID_TO_INDEX(HookId)], &Hook->Chain);
101 IntUnLockHookTable(Table);
102
103 return Hook;
104 }
105
106 /* get the hook table that a given hook belongs to */
107 STATIC PHOOKTABLE FASTCALL
108 IntGetTable(PHOOK Hook)
109 {
110 if (NULL == Hook->Thread || WH_KEYBOARD_LL == Hook->HookId ||
111 WH_MOUSE_LL == Hook->HookId)
112 {
113 return GlobalHooks;
114 }
115
116 return MsqGetHooks(Hook->Thread->Tcb.Win32Thread->MessageQueue);
117 }
118
119 /* get the first hook in the chain */
120 STATIC PHOOK FASTCALL
121 IntGetFirstHook(PHOOKTABLE Table, int HookId)
122 {
123 PLIST_ENTRY Elem = Table->Hooks[HOOKID_TO_INDEX(HookId)].Flink;
124 return Elem == &Table->Hooks[HOOKID_TO_INDEX(HookId)]
125 ? NULL : CONTAINING_RECORD(Elem, HOOK, Chain);
126 }
127
128 /* find the first non-deleted hook in the chain */
129 STATIC PHOOK FASTCALL
130 IntGetFirstValidHook(PHOOKTABLE Table, int HookId)
131 {
132 PHOOK Hook;
133 PLIST_ENTRY Elem;
134
135 IntLockHookTable(Table);
136 Hook = IntGetFirstHook(Table, HookId);
137 while (NULL != Hook && NULL == Hook->Proc)
138 {
139 Elem = Hook->Chain.Flink;
140 Hook = (Elem == &Table->Hooks[HOOKID_TO_INDEX(HookId)]
141 ? NULL : CONTAINING_RECORD(Elem, HOOK, Chain));
142 }
143 IntUnLockHookTable(Table);
144
145 return Hook;
146 }
147
148 /* find the next hook in the chain, skipping the deleted ones */
149 STATIC PHOOK FASTCALL
150 IntGetNextHook(PHOOK Hook)
151 {
152 PHOOKTABLE Table = IntGetTable(Hook);
153 int HookId = Hook->HookId;
154 PLIST_ENTRY Elem;
155
156 IntLockHookTable(Table);
157 Elem = Hook->Chain.Flink;
158 while (Elem != &Table->Hooks[HOOKID_TO_INDEX(HookId)])
159 {
160 Hook = CONTAINING_RECORD(Elem, HOOK, Chain);
161 if (NULL != Hook->Proc)
162 {
163 IntUnLockHookTable(Table);
164 return Hook;
165 }
166 }
167 IntUnLockHookTable(Table);
168
169 if (NULL != GlobalHooks && Table != GlobalHooks) /* now search through the global table */
170 {
171 return IntGetFirstValidHook(GlobalHooks, HookId);
172 }
173
174 return NULL;
175 }
176
177 /* free a hook, removing it from its chain */
178 STATIC VOID FASTCALL
179 IntFreeHook(PHOOKTABLE Table, PHOOK Hook, PWINSTATION_OBJECT WinStaObj)
180 {
181 RemoveEntryList(&Hook->Chain);
182 RtlFreeUnicodeString(&Hook->ModuleName);
183
184 /* Dereference thread if required */
185 if (Hook->Flags & HOOK_THREAD_REFERENCED)
186 {
187 ObDereferenceObject(Hook->Thread);
188 }
189
190 /* Close handle */
191 ObmCloseHandle(WinStaObj->HandleTable, Hook->Self);
192 }
193
194 /* remove a hook, freeing it if the chain is not in use */
195 STATIC FASTCALL VOID
196 IntRemoveHook(PHOOK Hook, PWINSTATION_OBJECT WinStaObj, BOOL TableAlreadyLocked)
197 {
198 PHOOKTABLE Table = IntGetTable(Hook);
199
200 ASSERT(NULL != Table);
201 if (NULL == Table)
202 {
203 return;
204 }
205
206 if (! TableAlreadyLocked)
207 {
208 IntLockHookTable(Table);
209 }
210 if (0 != Table->Counts[HOOKID_TO_INDEX(Hook->HookId)])
211 {
212 Hook->Proc = NULL; /* chain is in use, just mark it and return */
213 }
214 else
215 {
216 IntFreeHook(Table, Hook, WinStaObj);
217 }
218 if (! TableAlreadyLocked)
219 {
220 IntUnLockHookTable(Table);
221 }
222 }
223
224 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
225 STATIC VOID FASTCALL
226 IntReleaseHookChain(PHOOKTABLE Table, int HookId, PWINSTATION_OBJECT WinStaObj)
227 {
228 PLIST_ENTRY Elem;
229 PHOOK HookObj;
230
231 if (NULL == Table)
232 {
233 return;
234 }
235
236 IntLockHookTable(Table);
237 /* use count shouldn't already be 0 */
238 ASSERT(0 != Table->Counts[HOOKID_TO_INDEX(HookId)]);
239 if (0 == Table->Counts[HOOKID_TO_INDEX(HookId)])
240 {
241 IntUnLockHookTable(Table);
242 return;
243 }
244 if (0 == --Table->Counts[HOOKID_TO_INDEX(HookId)])
245 {
246 Elem = Table->Hooks[HOOKID_TO_INDEX(HookId)].Flink;
247 while (Elem != &Table->Hooks[HOOKID_TO_INDEX(HookId)])
248 {
249 HookObj = CONTAINING_RECORD(Elem, HOOK, Chain);
250 Elem = Elem->Flink;
251 if (NULL == HookObj->Proc)
252 {
253 IntFreeHook(Table, HookObj, WinStaObj);
254 }
255 }
256 }
257 IntUnLockHookTable(Table);
258 }
259
260 static LRESULT FASTCALL
261 IntCallLowLevelHook(INT HookId, INT Code, WPARAM wParam, LPARAM lParam, PHOOK Hook)
262 {
263 NTSTATUS Status;
264 ULONG_PTR uResult;
265
266 /* FIXME should get timeout from
267 * HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout */
268 Status = MsqSendMessage(Hook->Thread->Tcb.Win32Thread->MessageQueue, (HWND) Code, HookId,
269 wParam, lParam, /*500*/0, TRUE, TRUE, &uResult);
270
271 return NT_SUCCESS(Status) ? uResult : 0;
272 }
273
274 LRESULT FASTCALL
275 HOOK_CallHooks(INT HookId, INT Code, WPARAM wParam, LPARAM lParam)
276 {
277 PHOOK Hook;
278 PW32THREAD Win32Thread;
279 PHOOKTABLE Table;
280 LRESULT Result;
281 PWINSTATION_OBJECT WinStaObj;
282 NTSTATUS Status;
283
284 ASSERT(WH_MINHOOK <= HookId && HookId <= WH_MAXHOOK);
285
286 Win32Thread = PsGetWin32Thread();
287 if (NULL == Win32Thread)
288 {
289 Table = NULL;
290 }
291 else
292 {
293 Table = MsqGetHooks(Win32Thread->MessageQueue);
294 }
295
296 if (NULL == Table || ! (Hook = IntGetFirstValidHook(Table, HookId)))
297 {
298 /* try global table */
299 Table = GlobalHooks;
300 if (NULL == Table || ! (Hook = IntGetFirstValidHook(Table, HookId)))
301 {
302 return 0; /* no hook set */
303 }
304 }
305
306 if (Hook->Thread != PsGetCurrentThread()
307 && (WH_KEYBOARD_LL == HookId || WH_MOUSE_LL == HookId))
308 {
309 DPRINT("Calling hook in owning thread\n");
310 return IntCallLowLevelHook(HookId, Code, wParam, lParam, Hook);
311 }
312
313 if (Hook->Thread != PsGetCurrentThread())
314 {
315 DPRINT1("Calling hooks in other threads not implemented yet");
316 return 0;
317 }
318
319 IntLockHookTable(Table);
320 Table->Counts[HOOKID_TO_INDEX(HookId)]++;
321 IntUnLockHookTable(Table);
322 if (Table != GlobalHooks && GlobalHooks != NULL)
323 {
324 IntLockHookTable(GlobalHooks);
325 GlobalHooks->Counts[HOOKID_TO_INDEX(HookId)]++;
326 IntUnLockHookTable(GlobalHooks);
327 }
328
329 Result = IntCallHookProc(HookId, Code, wParam, lParam, Hook->Proc,
330 Hook->Ansi, &Hook->ModuleName);
331
332 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
333 KernelMode,
334 0,
335 &WinStaObj);
336
337 if (! NT_SUCCESS(Status))
338 {
339 DPRINT1("Invalid window station????\n");
340 }
341 else
342 {
343 IntReleaseHookChain(MsqGetHooks(PsGetWin32Thread()->MessageQueue), HookId, WinStaObj);
344 IntReleaseHookChain(GlobalHooks, HookId, WinStaObj);
345 ObDereferenceObject(WinStaObj);
346 }
347
348 return Result;
349 }
350
351 VOID FASTCALL
352 HOOK_DestroyThreadHooks(PETHREAD Thread)
353 {
354 int HookId;
355 PLIST_ENTRY Elem;
356 PHOOK HookObj;
357 PWINSTATION_OBJECT WinStaObj;
358 NTSTATUS Status;
359
360 if (NULL != GlobalHooks)
361 {
362 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
363 KernelMode,
364 0,
365 &WinStaObj);
366
367 if (! NT_SUCCESS(Status))
368 {
369 DPRINT1("Invalid window station????\n");
370 return;
371 }
372 IntLockHookTable(GlobalHooks);
373 for (HookId = WH_MINHOOK; HookId <= WH_MAXHOOK; HookId++)
374 {
375 /* only low-level keyboard/mouse global hooks can be owned by a thread */
376 switch(HookId)
377 {
378 case WH_KEYBOARD_LL:
379 case WH_MOUSE_LL:
380 Elem = GlobalHooks->Hooks[HOOKID_TO_INDEX(HookId)].Flink;
381 while (Elem != &GlobalHooks->Hooks[HOOKID_TO_INDEX(HookId)])
382 {
383 HookObj = CONTAINING_RECORD(Elem, HOOK, Chain);
384 Elem = Elem->Flink;
385 if (HookObj->Thread == Thread)
386 {
387 IntRemoveHook(HookObj, WinStaObj, TRUE);
388 }
389 }
390 break;
391 }
392 }
393 IntUnLockHookTable(GlobalHooks);
394 ObDereferenceObject(WinStaObj);
395 }
396 }
397
398 LRESULT
399 STDCALL
400 NtUserCallNextHookEx(
401 HHOOK Hook,
402 int Code,
403 WPARAM wParam,
404 LPARAM lParam)
405 {
406 PHOOK HookObj, NextObj;
407 PWINSTATION_OBJECT WinStaObj;
408 NTSTATUS Status;
409
410 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
411 KernelMode,
412 0,
413 &WinStaObj);
414
415 if (! NT_SUCCESS(Status))
416 {
417 SetLastNtError(Status);
418 return FALSE;
419 }
420
421 Status = ObmReferenceObjectByHandle(WinStaObj->HandleTable, Hook,
422 otHookProc, (PVOID *) &HookObj);
423 ObDereferenceObject(WinStaObj);
424 if (! NT_SUCCESS(Status))
425 {
426 DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
427 SetLastNtError(Status);
428 return 0;
429 }
430 ASSERT(Hook == HookObj->Self);
431
432 if (NULL != HookObj->Thread && (HookObj->Thread != PsGetCurrentThread()))
433 {
434 DPRINT1("Thread mismatch\n");
435 ObmDereferenceObject(HookObj);
436 SetLastWin32Error(ERROR_INVALID_HANDLE);
437 return 0;
438 }
439
440 NextObj = IntGetNextHook(HookObj);
441 ObmDereferenceObject(HookObj);
442 if (NULL != NextObj)
443 {
444 DPRINT1("Calling next hook not implemented\n");
445 UNIMPLEMENTED
446 SetLastWin32Error(ERROR_NOT_SUPPORTED);
447 return 0;
448 }
449
450 return 0;
451 }
452
453 DWORD
454 STDCALL
455 NtUserSetWindowsHookAW(
456 DWORD Unknown0,
457 DWORD Unknown1,
458 DWORD Unknown2)
459 {
460 UNIMPLEMENTED
461
462 return 0;
463 }
464
465 HHOOK
466 STDCALL
467 NtUserSetWindowsHookEx(
468 HINSTANCE Mod,
469 PUNICODE_STRING UnsafeModuleName,
470 DWORD ThreadId,
471 int HookId,
472 HOOKPROC HookProc,
473 BOOL Ansi)
474 {
475 PWINSTATION_OBJECT WinStaObj;
476 BOOLEAN Global;
477 PETHREAD Thread;
478 PHOOK Hook;
479 UNICODE_STRING ModuleName;
480 NTSTATUS Status;
481 HHOOK Handle;
482
483 if (HookId < WH_MINHOOK || WH_MAXHOOK < HookId || NULL == HookProc)
484 {
485 SetLastWin32Error(ERROR_INVALID_PARAMETER);
486 return NULL;
487 }
488
489 if (ThreadId) /* thread-local hook */
490 {
491 if (HookId == WH_JOURNALRECORD ||
492 HookId == WH_JOURNALPLAYBACK ||
493 HookId == WH_KEYBOARD_LL ||
494 HookId == WH_MOUSE_LL ||
495 HookId == WH_SYSMSGFILTER)
496 {
497 /* these can only be global */
498 SetLastWin32Error(ERROR_INVALID_PARAMETER);
499 return NULL;
500 }
501 Mod = NULL;
502 Global = FALSE;
503 if (! NT_SUCCESS(PsLookupThreadByThreadId((HANDLE) ThreadId, &Thread)))
504 {
505 DPRINT1("Invalid thread id 0x%x\n", ThreadId);
506 SetLastWin32Error(ERROR_INVALID_PARAMETER);
507 return NULL;
508 }
509 if (Thread->ThreadsProcess != PsGetCurrentProcess())
510 {
511 ObDereferenceObject(Thread);
512 DPRINT1("Can't specify thread belonging to another process\n");
513 SetLastWin32Error(ERROR_INVALID_PARAMETER);
514 return NULL;
515 }
516 }
517 else /* system-global hook */
518 {
519 if (HookId == WH_KEYBOARD_LL || HookId == WH_MOUSE_LL)
520 {
521 Mod = NULL;
522 Thread = PsGetCurrentThread();
523 Status = ObReferenceObjectByPointer(Thread,
524 THREAD_ALL_ACCESS,
525 PsThreadType,
526 KernelMode);
527
528 if (! NT_SUCCESS(Status))
529 {
530 SetLastNtError(Status);
531 return (HANDLE) NULL;
532 }
533 }
534 else if (NULL == Mod)
535 {
536 SetLastWin32Error(ERROR_INVALID_PARAMETER);
537 return NULL;
538 }
539 else
540 {
541 Thread = NULL;
542 }
543 Global = TRUE;
544 }
545
546 /* We only (partially) support local WH_CBT hooks and
547 * WH_KEYBOARD_LL/WH_MOUSE_LL hooks for now */
548 if ((WH_CBT != HookId || Global)
549 && WH_KEYBOARD_LL != HookId && WH_MOUSE_LL != HookId)
550 {
551 #if 0 /* Removed to get winEmbed working again */
552 UNIMPLEMENTED
553 #else
554 DPRINT1("Not implemented: HookId %d Global %s\n", HookId, Global ? "TRUE" : "FALSE");
555 #endif
556 if (NULL != Thread)
557 {
558 ObDereferenceObject(Thread);
559 }
560 SetLastWin32Error(ERROR_NOT_SUPPORTED);
561 return NULL;
562 }
563
564 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
565 KernelMode,
566 0,
567 &WinStaObj);
568
569 if (! NT_SUCCESS(Status))
570 {
571 if (NULL != Thread)
572 {
573 ObDereferenceObject(Thread);
574 }
575 SetLastNtError(Status);
576 return (HANDLE) NULL;
577 }
578
579 Hook = IntAddHook(Thread, HookId, Global, WinStaObj);
580 if (NULL == Hook)
581 {
582 if (NULL != Thread)
583 {
584 ObDereferenceObject(Thread);
585 }
586 ObDereferenceObject(WinStaObj);
587 return NULL;
588 }
589
590 if (NULL != Thread)
591 {
592 Hook->Flags |= HOOK_THREAD_REFERENCED;
593 }
594
595 if (NULL != Mod)
596 {
597 Status = MmCopyFromCaller(&ModuleName, UnsafeModuleName, sizeof(UNICODE_STRING));
598 if (! NT_SUCCESS(Status))
599 {
600 ObmDereferenceObject(Hook);
601 IntRemoveHook(Hook, WinStaObj, FALSE);
602 if (NULL != Thread)
603 {
604 ObDereferenceObject(Thread);
605 }
606 ObDereferenceObject(WinStaObj);
607 SetLastNtError(Status);
608 return NULL;
609 }
610 Hook->ModuleName.Buffer = ExAllocatePoolWithTag(PagedPool,
611 ModuleName.MaximumLength,
612 TAG_HOOK);
613 if (NULL == Hook->ModuleName.Buffer)
614 {
615 ObmDereferenceObject(Hook);
616 IntRemoveHook(Hook, WinStaObj, FALSE);
617 if (NULL != Thread)
618 {
619 ObDereferenceObject(Thread);
620 }
621 ObDereferenceObject(WinStaObj);
622 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY);
623 return NULL;
624 }
625 Hook->ModuleName.MaximumLength = ModuleName.MaximumLength;
626 Status = MmCopyFromCaller(Hook->ModuleName.Buffer,
627 ModuleName.Buffer,
628 ModuleName.MaximumLength);
629 if (! NT_SUCCESS(Status))
630 {
631 ObmDereferenceObject(Hook);
632 IntRemoveHook(Hook, WinStaObj, FALSE);
633 if (NULL != Thread)
634 {
635 ObDereferenceObject(Thread);
636 }
637 ObDereferenceObject(WinStaObj);
638 SetLastNtError(Status);
639 return NULL;
640 }
641 Hook->ModuleName.Length = ModuleName.Length;
642 }
643
644 Hook->Proc = HookProc;
645 Hook->Ansi = Ansi;
646 Handle = Hook->Self;
647
648 ObmDereferenceObject(Hook);
649 ObDereferenceObject(WinStaObj);
650
651 return Handle;
652 }
653
654 DWORD
655 STDCALL
656 NtUserSetWinEventHook(
657 DWORD Unknown0,
658 DWORD Unknown1,
659 DWORD Unknown2,
660 DWORD Unknown3,
661 DWORD Unknown4,
662 DWORD Unknown5,
663 DWORD Unknown6,
664 DWORD Unknown7)
665 {
666 UNIMPLEMENTED
667
668 return 0;
669 }
670
671 BOOL
672 STDCALL
673 NtUserUnhookWindowsHookEx(
674 HHOOK Hook)
675 {
676 PWINSTATION_OBJECT WinStaObj;
677 PHOOK HookObj;
678 NTSTATUS Status;
679
680 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
681 KernelMode,
682 0,
683 &WinStaObj);
684
685 if (! NT_SUCCESS(Status))
686 {
687 SetLastNtError(Status);
688 return FALSE;
689 }
690
691 Status = ObmReferenceObjectByHandle(WinStaObj->HandleTable, Hook,
692 otHookProc, (PVOID *) &HookObj);
693 if (! NT_SUCCESS(Status))
694 {
695 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
696 ObDereferenceObject(WinStaObj);
697 SetLastNtError(Status);
698 return FALSE;
699 }
700 ASSERT(Hook == HookObj->Self);
701
702 IntRemoveHook(HookObj, WinStaObj, FALSE);
703
704 ObmDereferenceObject(HookObj);
705 ObDereferenceObject(WinStaObj);
706
707 return TRUE;
708 }
709
710 DWORD
711 STDCALL
712 NtUserUnhookWinEvent(
713 DWORD Unknown0)
714 {
715 UNIMPLEMENTED
716
717 return 0;
718 }
719
720 /* EOF */