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)
38 #define HOOKID_TO_FLAG(HookId) (1 << ((HookId) + 1))
40 static PHOOKTABLE GlobalHooks
;
42 /* create a new hook table */
44 IntAllocHookTable(void)
49 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
52 for (i
= 0; i
< NB_HOOKS
; i
++)
54 InitializeListHead(&Table
->Hooks
[i
]);
63 PHOOK FASTCALL
IntGetHookObject(HHOOK hHook
)
69 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
73 Hook
= (PHOOK
)UserGetObject(gHandleTable
, hHook
, otHook
);
76 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
80 ASSERT(USER_BODY_TO_HEADER(Hook
)->RefCount
>= 0);
82 USER_BODY_TO_HEADER(Hook
)->RefCount
++;
89 /* create a new hook and add it to the specified table */
91 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
95 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
);
100 Table
= IntAllocHookTable();
111 MsqSetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
, Table
);
115 Hook
= ObmCreateObject(gHandleTable
, &Handle
, otHook
, sizeof(HOOK
));
122 Hook
->Thread
= Thread
;
123 Hook
->HookId
= HookId
;
125 W32Thread
= ((PW32THREAD
)Thread
->Tcb
.Win32Thread
);
126 ASSERT(W32Thread
!= NULL
);
127 W32Thread
->Hooks
|= HOOKID_TO_FLAG(HookId
);
128 if (W32Thread
->ThreadInfo
!= NULL
)
129 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
131 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
133 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
138 /* get the hook table that a given hook belongs to */
139 static PHOOKTABLE FASTCALL
140 IntGetTable(PHOOK Hook
)
142 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
143 WH_MOUSE_LL
== Hook
->HookId
)
148 return MsqGetHooks(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
);
151 /* get the first hook in the chain */
152 static PHOOK FASTCALL
153 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
155 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
156 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
157 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
160 /* find the first non-deleted hook in the chain */
161 static PHOOK FASTCALL
162 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
167 Hook
= IntGetFirstHook(Table
, HookId
);
168 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
170 Elem
= Hook
->Chain
.Flink
;
171 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
172 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
178 /* find the next hook in the chain, skipping the deleted ones */
179 static PHOOK FASTCALL
180 IntGetNextHook(PHOOK Hook
)
182 PHOOKTABLE Table
= IntGetTable(Hook
);
183 int HookId
= Hook
->HookId
;
186 Elem
= Hook
->Chain
.Flink
;
187 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
189 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
190 if (NULL
!= Hook
->Proc
)
196 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
198 return IntGetFirstValidHook(GlobalHooks
, HookId
);
204 /* free a hook, removing it from its chain */
206 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
208 RemoveEntryList(&Hook
->Chain
);
209 RtlFreeUnicodeString(&Hook
->ModuleName
);
211 /* Dereference thread if required */
212 if (Hook
->Flags
& HOOK_THREAD_REFERENCED
)
214 ObDereferenceObject(Hook
->Thread
);
218 ObmDeleteObject(Hook
->Self
, otHook
);
221 /* remove a hook, freeing it if the chain is not in use */
223 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
, BOOL TableAlreadyLocked
)
225 PW32THREAD W32Thread
;
226 PHOOKTABLE Table
= IntGetTable(Hook
);
228 ASSERT(NULL
!= Table
);
234 W32Thread
= ((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
);
235 ASSERT(W32Thread
!= NULL
);
236 W32Thread
->Hooks
&= ~HOOKID_TO_FLAG(Hook
->HookId
);
237 if (W32Thread
->ThreadInfo
!= NULL
)
238 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
240 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
242 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
246 IntFreeHook(Table
, Hook
, WinStaObj
);
250 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
252 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
262 /* use count shouldn't already be 0 */
263 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
264 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
268 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
270 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
271 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
273 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
275 if (NULL
== HookObj
->Proc
)
277 IntFreeHook(Table
, HookObj
, WinStaObj
);
283 static LRESULT FASTCALL
284 IntCallLowLevelHook(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
, PHOOK Hook
)
289 /* FIXME should get timeout from
290 * HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout */
291 Status
= co_MsqSendMessage(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
, (HWND
) Code
, HookId
,
292 wParam
, lParam
, 5000, TRUE
, TRUE
, &uResult
);
294 return NT_SUCCESS(Status
) ? uResult
: 0;
298 co_HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
301 PW32THREAD Win32Thread
;
304 PWINSTATION_OBJECT WinStaObj
;
307 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
309 Win32Thread
= PsGetCurrentThreadWin32Thread();
310 if (NULL
== Win32Thread
)
316 Table
= MsqGetHooks(Win32Thread
->MessageQueue
);
319 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
321 /* try global table */
323 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
325 return 0; /* no hook set */
329 if (Hook
->Thread
!= PsGetCurrentThread()
330 && (WH_KEYBOARD_LL
== HookId
|| WH_MOUSE_LL
== HookId
))
332 DPRINT("Calling hook in owning thread\n");
333 return IntCallLowLevelHook(HookId
, Code
, wParam
, lParam
, Hook
);
336 if (Hook
->Thread
!= PsGetCurrentThread())
338 DPRINT1("Calling hooks in other threads not implemented yet");
342 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
343 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
345 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
348 Result
= co_IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
349 Hook
->Ansi
, &Hook
->ModuleName
);
351 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
356 if (! NT_SUCCESS(Status
))
358 DPRINT1("Invalid window station????\n");
362 IntReleaseHookChain(MsqGetHooks(PsGetCurrentThreadWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
363 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
364 ObDereferenceObject(WinStaObj
);
371 HOOK_DestroyThreadHooks(PETHREAD Thread
)
376 PWINSTATION_OBJECT WinStaObj
;
379 if (NULL
!= GlobalHooks
)
381 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
386 if (! NT_SUCCESS(Status
))
388 DPRINT1("Invalid window station????\n");
392 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
394 /* only low-level keyboard/mouse global hooks can be owned by a thread */
399 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
400 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
402 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
404 if (HookObj
->Thread
== Thread
)
406 IntRemoveHook(HookObj
, WinStaObj
, TRUE
);
413 ObDereferenceObject(WinStaObj
);
419 NtUserCallNextHookEx(
425 PHOOK HookObj
, NextObj
;
426 PWINSTATION_OBJECT WinStaObj
;
428 DECLARE_RETURN(LRESULT
);
430 DPRINT("Enter NtUserCallNextHookEx\n");
431 UserEnterExclusive();
433 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
438 if (! NT_SUCCESS(Status
))
440 SetLastNtError(Status
);
444 //Status = ObmReferenceObjectByHandle(gHandleTable, Hook,
445 // otHookProc, (PVOID *) &HookObj);
446 ObDereferenceObject(WinStaObj
);
448 // if (! NT_SUCCESS(Status))
450 // DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
451 // SetLastNtError(Status);
455 if (!(HookObj
= IntGetHookObject(Hook
)))
460 ASSERT(Hook
== HookObj
->Self
);
462 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
464 DPRINT1("Thread mismatch\n");
465 ObmDereferenceObject(HookObj
);
466 SetLastWin32Error(ERROR_INVALID_HANDLE
);
470 NextObj
= IntGetNextHook(HookObj
);
471 ObmDereferenceObject(HookObj
);
474 DPRINT1("Calling next hook not implemented\n");
476 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
483 DPRINT("Leave NtUserCallNextHookEx, ret=%i\n",_ret_
);
490 NtUserSetWindowsHookAW(
502 NtUserSetWindowsHookEx(
504 PUNICODE_STRING UnsafeModuleName
,
510 PWINSTATION_OBJECT WinStaObj
;
514 UNICODE_STRING ModuleName
;
517 DECLARE_RETURN(HHOOK
);
519 DPRINT("Enter NtUserSetWindowsHookEx\n");
520 UserEnterExclusive();
522 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
524 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
528 if (ThreadId
) /* thread-local hook */
530 if (HookId
== WH_JOURNALRECORD
||
531 HookId
== WH_JOURNALPLAYBACK
||
532 HookId
== WH_KEYBOARD_LL
||
533 HookId
== WH_MOUSE_LL
||
534 HookId
== WH_SYSMSGFILTER
)
536 /* these can only be global */
537 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
542 if (! NT_SUCCESS(PsLookupThreadByThreadId((HANDLE
) ThreadId
, &Thread
)))
544 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
545 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
548 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
550 ObDereferenceObject(Thread
);
551 DPRINT1("Can't specify thread belonging to another process\n");
552 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
556 else /* system-global hook */
558 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
561 Thread
= PsGetCurrentThread();
562 Status
= ObReferenceObjectByPointer(Thread
,
567 if (! NT_SUCCESS(Status
))
569 SetLastNtError(Status
);
570 RETURN( (HANDLE
) NULL
);
573 else if (NULL
== Mod
)
575 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
585 /* We only (partially) support local WH_CBT hooks and
586 * WH_KEYBOARD_LL/WH_MOUSE_LL hooks for now */
587 if ((WH_CBT
!= HookId
|| Global
)
588 && WH_KEYBOARD_LL
!= HookId
&& WH_MOUSE_LL
!= HookId
)
590 #if 0 /* Removed to get winEmbed working again */
593 DPRINT1("Not implemented: HookId %d Global %s\n", HookId
, Global
? "TRUE" : "FALSE");
598 ObDereferenceObject(Thread
);
600 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
604 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
609 if (! NT_SUCCESS(Status
))
613 ObDereferenceObject(Thread
);
615 SetLastNtError(Status
);
616 RETURN( (HANDLE
) NULL
);
619 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
624 ObDereferenceObject(Thread
);
626 ObDereferenceObject(WinStaObj
);
632 Hook
->Flags
|= HOOK_THREAD_REFERENCED
;
637 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
638 if (! NT_SUCCESS(Status
))
640 ObmDereferenceObject(Hook
);
641 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
644 ObDereferenceObject(Thread
);
646 ObDereferenceObject(WinStaObj
);
647 SetLastNtError(Status
);
650 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
651 ModuleName
.MaximumLength
,
653 if (NULL
== Hook
->ModuleName
.Buffer
)
655 ObmDereferenceObject(Hook
);
656 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
659 ObDereferenceObject(Thread
);
661 ObDereferenceObject(WinStaObj
);
662 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
665 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
666 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
668 ModuleName
.MaximumLength
);
669 if (! NT_SUCCESS(Status
))
671 ExFreePool(Hook
->ModuleName
.Buffer
);
672 ObmDereferenceObject(Hook
);
673 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
676 ObDereferenceObject(Thread
);
678 ObDereferenceObject(WinStaObj
);
679 SetLastNtError(Status
);
682 Hook
->ModuleName
.Length
= ModuleName
.Length
;
685 Hook
->Proc
= HookProc
;
689 ObmDereferenceObject(Hook
);
690 ObDereferenceObject(WinStaObj
);
695 DPRINT("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_
);
702 NtUserSetWinEventHook(
719 NtUserUnhookWindowsHookEx(
722 PWINSTATION_OBJECT WinStaObj
;
725 DECLARE_RETURN(BOOL
);
727 DPRINT("Enter NtUserUnhookWindowsHookEx\n");
728 UserEnterExclusive();
730 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
735 if (! NT_SUCCESS(Status
))
737 SetLastNtError(Status
);
741 // Status = ObmReferenceObjectByHandle(gHandleTable, Hook,
742 // otHookProc, (PVOID *) &HookObj);
743 if (!(HookObj
= IntGetHookObject(Hook
)))
745 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
746 ObDereferenceObject(WinStaObj
);
747 // SetLastNtError(Status);
750 ASSERT(Hook
== HookObj
->Self
);
752 IntRemoveHook(HookObj
, WinStaObj
, FALSE
);
754 ObmDereferenceObject(HookObj
);
755 ObDereferenceObject(WinStaObj
);
760 DPRINT("Leave NtUserUnhookWindowsHookEx, ret=%i\n",_ret_
);
767 NtUserUnhookWinEvent(