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.
19 /* $Id: hook.c,v 1.3 2003/12/12 14:22:37 gvg Exp $
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
32 #include <ddk/ntddk.h>
33 #include <win32k/win32k.h>
34 #include <include/callback.h>
35 #include <include/error.h>
36 #include <include/hook.h>
37 #include <include/object.h>
38 #include <include/msgqueue.h>
39 #include <include/winsta.h>
40 #include <internal/ps.h>
41 #include <internal/safe.h>
44 #include <win32k/debug1.h>
46 #define TAG_HOOK TAG('W', 'N', 'H', 'K')
48 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
50 STATIC PHOOKTABLE GlobalHooks
;
52 /* create a new hook table */
53 STATIC FASTCALL PHOOKTABLE
54 IntAllocHookTable(void)
59 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
62 ExInitializeFastMutex(&Table
->Lock
);
63 for (i
= 0; i
< NB_HOOKS
; i
++)
65 InitializeListHead(&Table
->Hooks
[i
]);
73 /* create a new hook and add it to the specified table */
75 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
78 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(Thread
->Win32Thread
->MessageQueue
);
83 Table
= IntAllocHookTable();
94 MsqSetHooks(Thread
->Win32Thread
->MessageQueue
, Table
);
98 Hook
= ObmCreateObject(WinStaObj
->HandleTable
, &Handle
,
99 otHookProc
, sizeof(HOOK
));
106 Hook
->Thread
= Thread
;
107 Hook
->HookId
= HookId
;
108 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
110 ExAcquireFastMutex(&Table
->Lock
);
111 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
112 ExReleaseFastMutex(&Table
->Lock
);
117 /* get the hook table that a given hook belongs to */
118 STATIC PHOOKTABLE FASTCALL
119 IntGetTable(PHOOK Hook
)
121 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
122 WH_MOUSE_LL
== Hook
->HookId
)
127 return MsqGetHooks(Hook
->Thread
->Win32Thread
->MessageQueue
);
130 /* get the first hook in the chain */
131 STATIC PHOOK FASTCALL
132 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
134 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
135 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
136 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
139 /* find the first non-deleted hook in the chain */
140 STATIC PHOOK FASTCALL
141 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
146 ExAcquireFastMutex(&Table
->Lock
);
147 Hook
= IntGetFirstHook(Table
, HookId
);
148 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
150 Elem
= Hook
->Chain
.Flink
;
151 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
152 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
154 ExReleaseFastMutex(&Table
->Lock
);
159 /* find the next hook in the chain, skipping the deleted ones */
160 STATIC PHOOK FASTCALL
161 IntGetNextHook(PHOOK Hook
)
163 PHOOKTABLE Table
= IntGetTable(Hook
);
164 int HookId
= Hook
->HookId
;
167 ExAcquireFastMutex(&Table
->Lock
);
168 Elem
= Hook
->Chain
.Flink
;
169 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
171 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
172 if (NULL
!= Hook
->Proc
)
174 ExReleaseFastMutex(&Table
->Lock
);
178 ExReleaseFastMutex(&Table
->Lock
);
180 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
182 return IntGetFirstValidHook(GlobalHooks
, HookId
);
188 /* free a hook, removing it from its chain */
190 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
192 RemoveEntryList(&Hook
->Chain
);
193 RtlFreeUnicodeString(&Hook
->ModuleName
);
195 ObmCloseHandle(WinStaObj
->HandleTable
, Hook
->Self
);
198 /* remove a hook, freeing it if the chain is not in use */
200 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
202 PHOOKTABLE Table
= IntGetTable(Hook
);
204 ASSERT(NULL
!= Table
);
210 ExAcquireFastMutex(&Table
->Lock
);
211 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
213 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
217 IntFreeHook(Table
, Hook
, WinStaObj
);
219 ExReleaseFastMutex(&Table
->Lock
);
222 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
224 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
234 ExAcquireFastMutex(&Table
->Lock
);
235 /* use count shouldn't already be 0 */
236 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
237 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
239 ExReleaseFastMutex(&Table
->Lock
);
242 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
244 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
245 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
247 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
249 if (NULL
== HookObj
->Proc
)
251 IntFreeHook(Table
, HookObj
, WinStaObj
);
255 ExReleaseFastMutex(&Table
->Lock
);
259 HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
262 PHOOKTABLE Table
= MsqGetHooks(PsGetWin32Thread()->MessageQueue
);
264 PWINSTATION_OBJECT WinStaObj
;
267 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
269 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
271 /* try global table */
273 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
275 return 0; /* no hook set */
279 if (Hook
->Thread
!= PsGetCurrentThread())
281 DPRINT1("Calling hooks in other threads not implemented yet");
285 ExAcquireFastMutex(&Table
->Lock
);
286 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
287 ExReleaseFastMutex(&Table
->Lock
);
288 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
290 ExAcquireFastMutex(&GlobalHooks
->Lock
);
291 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
292 ExReleaseFastMutex(&GlobalHooks
->Lock
);
295 Result
= IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
296 Hook
->Ansi
, &Hook
->ModuleName
);
298 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
303 if(! NT_SUCCESS(Status
))
305 DPRINT1("Invalid window station????\n");
309 IntReleaseHookChain(MsqGetHooks(PsGetWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
310 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
311 ObDereferenceObject(WinStaObj
);
318 HOOK_DestroyThreadHooks(PETHREAD Thread
)
323 PWINSTATION_OBJECT WinStaObj
;
326 if (NULL
!= GlobalHooks
)
328 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
333 if(! NT_SUCCESS(Status
))
335 DPRINT1("Invalid window station????\n");
338 ExAcquireFastMutex(&GlobalHooks
->Lock
);
339 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
341 /* only low-level keyboard/mouse global hooks can be owned by a thread */
346 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
347 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
349 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
351 if (HookObj
->Thread
== Thread
)
353 IntRemoveHook(HookObj
, WinStaObj
);
359 ExReleaseFastMutex(&GlobalHooks
->Lock
);
360 ObDereferenceObject(WinStaObj
);
366 NtUserCallNextHookEx(
372 PHOOK HookObj
, NextObj
;
373 PWINSTATION_OBJECT WinStaObj
;
376 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
381 if(! NT_SUCCESS(Status
))
383 SetLastNtError(Status
);
387 Status
= ObmReferenceObjectByHandle(WinStaObj
->HandleTable
, Hook
,
388 otHookProc
, (PVOID
*) &HookObj
);
389 ObDereferenceObject(WinStaObj
);
390 if (! NT_SUCCESS(Status
))
392 DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
393 SetLastNtError(Status
);
396 ASSERT(Hook
== HookObj
->Self
);
398 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
400 DPRINT1("Thread mismatch\n");
401 ObmDereferenceObject(HookObj
);
402 SetLastWin32Error(ERROR_INVALID_HANDLE
);
406 NextObj
= IntGetNextHook(HookObj
);
407 ObmDereferenceObject(HookObj
);
410 DPRINT1("Calling next hook not implemented\n");
412 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
421 NtUserSetWindowsHookAW(
433 NtUserSetWindowsHookEx(
435 PUNICODE_STRING UnsafeModuleName
,
441 PWINSTATION_OBJECT WinStaObj
;
445 UNICODE_STRING ModuleName
;
449 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
451 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
455 if (ThreadId
) /* thread-local hook */
457 if (HookId
== WH_JOURNALRECORD
||
458 HookId
== WH_JOURNALPLAYBACK
||
459 HookId
== WH_KEYBOARD_LL
||
460 HookId
== WH_MOUSE_LL
||
461 HookId
== WH_SYSMSGFILTER
)
463 /* these can only be global */
464 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
469 if (! NT_SUCCESS(PsLookupThreadByThreadId((PVOID
) ThreadId
, &Thread
)))
471 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
472 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
475 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
477 DPRINT1("Can't specify thread belonging to another process\n");
478 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
482 else /* system-global hook */
484 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
487 Thread
= PsGetCurrentThread();
489 else if (NULL
== Mod
)
491 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
501 /* We only (partially) support local WH_CBT hooks for now */
502 if (WH_CBT
!= HookId
|| Global
)
505 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
509 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
514 if(! NT_SUCCESS(Status
))
516 SetLastNtError(Status
);
517 return (HANDLE
) NULL
;
520 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
523 ObDereferenceObject(WinStaObj
);
529 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
530 if (! NT_SUCCESS(Status
))
532 ObmDereferenceObject(Hook
);
533 IntRemoveHook(Hook
, WinStaObj
);
534 ObDereferenceObject(WinStaObj
);
535 SetLastNtError(Status
);
538 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
539 ModuleName
.MaximumLength
,
541 if (NULL
== Hook
->ModuleName
.Buffer
)
543 ObmDereferenceObject(Hook
);
544 IntRemoveHook(Hook
, WinStaObj
);
545 ObDereferenceObject(WinStaObj
);
546 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
549 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
550 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
552 ModuleName
.MaximumLength
);
553 if (! NT_SUCCESS(Status
))
555 ObmDereferenceObject(Hook
);
556 IntRemoveHook(Hook
, WinStaObj
);
557 ObDereferenceObject(WinStaObj
);
558 SetLastNtError(Status
);
561 Hook
->ModuleName
.Length
= ModuleName
.Length
;
564 Hook
->Proc
= HookProc
;
568 ObmDereferenceObject(Hook
);
569 ObDereferenceObject(WinStaObj
);
576 NtUserSetWinEventHook(
593 NtUserUnhookWindowsHookEx(
596 PWINSTATION_OBJECT WinStaObj
;
600 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
605 if(! NT_SUCCESS(Status
))
607 SetLastNtError(Status
);
611 Status
= ObmReferenceObjectByHandle(WinStaObj
->HandleTable
, Hook
,
612 otHookProc
, (PVOID
*) &HookObj
);
613 if (! NT_SUCCESS(Status
))
615 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
616 ObDereferenceObject(WinStaObj
);
617 SetLastNtError(Status
);
620 ASSERT(Hook
== HookObj
->Self
);
622 IntRemoveHook(HookObj
, WinStaObj
);
624 ObmDereferenceObject(HookObj
);
625 ObDereferenceObject(WinStaObj
);
632 NtUserUnhookWinEvent(