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.5 2004/02/19 21:12:09 weiden 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 <include/tags.h>
41 #include <internal/ps.h>
42 #include <internal/safe.h>
45 #include <win32k/debug1.h>
47 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
49 STATIC PHOOKTABLE GlobalHooks
;
51 /* create a new hook table */
52 STATIC FASTCALL PHOOKTABLE
53 IntAllocHookTable(void)
58 Table
= ExAllocatePoolWithTag(PagedPool
, sizeof(HOOKTABLE
), TAG_HOOK
);
61 ExInitializeFastMutex(&Table
->Lock
);
62 for (i
= 0; i
< NB_HOOKS
; i
++)
64 InitializeListHead(&Table
->Hooks
[i
]);
72 /* create a new hook and add it to the specified table */
74 IntAddHook(PETHREAD Thread
, int HookId
, BOOLEAN Global
, PWINSTATION_OBJECT WinStaObj
)
77 PHOOKTABLE Table
= Global
? GlobalHooks
: MsqGetHooks(Thread
->Win32Thread
->MessageQueue
);
82 Table
= IntAllocHookTable();
93 MsqSetHooks(Thread
->Win32Thread
->MessageQueue
, Table
);
97 Hook
= ObmCreateObject(WinStaObj
->HandleTable
, &Handle
,
98 otHookProc
, sizeof(HOOK
));
105 Hook
->Thread
= Thread
;
106 Hook
->HookId
= HookId
;
107 RtlInitUnicodeString(&Hook
->ModuleName
, NULL
);
109 ExAcquireFastMutex(&Table
->Lock
);
110 InsertHeadList(&Table
->Hooks
[HOOKID_TO_INDEX(HookId
)], &Hook
->Chain
);
111 ExReleaseFastMutex(&Table
->Lock
);
116 /* get the hook table that a given hook belongs to */
117 STATIC PHOOKTABLE FASTCALL
118 IntGetTable(PHOOK Hook
)
120 if (NULL
== Hook
->Thread
|| WH_KEYBOARD_LL
== Hook
->HookId
||
121 WH_MOUSE_LL
== Hook
->HookId
)
126 return MsqGetHooks(Hook
->Thread
->Win32Thread
->MessageQueue
);
129 /* get the first hook in the chain */
130 STATIC PHOOK FASTCALL
131 IntGetFirstHook(PHOOKTABLE Table
, int HookId
)
133 PLIST_ENTRY Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
134 return Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
135 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
);
138 /* find the first non-deleted hook in the chain */
139 STATIC PHOOK FASTCALL
140 IntGetFirstValidHook(PHOOKTABLE Table
, int HookId
)
145 ExAcquireFastMutex(&Table
->Lock
);
146 Hook
= IntGetFirstHook(Table
, HookId
);
147 while (NULL
!= Hook
&& NULL
== Hook
->Proc
)
149 Elem
= Hook
->Chain
.Flink
;
150 Hook
= (Elem
== &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)]
151 ? NULL
: CONTAINING_RECORD(Elem
, HOOK
, Chain
));
153 ExReleaseFastMutex(&Table
->Lock
);
158 /* find the next hook in the chain, skipping the deleted ones */
159 STATIC PHOOK FASTCALL
160 IntGetNextHook(PHOOK Hook
)
162 PHOOKTABLE Table
= IntGetTable(Hook
);
163 int HookId
= Hook
->HookId
;
166 ExAcquireFastMutex(&Table
->Lock
);
167 Elem
= Hook
->Chain
.Flink
;
168 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
170 Hook
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
171 if (NULL
!= Hook
->Proc
)
173 ExReleaseFastMutex(&Table
->Lock
);
177 ExReleaseFastMutex(&Table
->Lock
);
179 if (NULL
!= GlobalHooks
&& Table
!= GlobalHooks
) /* now search through the global table */
181 return IntGetFirstValidHook(GlobalHooks
, HookId
);
187 /* free a hook, removing it from its chain */
189 IntFreeHook(PHOOKTABLE Table
, PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
191 RemoveEntryList(&Hook
->Chain
);
192 RtlFreeUnicodeString(&Hook
->ModuleName
);
194 ObmCloseHandle(WinStaObj
->HandleTable
, Hook
->Self
);
197 /* remove a hook, freeing it if the chain is not in use */
199 IntRemoveHook(PHOOK Hook
, PWINSTATION_OBJECT WinStaObj
)
201 PHOOKTABLE Table
= IntGetTable(Hook
);
203 ASSERT(NULL
!= Table
);
209 ExAcquireFastMutex(&Table
->Lock
);
210 if (0 != Table
->Counts
[HOOKID_TO_INDEX(Hook
->HookId
)])
212 Hook
->Proc
= NULL
; /* chain is in use, just mark it and return */
216 IntFreeHook(Table
, Hook
, WinStaObj
);
218 ExReleaseFastMutex(&Table
->Lock
);
221 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
223 IntReleaseHookChain(PHOOKTABLE Table
, int HookId
, PWINSTATION_OBJECT WinStaObj
)
233 ExAcquireFastMutex(&Table
->Lock
);
234 /* use count shouldn't already be 0 */
235 ASSERT(0 != Table
->Counts
[HOOKID_TO_INDEX(HookId
)]);
236 if (0 == Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
238 ExReleaseFastMutex(&Table
->Lock
);
241 if (0 == --Table
->Counts
[HOOKID_TO_INDEX(HookId
)])
243 Elem
= Table
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
244 while (Elem
!= &Table
->Hooks
[HOOKID_TO_INDEX(HookId
)])
246 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
248 if (NULL
== HookObj
->Proc
)
250 IntFreeHook(Table
, HookObj
, WinStaObj
);
254 ExReleaseFastMutex(&Table
->Lock
);
258 HOOK_CallHooks(INT HookId
, INT Code
, WPARAM wParam
, LPARAM lParam
)
261 PHOOKTABLE Table
= MsqGetHooks(PsGetWin32Thread()->MessageQueue
);
263 PWINSTATION_OBJECT WinStaObj
;
266 ASSERT(WH_MINHOOK
<= HookId
&& HookId
<= WH_MAXHOOK
);
268 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
270 /* try global table */
272 if (NULL
== Table
|| ! (Hook
= IntGetFirstValidHook(Table
, HookId
)))
274 return 0; /* no hook set */
278 if (Hook
->Thread
!= PsGetCurrentThread())
280 DPRINT1("Calling hooks in other threads not implemented yet");
284 ExAcquireFastMutex(&Table
->Lock
);
285 Table
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
286 ExReleaseFastMutex(&Table
->Lock
);
287 if (Table
!= GlobalHooks
&& GlobalHooks
!= NULL
)
289 ExAcquireFastMutex(&GlobalHooks
->Lock
);
290 GlobalHooks
->Counts
[HOOKID_TO_INDEX(HookId
)]++;
291 ExReleaseFastMutex(&GlobalHooks
->Lock
);
294 Result
= IntCallHookProc(HookId
, Code
, wParam
, lParam
, Hook
->Proc
,
295 Hook
->Ansi
, &Hook
->ModuleName
);
297 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
302 if(! NT_SUCCESS(Status
))
304 DPRINT1("Invalid window station????\n");
308 IntReleaseHookChain(MsqGetHooks(PsGetWin32Thread()->MessageQueue
), HookId
, WinStaObj
);
309 IntReleaseHookChain(GlobalHooks
, HookId
, WinStaObj
);
310 ObDereferenceObject(WinStaObj
);
317 HOOK_DestroyThreadHooks(PETHREAD Thread
)
322 PWINSTATION_OBJECT WinStaObj
;
325 if (NULL
!= GlobalHooks
)
327 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
332 if(! NT_SUCCESS(Status
))
334 DPRINT1("Invalid window station????\n");
337 ExAcquireFastMutex(&GlobalHooks
->Lock
);
338 for (HookId
= WH_MINHOOK
; HookId
<= WH_MAXHOOK
; HookId
++)
340 /* only low-level keyboard/mouse global hooks can be owned by a thread */
345 Elem
= GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)].Flink
;
346 while (Elem
!= &GlobalHooks
->Hooks
[HOOKID_TO_INDEX(HookId
)])
348 HookObj
= CONTAINING_RECORD(Elem
, HOOK
, Chain
);
350 if (HookObj
->Thread
== Thread
)
352 IntRemoveHook(HookObj
, WinStaObj
);
358 ExReleaseFastMutex(&GlobalHooks
->Lock
);
359 ObDereferenceObject(WinStaObj
);
365 NtUserCallNextHookEx(
371 PHOOK HookObj
, NextObj
;
372 PWINSTATION_OBJECT WinStaObj
;
375 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
380 if(! NT_SUCCESS(Status
))
382 SetLastNtError(Status
);
386 Status
= ObmReferenceObjectByHandle(WinStaObj
->HandleTable
, Hook
,
387 otHookProc
, (PVOID
*) &HookObj
);
388 ObDereferenceObject(WinStaObj
);
389 if (! NT_SUCCESS(Status
))
391 DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
392 SetLastNtError(Status
);
395 ASSERT(Hook
== HookObj
->Self
);
397 if (NULL
!= HookObj
->Thread
&& (HookObj
->Thread
!= PsGetCurrentThread()))
399 DPRINT1("Thread mismatch\n");
400 ObmDereferenceObject(HookObj
);
401 SetLastWin32Error(ERROR_INVALID_HANDLE
);
405 NextObj
= IntGetNextHook(HookObj
);
406 ObmDereferenceObject(HookObj
);
409 DPRINT1("Calling next hook not implemented\n");
411 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
420 NtUserSetWindowsHookAW(
432 NtUserSetWindowsHookEx(
434 PUNICODE_STRING UnsafeModuleName
,
440 PWINSTATION_OBJECT WinStaObj
;
444 UNICODE_STRING ModuleName
;
448 if (HookId
< WH_MINHOOK
|| WH_MAXHOOK
< HookId
|| NULL
== HookProc
)
450 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
454 if (ThreadId
) /* thread-local hook */
456 if (HookId
== WH_JOURNALRECORD
||
457 HookId
== WH_JOURNALPLAYBACK
||
458 HookId
== WH_KEYBOARD_LL
||
459 HookId
== WH_MOUSE_LL
||
460 HookId
== WH_SYSMSGFILTER
)
462 /* these can only be global */
463 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
468 if (! NT_SUCCESS(PsLookupThreadByThreadId((PVOID
) ThreadId
, &Thread
)))
470 DPRINT1("Invalid thread id 0x%x\n", ThreadId
);
471 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
474 if (Thread
->ThreadsProcess
!= PsGetCurrentProcess())
476 DPRINT1("Can't specify thread belonging to another process\n");
477 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
481 else /* system-global hook */
483 if (HookId
== WH_KEYBOARD_LL
|| HookId
== WH_MOUSE_LL
)
486 Thread
= PsGetCurrentThread();
488 else if (NULL
== Mod
)
490 SetLastWin32Error(ERROR_INVALID_PARAMETER
);
500 /* We only (partially) support local WH_CBT hooks for now */
501 if (WH_CBT
!= HookId
|| Global
)
503 #if 0 /* Removed to get winEmbed working again */
506 DPRINT1("Not implemented: HookId %d Global %s\n", HookId
, Global
? "TRUE" : "FALSE");
508 SetLastWin32Error(ERROR_NOT_SUPPORTED
);
512 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
517 if(! NT_SUCCESS(Status
))
519 SetLastNtError(Status
);
520 return (HANDLE
) NULL
;
523 Hook
= IntAddHook(Thread
, HookId
, Global
, WinStaObj
);
526 ObDereferenceObject(WinStaObj
);
532 Status
= MmCopyFromCaller(&ModuleName
, UnsafeModuleName
, sizeof(UNICODE_STRING
));
533 if (! NT_SUCCESS(Status
))
535 ObmDereferenceObject(Hook
);
536 IntRemoveHook(Hook
, WinStaObj
);
537 ObDereferenceObject(WinStaObj
);
538 SetLastNtError(Status
);
541 Hook
->ModuleName
.Buffer
= ExAllocatePoolWithTag(PagedPool
,
542 ModuleName
.MaximumLength
,
544 if (NULL
== Hook
->ModuleName
.Buffer
)
546 ObmDereferenceObject(Hook
);
547 IntRemoveHook(Hook
, WinStaObj
);
548 ObDereferenceObject(WinStaObj
);
549 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY
);
552 Hook
->ModuleName
.MaximumLength
= ModuleName
.MaximumLength
;
553 Status
= MmCopyFromCaller(Hook
->ModuleName
.Buffer
,
555 ModuleName
.MaximumLength
);
556 if (! NT_SUCCESS(Status
))
558 ObmDereferenceObject(Hook
);
559 IntRemoveHook(Hook
, WinStaObj
);
560 ObDereferenceObject(WinStaObj
);
561 SetLastNtError(Status
);
564 Hook
->ModuleName
.Length
= ModuleName
.Length
;
567 Hook
->Proc
= HookProc
;
571 ObmDereferenceObject(Hook
);
572 ObDereferenceObject(WinStaObj
);
579 NtUserSetWinEventHook(
596 NtUserUnhookWindowsHookEx(
599 PWINSTATION_OBJECT WinStaObj
;
603 Status
= IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
608 if(! NT_SUCCESS(Status
))
610 SetLastNtError(Status
);
614 Status
= ObmReferenceObjectByHandle(WinStaObj
->HandleTable
, Hook
,
615 otHookProc
, (PVOID
*) &HookObj
);
616 if (! NT_SUCCESS(Status
))
618 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
619 ObDereferenceObject(WinStaObj
);
620 SetLastNtError(Status
);
623 ASSERT(Hook
== HookObj
->Self
);
625 IntRemoveHook(HookObj
, WinStaObj
);
627 ObmDereferenceObject(HookObj
);
628 ObDereferenceObject(WinStaObj
);
635 NtUserUnhookWinEvent(