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.
20 * COPYRIGHT: See COPYING in the top level directory
21 * PROJECT: ReactOS kernel
22 * PURPOSE: Window hooks
23 * FILE: subsys/win32k/ntuser/hook.c
24 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net)
26 * 06-06-2001 CSH Created
27 * NOTE: Most of this code was adapted from Wine,
28 * Copyright (C) 2002 Alexandre Julliard
36 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
37 #define HOOKID_TO_FLAG(HookId) (1 << ((HookId) + 1))
39 static PHOOKTABLE GlobalHooks
;
40 DWORD Bogus_SrvEventActivity
= 0;
43 /* PRIVATE FUNCTIONS *********************************************************/
48 GetMaskFromEvent(DWORD Event
)
52 if ( Event
> EVENT_OBJECT_STATECHANGE
)
54 if ( Event
== EVENT_OBJECT_LOCATIONCHANGE
) return SRV_EVENT_LOCATIONCHANGE
;
55 if ( Event
== EVENT_OBJECT_NAMECHANGE
) return SRV_EVENT_NAMECHANGE
;
56 if ( Event
== EVENT_OBJECT_VALUECHANGE
) return SRV_EVENT_VALUECHANGE
;
57 return SRV_EVENT_CREATE
;
60 if ( Event
== EVENT_OBJECT_STATECHANGE
) return SRV_EVENT_STATECHANGE
;
62 Ret
= SRV_EVENT_RUNNING
;
64 if ( Event
< EVENT_SYSTEM_MENUSTART
) return SRV_EVENT_CREATE
;
66 if ( Event
<= EVENT_SYSTEM_MENUPOPUPEND
)
72 if ( Event
<= EVENT_CONSOLE_CARET
-1 ) return SRV_EVENT_CREATE
;
73 if ( Event
<= EVENT_CONSOLE_END_APPLICATION
) return SRV_EVENT_END_APPLICATION
;
74 if ( Event
!= EVENT_OBJECT_FOCUS
) return SRV_EVENT_CREATE
;
79 /* create a new hook table */
81 IntAllocHookTable(void)
86 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
89 for (i
= 0; i
< NB_HOOKS
; i
++)
91 InitializeListHead(&Table
->Hooks
[i
]);
100 PHOOK FASTCALL
IntGetHookObject(HHOOK hHook
)
106 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
110 Hook
= (PHOOK
)UserGetObject(gHandleTable
, hHook
, otHook
);
113 SetLastWin32Error(ERROR_INVALID_HOOK_HANDLE
);
117 ASSERT(USER_BODY_TO_HEADER(Hook
)->RefCount
>= 0);
119 USER_BODY_TO_HEADER(Hook
)->RefCount
++;
126 /* create a new hook and add it to the specified table */
128 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
130 PW32THREAD W32Thread
;
132 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
);
137 Table
= IntAllocHookTable();
148 MsqSetHooks(((PW32THREAD
)Thread
->Tcb
.Win32Thread
)->MessageQueue
, Table
);
152 Hook
= UserCreateObject(gHandleTable
, &Handle
, otHook
, sizeof(HOOK
));
159 Hook
->Thread
= Thread
;
160 Hook
->HookId
= HookId
;
162 W32Thread
= ((PW32THREAD
)Thread
->Tcb
.Win32Thread
);
163 ASSERT(W32Thread
!= NULL
);
164 W32Thread
->Hooks
|= HOOKID_TO_FLAG(HookId
);
165 if (W32Thread
->ThreadInfo
!= NULL
)
166 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
168 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
170 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
175 /* get the hook table that a given hook belongs to */
176 static PHOOKTABLE FASTCALL
177 IntGetTable(PHOOK Hook
)
179 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
180 WH_MOUSE_LL
== Hook
->HookId
)
185 return MsqGetHooks(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
);
188 /* get the first hook in the chain */
189 static PHOOK FASTCALL
190 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
192 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
193 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
194 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
197 /* find the first non-deleted hook in the chain */
198 static PHOOK FASTCALL
199 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
204 Hook
= IntGetFirstHook(Table
, HookId
);
205 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
207 Elem
= Hook
->Chain
.Flink
;
208 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
209 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
215 /* find the next hook in the chain, skipping the deleted ones */
216 static PHOOK FASTCALL
217 IntGetNextHook(PHOOK Hook
)
219 PHOOKTABLE Table
= IntGetTable(Hook
);
220 int HookId
= Hook
->HookId
;
223 Elem
= Hook
->Chain
.Flink
;
224 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
226 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
227 if (NULL
!= Hook
->Proc
)
233 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
235 return IntGetFirstValidHook(GlobalHooks
, HookId
);
241 /* free a hook, removing it from its chain */
243 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
245 RemoveEntryList(&Hook
->Chain
);
246 RtlFreeUnicodeString(&Hook
->ModuleName
);
248 /* Dereference thread if required */
249 if (Hook
->Flags
& HOOK_THREAD_REFERENCED
)
251 ObDereferenceObject(Hook
->Thread
);
255 UserDeleteObject(Hook
->Self
, otHook
);
258 /* remove a hook, freeing it if the chain is not in use */
260 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
, BOOL TableAlreadyLocked
)
262 PW32THREAD W32Thread
;
263 PHOOKTABLE Table
= IntGetTable(Hook
);
265 ASSERT(NULL
!= Table
);
271 W32Thread
= ((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
);
272 ASSERT(W32Thread
!= NULL
);
273 W32Thread
->Hooks
&= ~HOOKID_TO_FLAG(Hook
->HookId
);
274 if (W32Thread
->ThreadInfo
!= NULL
)
275 W32Thread
->ThreadInfo
->Hooks
= W32Thread
->Hooks
;
277 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
279 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
283 IntFreeHook(Table
, Hook
, WinStaObj
);
287 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
289 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
299 /* use count shouldn't already be 0 */
300 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
301 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
305 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
307 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
308 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
310 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
312 if (NULL
== HookObj
->Proc
)
314 IntFreeHook(Table
, HookObj
, WinStaObj
);
320 static LRESULT FASTCALL
321 IntCallLowLevelHook(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
, PHOOK Hook
)
326 /* FIXME should get timeout from
327 * HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout */
328 Status
= co_MsqSendMessage(((PW32THREAD
)Hook
->Thread
->Tcb
.Win32Thread
)->MessageQueue
, (HWND
) Code
, HookId
,
329 wParam
, lParam
, 5000, TRUE
, TRUE
, &uResult
);
331 return NT_SUCCESS(Status
) ? uResult
: 0;
335 co_HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
338 PW32THREAD Win32Thread
;
341 PWINSTATION_OBJECT WinStaObj
;
344 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
346 Win32Thread
= PsGetCurrentThreadWin32Thread();
347 if (NULL
== Win32Thread
)
353 Table
= MsqGetHooks(Win32Thread
->MessageQueue
);
356 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
358 /* try global table */
360 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
362 return 0; /* no hook set */
366 if (Hook
->Thread
!= PsGetCurrentThread()
367 && (WH_KEYBOARD_LL
== HookId
|| WH_MOUSE_LL
== HookId
))
369 DPRINT("Calling hook in owning thread\n");
370 return IntCallLowLevelHook(HookId
, Code
, wParam
, lParam
, Hook
);
373 if (Hook
->Thread
!= PsGetCurrentThread())
375 DPRINT1("Calling hooks in other threads not implemented yet");
379 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
380 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
382 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
385 Result
= co_IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
386 Hook
->Ansi
, &Hook
->ModuleName
);
388 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
393 if (! NT_SUCCESS(Status
))
395 DPRINT1("Invalid window station????\n");
399 IntReleaseHookChain(MsqGetHooks(PsGetCurrentThreadWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
400 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
401 ObDereferenceObject(WinStaObj
);
408 HOOK_DestroyThreadHooks(PETHREAD Thread
)
413 PWINSTATION_OBJECT WinStaObj
;
416 if (NULL
!= GlobalHooks
)
418 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
423 if (! NT_SUCCESS(Status
))
425 DPRINT1("Invalid window station????\n");
429 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
431 /* only low-level keyboard/mouse global hooks can be owned by a thread */
436 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
437 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
439 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
441 if (HookObj
->Thread
== Thread
)
443 IntRemoveHook(HookObj
, WinStaObj
, TRUE
);
450 ObDereferenceObject(WinStaObj
);
456 NtUserCallNextHookEx(
462 PHOOK HookObj
, NextObj
;
463 PWINSTATION_OBJECT WinStaObj
;
465 DECLARE_RETURN(LRESULT
);
467 DPRINT("Enter NtUserCallNextHookEx\n");
468 UserEnterExclusive();
470 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
475 if (! NT_SUCCESS(Status
))
477 SetLastNtError(Status
);
481 //Status = UserReferenceObjectByHandle(gHandleTable, Hook,
482 // otHookProc, (PVOID *) &HookObj);
483 ObDereferenceObject(WinStaObj
);
485 // if (! NT_SUCCESS(Status))
487 // DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
488 // SetLastNtError(Status);
492 if (!(HookObj
= IntGetHookObject(Hook
)))
497 ASSERT(Hook
== HookObj
->Self
);
499 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
501 DPRINT1("Thread mismatch\n");
502 UserDereferenceObject(HookObj
);
503 SetLastWin32Error(ERROR_INVALID_HANDLE
);
507 NextObj
= IntGetNextHook(HookObj
);
508 UserDereferenceObject(HookObj
);
511 DPRINT1("Calling next hook not implemented\n");
513 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
520 DPRINT("Leave NtUserCallNextHookEx, ret=%i\n",_ret_
);
527 NtUserSetWindowsHookAW(
532 UNICODE_STRING USModuleName
;
533 RtlInitUnicodeString(&USModuleName
, NULL
);
534 return NtUserSetWindowsHookEx(NULL
, &USModuleName
, 0, idHook
, lpfn
, Ansi
);
539 NtUserSetWindowsHookEx(
541 PUNICODE_STRING UnsafeModuleName
,
547 PWINSTATION_OBJECT WinStaObj
;
551 UNICODE_STRING ModuleName
;
554 DECLARE_RETURN(HHOOK
);
556 DPRINT("Enter NtUserSetWindowsHookEx\n");
557 UserEnterExclusive();
559 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
561 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
565 if (ThreadId
) /* thread-local hook */
567 if (HookId
== WH_JOURNALRECORD
||
568 HookId
== WH_JOURNALPLAYBACK
||
569 HookId
== WH_KEYBOARD_LL
||
570 HookId
== WH_MOUSE_LL
||
571 HookId
== WH_SYSMSGFILTER
)
573 /* these can only be global */
574 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
579 if (! NT_SUCCESS(PsLookupThreadByThreadId((HANDLE
) ThreadId
, &Thread
)))
581 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
582 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
585 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
587 ObDereferenceObject(Thread
);
588 DPRINT1("Can't specify thread belonging to another process\n");
589 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
593 else /* system-global hook */
595 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
598 Thread
= PsGetCurrentThread();
599 Status
= ObReferenceObjectByPointer(Thread
,
604 if (! NT_SUCCESS(Status
))
606 SetLastNtError(Status
);
607 RETURN( (HANDLE
) NULL
);
610 else if (NULL
== Mod
)
612 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
622 /* We only (partially) support local WH_CBT hooks and
623 * WH_KEYBOARD_LL/WH_MOUSE_LL hooks for now */
624 if ((WH_CBT
!= HookId
|| Global
)
625 && WH_KEYBOARD_LL
!= HookId
&& WH_MOUSE_LL
!= HookId
)
627 #if 0 /* Removed to get winEmbed working again */
630 DPRINT1("Not implemented: HookId %d Global %s\n", HookId
, Global
? "TRUE" : "FALSE");
635 ObDereferenceObject(Thread
);
637 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
641 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
646 if (! NT_SUCCESS(Status
))
650 ObDereferenceObject(Thread
);
652 SetLastNtError(Status
);
653 RETURN( (HANDLE
) NULL
);
656 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
661 ObDereferenceObject(Thread
);
663 ObDereferenceObject(WinStaObj
);
669 Hook
->Flags
|= HOOK_THREAD_REFERENCED
;
674 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
675 if (! NT_SUCCESS(Status
))
677 UserDereferenceObject(Hook
);
678 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
681 ObDereferenceObject(Thread
);
683 ObDereferenceObject(WinStaObj
);
684 SetLastNtError(Status
);
687 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
688 ModuleName
.MaximumLength
,
690 if (NULL
== Hook
->ModuleName
.Buffer
)
692 UserDereferenceObject(Hook
);
693 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
696 ObDereferenceObject(Thread
);
698 ObDereferenceObject(WinStaObj
);
699 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
702 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
703 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
705 ModuleName
.MaximumLength
);
706 if (! NT_SUCCESS(Status
))
708 ExFreePool(Hook
->ModuleName
.Buffer
);
709 UserDereferenceObject(Hook
);
710 IntRemoveHook(Hook
, WinStaObj
, FALSE
);
713 ObDereferenceObject(Thread
);
715 ObDereferenceObject(WinStaObj
);
716 SetLastNtError(Status
);
719 Hook
->ModuleName
.Length
= ModuleName
.Length
;
722 Hook
->Proc
= HookProc
;
726 UserDereferenceObject(Hook
);
727 ObDereferenceObject(WinStaObj
);
732 DPRINT("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_
);
739 NtUserSetWinEventHook(
742 HMODULE hmodWinEventProc
,
743 PUNICODE_STRING puString
,
744 WINEVENTPROC lpfnWinEventProc
,
750 Bogus_SrvEventActivity
|= GetMaskFromEvent(eventMin
); // Fake it out for now.
751 Bogus_SrvEventActivity
&= ~GetMaskFromEvent(eventMin
);
759 NtUserUnhookWindowsHookEx(
762 PWINSTATION_OBJECT WinStaObj
;
765 DECLARE_RETURN(BOOL
);
767 DPRINT("Enter NtUserUnhookWindowsHookEx\n");
768 UserEnterExclusive();
770 Status
= IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation
,
775 if (! NT_SUCCESS(Status
))
777 SetLastNtError(Status
);
781 // Status = UserReferenceObjectByHandle(gHandleTable, Hook,
782 // otHookProc, (PVOID *) &HookObj);
783 if (!(HookObj
= IntGetHookObject(Hook
)))
785 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
786 ObDereferenceObject(WinStaObj
);
787 // SetLastNtError(Status);
790 ASSERT(Hook
== HookObj
->Self
);
792 IntRemoveHook(HookObj
, WinStaObj
, FALSE
);
794 UserDereferenceObject(HookObj
);
795 ObDereferenceObject(WinStaObj
);
800 DPRINT("Leave NtUserUnhookWindowsHookEx, ret=%i\n",_ret_
);
807 NtUserUnhookWinEvent(
808 HWINEVENTHOOK hWinEventHook
)