2 * ReactOS W32 Subsystem
3 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 ReactOS Team
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.
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.
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.
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)
27 * 06-06-2001 CSH Created
28 * NOTE: Most of this code was adapted from Wine,
29 * Copyright (C) 2002 Alexandre Julliard
37 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
39 STATIC PHOOKTABLE GlobalHooks
;
41 /* create a new hook table */
42 STATIC FASTCALL PHOOKTABLE
43 IntAllocHookTable(void)
48 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
51 for (i
= 0; i
< NB_HOOKS
; i
++)
53 InitializeListHead(&Table
->Hooks
[i
]);
62 PHOOK FASTCALL
IntGetHookObject(HHOOK hHook
)
68 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
72 Hook
= (PHOOK
)UserGetObject(&gHandleTable
, hHook
, otHook
);
75 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
79 ASSERT(USER_BODY_TO_HEADER(Hook
)->RefCount
>= 0);
81 USER_BODY_TO_HEADER(Hook
)->RefCount
++;
88 /* create a new hook and add it to the specified table */
90 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
93 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
);
98 Table
= IntAllocHookTable();
109 MsqSetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
, Table
);
113 Hook
= ObmCreateObject(&gHandleTable
, &Handle
, otHook
, sizeof(HOOK
));
120 Hook
->Thread
= Thread
;
121 Hook
->HookId
= HookId
;
122 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
124 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
129 /* get the hook table that a given hook belongs to */
130 STATIC PHOOKTABLE FASTCALL
131 IntGetTable(PHOOK Hook
)
133 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
134 WH_MOUSE_LL
== Hook
->HookId
)
139 return MsqGetHooks(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
);
142 /* get the first hook in the chain */
143 STATIC PHOOK FASTCALL
144 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
146 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
147 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
148 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
151 /* find the first non-deleted hook in the chain */
152 STATIC PHOOK FASTCALL
153 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
158 Hook
= IntGetFirstHook(Table
, HookId
);
159 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
161 Elem
= Hook
->Chain
.Flink
;
162 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
163 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
169 /* find the next hook in the chain, skipping the deleted ones */
170 STATIC PHOOK FASTCALL
171 IntGetNextHook(PHOOK Hook
)
173 PHOOKTABLE Table
= IntGetTable(Hook
);
174 int HookId
= Hook
->HookId
;
177 Elem
= Hook
->Chain
.Flink
;
178 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
180 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
181 if (NULL
!= Hook
->Proc
)
187 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
189 return IntGetFirstValidHook(GlobalHooks
, HookId
);
195 /* free a hook, removing it from its chain */
197 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
199 RemoveEntryList(&Hook
->Chain
);
200 RtlFreeUnicodeString(&Hook
->ModuleName
);
202 /* Dereference thread if required */
203 if (Hook
->Flags
& HOOK_THREAD_REFERENCED
)
205 ObDereferenceObject(Hook
->Thread
);
209 ObmDeleteObject(Hook
->Self
, otHook
);
212 /* remove a hook, freeing it if the chain is not in use */
214 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
, BOOL TableAlreadyLocked
)
216 PHOOKTABLE Table
= IntGetTable(Hook
);
218 ASSERT(NULL
!= Table
);
224 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
226 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
230 IntFreeHook(Table
, Hook
, WinStaObj
);
234 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
236 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
246 /* use count shouldn't already be 0 */
247 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
248 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
252 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
254 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
255 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
257 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
259 if (NULL
== HookObj
->Proc
)
261 IntFreeHook(Table
, HookObj
, WinStaObj
);
267 static LRESULT FASTCALL
268 IntCallLowLevelHook(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
, PHOOK Hook
)
273 /* FIXME should get timeout from
274 * HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout */
275 Status
= co_MsqSendMessage(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
, (HWND
) Code
, HookId
,
276 wParam
, lParam
, 5000, TRUE
, TRUE
, &uResult
);
278 return NT_SUCCESS(Status
) ? uResult
: 0;
282 co_HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
285 PW32THREAD Win32Thread
;
288 PWINSTATION_OBJECT WinStaObj
;
291 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
293 Win32Thread
= PsGetWin32Thread();
294 if (NULL
== Win32Thread
)
300 Table
= MsqGetHooks(Win32Thread
->MessageQueue
);
303 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
305 /* try global table */
307 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
309 return 0; /* no hook set */
313 if (Hook
->Thread
!= PsGetCurrentThread()
314 && (WH_KEYBOARD_LL
== HookId
|| WH_MOUSE_LL
== HookId
))
316 DPRINT("Calling hook in owning thread\n");
317 return IntCallLowLevelHook(HookId
, Code
, wParam
, lParam
, Hook
);
320 if (Hook
->Thread
!= PsGetCurrentThread())
322 DPRINT1("Calling hooks in other threads not implemented yet");
326 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
327 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
329 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
332 Result
= co_IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
333 Hook
->Ansi
, &Hook
->ModuleName
);
335 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
340 if (! NT_SUCCESS(Status
))
342 DPRINT1("Invalid window station????\n");
346 IntReleaseHookChain(MsqGetHooks(PsGetWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
347 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
348 ObDereferenceObject(WinStaObj
);
355 HOOK_DestroyThreadHooks(PETHREAD Thread
)
360 PWINSTATION_OBJECT WinStaObj
;
363 if (NULL
!= GlobalHooks
)
365 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
370 if (! NT_SUCCESS(Status
))
372 DPRINT1("Invalid window station????\n");
376 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
378 /* only low-level keyboard/mouse global hooks can be owned by a thread */
383 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
384 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
386 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
388 if (HookObj
->Thread
== Thread
)
390 IntRemoveHook(HookObj
, WinStaObj
, TRUE
);
397 ObDereferenceObject(WinStaObj
);
403 NtUserCallNextHookEx(
409 PHOOK HookObj
, NextObj
;
410 PWINSTATION_OBJECT WinStaObj
;
412 DECLARE_RETURN(LRESULT
);
414 DPRINT("Enter NtUserCallNextHookEx\n");
415 UserEnterExclusive();
417 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
422 if (! NT_SUCCESS(Status
))
424 SetLastNtError(Status
);
428 //Status = ObmReferenceObjectByHandle(gHandleTable, Hook,
429 // otHookProc, (PVOID *) &HookObj);
430 ObDereferenceObject(WinStaObj
);
432 // if (! NT_SUCCESS(Status))
434 // DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
435 // SetLastNtError(Status);
439 if (!(HookObj
= IntGetHookObject(Hook
)))
444 ASSERT(Hook
== HookObj
->Self
);
446 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
448 DPRINT1("Thread mismatch\n");
449 ObmDereferenceObject(HookObj
);
450 SetLastWin32Error(ERROR_INVALID_HANDLE
);
454 NextObj
= IntGetNextHook(HookObj
);
455 ObmDereferenceObject(HookObj
);
458 DPRINT1("Calling next hook not implemented\n");
460 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
467 DPRINT("Leave NtUserCallNextHookEx, ret=%i\n",_ret_
);
474 NtUserSetWindowsHookAW(
486 NtUserSetWindowsHookEx(
488 PUNICODE_STRING UnsafeModuleName
,
494 PWINSTATION_OBJECT WinStaObj
;
498 UNICODE_STRING ModuleName
;
501 DECLARE_RETURN(HHOOK
);
503 DPRINT("Enter NtUserSetWindowsHookEx\n");
504 UserEnterExclusive();
506 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
508 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
512 if (ThreadId
) /* thread-local hook */
514 if (HookId
== WH_JOURNALRECORD
||
515 HookId
== WH_JOURNALPLAYBACK
||
516 HookId
== WH_KEYBOARD_LL
||
517 HookId
== WH_MOUSE_LL
||
518 HookId
== WH_SYSMSGFILTER
)
520 /* these can only be global */
521 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
526 if (! NT_SUCCESS(PsLookupThreadByThreadId((HANDLE
) ThreadId
, &Thread
)))
528 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
529 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
532 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
534 ObDereferenceObject(Thread
);
535 DPRINT1("Can't specify thread belonging to another process\n");
536 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
540 else /* system-global hook */
542 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
545 Thread
= PsGetCurrentThread();
546 Status
= ObReferenceObjectByPointer(Thread
,
551 if (! NT_SUCCESS(Status
))
553 SetLastNtError(Status
);
554 RETURN( (HANDLE
) NULL
);
557 else if (NULL
== Mod
)
559 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
569 /* We only (partially) support local WH_CBT hooks and
570 * WH_KEYBOARD_LL/WH_MOUSE_LL hooks for now */
571 if ((WH_CBT
!= HookId
|| Global
)
572 && WH_KEYBOARD_LL
!= HookId
&& WH_MOUSE_LL
!= HookId
)
574 #if 0 /* Removed to get winEmbed working again */
577 DPRINT1("Not implemented: HookId %d Global %s\n", HookId
, Global
? "TRUE" : "FALSE");
582 ObDereferenceObject(Thread
);
584 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
588 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
593 if (! NT_SUCCESS(Status
))
597 ObDereferenceObject(Thread
);
599 SetLastNtError(Status
);
600 RETURN( (HANDLE
) NULL
);
603 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
608 ObDereferenceObject(Thread
);
610 ObDereferenceObject(WinStaObj
);
616 Hook
->Flags
|= HOOK_THREAD_REFERENCED
;
621 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
622 if (! NT_SUCCESS(Status
))
624 ObmDereferenceObject(Hook
);
625 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
628 ObDereferenceObject(Thread
);
630 ObDereferenceObject(WinStaObj
);
631 SetLastNtError(Status
);
634 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
635 ModuleName
.MaximumLength
,
637 if (NULL
== Hook
->ModuleName
.Buffer
)
639 ObmDereferenceObject(Hook
);
640 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
643 ObDereferenceObject(Thread
);
645 ObDereferenceObject(WinStaObj
);
646 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
649 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
650 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
652 ModuleName
.MaximumLength
);
653 if (! NT_SUCCESS(Status
))
655 ObmDereferenceObject(Hook
);
656 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
659 ObDereferenceObject(Thread
);
661 ObDereferenceObject(WinStaObj
);
662 SetLastNtError(Status
);
665 Hook
->ModuleName
.Length
= ModuleName
.Length
;
668 Hook
->Proc
= HookProc
;
672 ObmDereferenceObject(Hook
);
673 ObDereferenceObject(WinStaObj
);
678 DPRINT("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_
);
685 NtUserSetWinEventHook(
702 NtUserUnhookWindowsHookEx(
705 PWINSTATION_OBJECT WinStaObj
;
708 DECLARE_RETURN(BOOL
);
710 DPRINT("Enter NtUserUnhookWindowsHookEx\n");
711 UserEnterExclusive();
713 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
718 if (! NT_SUCCESS(Status
))
720 SetLastNtError(Status
);
724 // Status = ObmReferenceObjectByHandle(gHandleTable, Hook,
725 // otHookProc, (PVOID *) &HookObj);
726 if (!(HookObj
= IntGetHookObject(Hook
)))
728 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
729 ObDereferenceObject(WinStaObj
);
730 // SetLastNtError(Status);
733 ASSERT(Hook
== HookObj
->Self
);
735 IntRemoveHook(HookObj
, WinStaObj
, FALSE
);
737 ObmDereferenceObject(HookObj
);
738 ObDereferenceObject(WinStaObj
);
743 DPRINT("Leave NtUserUnhookWindowsHookEx, ret=%i\n",_ret_
);
750 NtUserUnhookWinEvent(