2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * PURPOSE: Window hooks
5 * FILE: subsystem/win32/win32k/ntuser/hook.c
6 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
8 * 06-06-2001 CSH Created
9 * NOTE: Most of this code was adapted from Wine,
10 * Copyright (C) 2002 Alexandre Julliard
18 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
19 #define HOOKID_TO_FLAG(HookId) (1 << ((HookId) + 1))
21 static PHOOKTABLE GlobalHooks
;
24 /* PRIVATE FUNCTIONS *********************************************************/
27 /* create a new hook table */
29 IntAllocHookTable(void)
34 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
37 for (i
= 0; i
< NB_HOOKS
; i
++)
39 InitializeListHead(&Table
->Hooks
[i
]);
48 PHOOK FASTCALL
IntGetHookObject(HHOOK hHook
)
54 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
58 Hook
= (PHOOK
)UserGetObject(gHandleTable
, hHook
, otHook
);
61 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
65 ASSERT(USER_BODY_TO_HEADER(Hook
)->RefCount
>= 0);
67 USER_BODY_TO_HEADER(Hook
)->RefCount
++;
74 /* create a new hook and add it to the specified table */
76 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
80 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
);
85 Table
= IntAllocHookTable();
96 MsqSetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
, Table
);
100 Hook
= UserCreateObject(gHandleTable
, &Handle
, otHook
, sizeof(HOOK
));
107 Hook
->Thread
= Thread
;
108 Hook
->HookId
= HookId
;
110 W32Thread
= ((PW32THREAD
)Thread
->Tcb
.Win32Thread
);
111 ASSERT(W32Thread
!= NULL
);
112 W32Thread
->Hooks
|= HOOKID_TO_FLAG(HookId
);
113 if (W32Thread
->ThreadInfo
!= NULL
)
114 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
116 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
118 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
123 /* get the hook table that a given hook belongs to */
124 static PHOOKTABLE FASTCALL
125 IntGetTable(PHOOK Hook
)
127 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
128 WH_MOUSE_LL
== Hook
->HookId
)
133 return MsqGetHooks(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
);
136 /* get the first hook in the chain */
137 static PHOOK FASTCALL
138 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
140 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
141 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
142 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
145 /* find the first non-deleted hook in the chain */
146 static PHOOK FASTCALL
147 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
152 Hook
= IntGetFirstHook(Table
, HookId
);
153 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
155 Elem
= Hook
->Chain
.Flink
;
156 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
157 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
163 /* find the next hook in the chain, skipping the deleted ones */
164 static PHOOK FASTCALL
165 IntGetNextHook(PHOOK Hook
)
167 PHOOKTABLE Table
= IntGetTable(Hook
);
168 int HookId
= Hook
->HookId
;
171 Elem
= Hook
->Chain
.Flink
;
172 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
174 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
175 if (NULL
!= Hook
->Proc
)
181 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
183 return IntGetFirstValidHook(GlobalHooks
, HookId
);
189 /* free a hook, removing it from its chain */
191 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
193 RemoveEntryList(&Hook
->Chain
);
194 RtlFreeUnicodeString(&Hook
->ModuleName
);
196 /* Dereference thread if required */
197 if (Hook
->Flags
& HOOK_THREAD_REFERENCED
)
199 ObDereferenceObject(Hook
->Thread
);
203 UserDeleteObject(Hook
->Self
, otHook
);
206 /* remove a hook, freeing it if the chain is not in use */
208 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
, BOOL TableAlreadyLocked
)
210 PW32THREAD W32Thread
;
211 PHOOKTABLE Table
= IntGetTable(Hook
);
213 ASSERT(NULL
!= Table
);
219 W32Thread
= ((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
);
220 ASSERT(W32Thread
!= NULL
);
221 W32Thread
->Hooks
&= ~HOOKID_TO_FLAG(Hook
->HookId
);
222 if (W32Thread
->ThreadInfo
!= NULL
)
223 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
225 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
227 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
231 IntFreeHook(Table
, Hook
, WinStaObj
);
235 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
237 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
247 /* use count shouldn't already be 0 */
248 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
249 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
253 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
255 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
256 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
258 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
260 if (NULL
== HookObj
->Proc
)
262 IntFreeHook(Table
, HookObj
, WinStaObj
);
268 static LRESULT FASTCALL
269 IntCallLowLevelHook(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
, PHOOK Hook
)
274 /* FIXME should get timeout from
275 * HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout */
276 Status
= co_MsqSendMessage(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
, (HWND
) Code
, HookId
,
277 wParam
, lParam
, 5000, TRUE
, TRUE
, &uResult
);
279 return NT_SUCCESS(Status
) ? uResult
: 0;
283 co_HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
286 PW32THREAD Win32Thread
;
289 PWINSTATION_OBJECT WinStaObj
;
292 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
294 Win32Thread
= PsGetCurrentThreadWin32Thread();
295 if (NULL
== Win32Thread
)
301 Table
= MsqGetHooks(Win32Thread
->MessageQueue
);
304 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
306 /* try global table */
308 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
310 return 0; /* no hook set */
314 if (Hook
->Thread
!= PsGetCurrentThread()
315 && (WH_KEYBOARD_LL
== HookId
|| WH_MOUSE_LL
== HookId
))
317 DPRINT("Calling hook in owning thread\n");
318 return IntCallLowLevelHook(HookId
, Code
, wParam
, lParam
, Hook
);
321 if (Hook
->Thread
!= PsGetCurrentThread())
323 DPRINT1("Calling hooks in other threads not implemented yet");
327 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
328 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
330 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
333 Result
= co_IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
334 Hook
->Ansi
, &Hook
->ModuleName
);
336 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
341 if (! NT_SUCCESS(Status
))
343 DPRINT1("Invalid window station????\n");
347 IntReleaseHookChain(MsqGetHooks(PsGetCurrentThreadWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
348 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
349 ObDereferenceObject(WinStaObj
);
356 HOOK_DestroyThreadHooks(PETHREAD Thread
)
361 PWINSTATION_OBJECT WinStaObj
;
364 if (NULL
!= GlobalHooks
)
366 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
371 if (! NT_SUCCESS(Status
))
373 DPRINT1("Invalid window station????\n");
377 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
379 /* only low-level keyboard/mouse global hooks can be owned by a thread */
384 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
385 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
387 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
389 if (HookObj
->Thread
== Thread
)
391 IntRemoveHook(HookObj
, WinStaObj
, TRUE
);
398 ObDereferenceObject(WinStaObj
);
404 NtUserCallNextHookEx(
410 PHOOK HookObj
, NextObj
;
411 PWINSTATION_OBJECT WinStaObj
;
413 DECLARE_RETURN(LRESULT
);
415 DPRINT("Enter NtUserCallNextHookEx\n");
416 UserEnterExclusive();
418 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
423 if (! NT_SUCCESS(Status
))
425 SetLastNtError(Status
);
429 //Status = UserReferenceObjectByHandle(gHandleTable, Hook,
430 // otHookProc, (PVOID *) &HookObj);
431 ObDereferenceObject(WinStaObj
);
433 // if (! NT_SUCCESS(Status))
435 // DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
436 // SetLastNtError(Status);
440 if (!(HookObj
= IntGetHookObject(Hook
)))
445 ASSERT(Hook
== HookObj
->Self
);
447 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
449 DPRINT1("Thread mismatch\n");
450 UserDereferenceObject(HookObj
);
451 SetLastWin32Error(ERROR_INVALID_HANDLE
);
455 NextObj
= IntGetNextHook(HookObj
);
456 UserDereferenceObject(HookObj
);
459 DPRINT1("Calling next hook not implemented\n");
461 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
468 DPRINT("Leave NtUserCallNextHookEx, ret=%i\n",_ret_
);
475 NtUserSetWindowsHookAW(
480 UNICODE_STRING USModuleName
;
481 RtlInitUnicodeString(&USModuleName
, NULL
);
482 return NtUserSetWindowsHookEx(NULL
, &USModuleName
, 0, idHook
, lpfn
, Ansi
);
487 NtUserSetWindowsHookEx(
489 PUNICODE_STRING UnsafeModuleName
,
495 PWINSTATION_OBJECT WinStaObj
;
499 UNICODE_STRING ModuleName
;
502 DECLARE_RETURN(HHOOK
);
504 DPRINT("Enter NtUserSetWindowsHookEx\n");
505 UserEnterExclusive();
507 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
509 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
513 if (ThreadId
) /* thread-local hook */
515 if (HookId
== WH_JOURNALRECORD
||
516 HookId
== WH_JOURNALPLAYBACK
||
517 HookId
== WH_KEYBOARD_LL
||
518 HookId
== WH_MOUSE_LL
||
519 HookId
== WH_SYSMSGFILTER
)
521 /* these can only be global */
522 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
527 if (! NT_SUCCESS(PsLookupThreadByThreadId((HANDLE
) ThreadId
, &Thread
)))
529 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
530 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
533 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
535 ObDereferenceObject(Thread
);
536 DPRINT1("Can't specify thread belonging to another process\n");
537 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
541 else /* system-global hook */
543 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
546 Thread
= PsGetCurrentThread();
547 Status
= ObReferenceObjectByPointer(Thread
,
552 if (! NT_SUCCESS(Status
))
554 SetLastNtError(Status
);
555 RETURN( (HANDLE
) NULL
);
558 else if (NULL
== Mod
)
560 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
570 /* We only (partially) support local WH_CBT hooks and
571 * WH_KEYBOARD_LL/WH_MOUSE_LL hooks for now */
572 if ((WH_CBT
!= HookId
|| Global
)
573 && WH_KEYBOARD_LL
!= HookId
&& WH_MOUSE_LL
!= HookId
)
575 #if 0 /* Removed to get winEmbed working again */
578 DPRINT1("Not implemented: HookId %d Global %s\n", HookId
, Global
? "TRUE" : "FALSE");
583 ObDereferenceObject(Thread
);
585 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
589 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
594 if (! NT_SUCCESS(Status
))
598 ObDereferenceObject(Thread
);
600 SetLastNtError(Status
);
601 RETURN( (HANDLE
) NULL
);
604 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
609 ObDereferenceObject(Thread
);
611 ObDereferenceObject(WinStaObj
);
617 Hook
->Flags
|= HOOK_THREAD_REFERENCED
;
622 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
623 if (! NT_SUCCESS(Status
))
625 UserDereferenceObject(Hook
);
626 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
629 ObDereferenceObject(Thread
);
631 ObDereferenceObject(WinStaObj
);
632 SetLastNtError(Status
);
635 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
636 ModuleName
.MaximumLength
,
638 if (NULL
== Hook
->ModuleName
.Buffer
)
640 UserDereferenceObject(Hook
);
641 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
644 ObDereferenceObject(Thread
);
646 ObDereferenceObject(WinStaObj
);
647 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
650 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
651 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
653 ModuleName
.MaximumLength
);
654 if (! NT_SUCCESS(Status
))
656 ExFreePool(Hook
->ModuleName
.Buffer
);
657 UserDereferenceObject(Hook
);
658 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
661 ObDereferenceObject(Thread
);
663 ObDereferenceObject(WinStaObj
);
664 SetLastNtError(Status
);
667 Hook
->ModuleName
.Length
= ModuleName
.Length
;
670 Hook
->Proc
= HookProc
;
674 UserDereferenceObject(Hook
);
675 ObDereferenceObject(WinStaObj
);
680 DPRINT("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_
);
688 NtUserUnhookWindowsHookEx(
691 PWINSTATION_OBJECT WinStaObj
;
694 DECLARE_RETURN(BOOL
);
696 DPRINT("Enter NtUserUnhookWindowsHookEx\n");
697 UserEnterExclusive();
699 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
704 if (! NT_SUCCESS(Status
))
706 SetLastNtError(Status
);
710 // Status = UserReferenceObjectByHandle(gHandleTable, Hook,
711 // otHookProc, (PVOID *) &HookObj);
712 if (!(HookObj
= IntGetHookObject(Hook
)))
714 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
715 ObDereferenceObject(WinStaObj
);
716 // SetLastNtError(Status);
719 ASSERT(Hook
== HookObj
->Self
);
721 IntRemoveHook(HookObj
, WinStaObj
, FALSE
);
723 UserDereferenceObject(HookObj
);
724 ObDereferenceObject(WinStaObj
);
729 DPRINT("Leave NtUserUnhookWindowsHookEx, ret=%i\n",_ret_
);