added pool tags for better debugging
[reactos.git] / reactos / subsys / win32k / ntuser / hook.c
1 /*
2 * ReactOS W32 Subsystem
3 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 ReactOS Team
4 *
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.
9 *
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.
14 *
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.
18 */
19 /* $Id: hook.c,v 1.5 2004/02/19 21:12:09 weiden Exp $
20 *
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)
26 * REVISION HISTORY:
27 * 06-06-2001 CSH Created
28 * NOTE: Most of this code was adapted from Wine,
29 * Copyright (C) 2002 Alexandre Julliard
30 */
31
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>
43
44 #define NDEBUG
45 #include <win32k/debug1.h>
46
47 #define HOOKID_TO_INDEX(HookId) (HookId - WH_MINHOOK)
48
49 STATIC PHOOKTABLE GlobalHooks;
50
51 /* create a new hook table */
52 STATIC FASTCALL PHOOKTABLE
53 IntAllocHookTable(void)
54 {
55 PHOOKTABLE Table;
56 UINT i;
57
58 Table = ExAllocatePoolWithTag(PagedPool, sizeof(HOOKTABLE), TAG_HOOK);
59 if (NULL != Table)
60 {
61 ExInitializeFastMutex(&Table->Lock);
62 for (i = 0; i < NB_HOOKS; i++)
63 {
64 InitializeListHead(&Table->Hooks[i]);
65 Table->Counts[i] = 0;
66 }
67 }
68
69 return Table;
70 }
71
72 /* create a new hook and add it to the specified table */
73 STATIC FASTCALL PHOOK
74 IntAddHook(PETHREAD Thread, int HookId, BOOLEAN Global, PWINSTATION_OBJECT WinStaObj)
75 {
76 PHOOK Hook;
77 PHOOKTABLE Table = Global ? GlobalHooks : MsqGetHooks(Thread->Win32Thread->MessageQueue);
78 HANDLE Handle;
79
80 if (NULL == Table)
81 {
82 Table = IntAllocHookTable();
83 if (NULL == Table)
84 {
85 return NULL;
86 }
87 if (Global)
88 {
89 GlobalHooks = Table;
90 }
91 else
92 {
93 MsqSetHooks(Thread->Win32Thread->MessageQueue, Table);
94 }
95 }
96
97 Hook = ObmCreateObject(WinStaObj->HandleTable, &Handle,
98 otHookProc, sizeof(HOOK));
99 if (NULL == Hook)
100 {
101 return NULL;
102 }
103
104 Hook->Self = Handle;
105 Hook->Thread = Thread;
106 Hook->HookId = HookId;
107 RtlInitUnicodeString(&Hook->ModuleName, NULL);
108
109 ExAcquireFastMutex(&Table->Lock);
110 InsertHeadList(&Table->Hooks[HOOKID_TO_INDEX(HookId)], &Hook->Chain);
111 ExReleaseFastMutex(&Table->Lock);
112
113 return Hook;
114 }
115
116 /* get the hook table that a given hook belongs to */
117 STATIC PHOOKTABLE FASTCALL
118 IntGetTable(PHOOK Hook)
119 {
120 if (NULL == Hook->Thread || WH_KEYBOARD_LL == Hook->HookId ||
121 WH_MOUSE_LL == Hook->HookId)
122 {
123 return GlobalHooks;
124 }
125
126 return MsqGetHooks(Hook->Thread->Win32Thread->MessageQueue);
127 }
128
129 /* get the first hook in the chain */
130 STATIC PHOOK FASTCALL
131 IntGetFirstHook(PHOOKTABLE Table, int HookId)
132 {
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);
136 }
137
138 /* find the first non-deleted hook in the chain */
139 STATIC PHOOK FASTCALL
140 IntGetFirstValidHook(PHOOKTABLE Table, int HookId)
141 {
142 PHOOK Hook;
143 PLIST_ENTRY Elem;
144
145 ExAcquireFastMutex(&Table->Lock);
146 Hook = IntGetFirstHook(Table, HookId);
147 while (NULL != Hook && NULL == Hook->Proc)
148 {
149 Elem = Hook->Chain.Flink;
150 Hook = (Elem == &Table->Hooks[HOOKID_TO_INDEX(HookId)]
151 ? NULL : CONTAINING_RECORD(Elem, HOOK, Chain));
152 }
153 ExReleaseFastMutex(&Table->Lock);
154
155 return Hook;
156 }
157
158 /* find the next hook in the chain, skipping the deleted ones */
159 STATIC PHOOK FASTCALL
160 IntGetNextHook(PHOOK Hook)
161 {
162 PHOOKTABLE Table = IntGetTable(Hook);
163 int HookId = Hook->HookId;
164 PLIST_ENTRY Elem;
165
166 ExAcquireFastMutex(&Table->Lock);
167 Elem = Hook->Chain.Flink;
168 while (Elem != &Table->Hooks[HOOKID_TO_INDEX(HookId)])
169 {
170 Hook = CONTAINING_RECORD(Elem, HOOK, Chain);
171 if (NULL != Hook->Proc)
172 {
173 ExReleaseFastMutex(&Table->Lock);
174 return Hook;
175 }
176 }
177 ExReleaseFastMutex(&Table->Lock);
178
179 if (NULL != GlobalHooks && Table != GlobalHooks) /* now search through the global table */
180 {
181 return IntGetFirstValidHook(GlobalHooks, HookId);
182 }
183
184 return NULL;
185 }
186
187 /* free a hook, removing it from its chain */
188 STATIC VOID FASTCALL
189 IntFreeHook(PHOOKTABLE Table, PHOOK Hook, PWINSTATION_OBJECT WinStaObj)
190 {
191 RemoveEntryList(&Hook->Chain);
192 RtlFreeUnicodeString(&Hook->ModuleName);
193
194 ObmCloseHandle(WinStaObj->HandleTable, Hook->Self);
195 }
196
197 /* remove a hook, freeing it if the chain is not in use */
198 STATIC FASTCALL VOID
199 IntRemoveHook(PHOOK Hook, PWINSTATION_OBJECT WinStaObj)
200 {
201 PHOOKTABLE Table = IntGetTable(Hook);
202
203 ASSERT(NULL != Table);
204 if (NULL == Table)
205 {
206 return;
207 }
208
209 ExAcquireFastMutex(&Table->Lock);
210 if (0 != Table->Counts[HOOKID_TO_INDEX(Hook->HookId)])
211 {
212 Hook->Proc = NULL; /* chain is in use, just mark it and return */
213 }
214 else
215 {
216 IntFreeHook(Table, Hook, WinStaObj);
217 }
218 ExReleaseFastMutex(&Table->Lock);
219 }
220
221 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
222 STATIC VOID FASTCALL
223 IntReleaseHookChain(PHOOKTABLE Table, int HookId, PWINSTATION_OBJECT WinStaObj)
224 {
225 PLIST_ENTRY Elem;
226 PHOOK HookObj;
227
228 if (NULL == Table)
229 {
230 return;
231 }
232
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)])
237 {
238 ExReleaseFastMutex(&Table->Lock);
239 return;
240 }
241 if (0 == --Table->Counts[HOOKID_TO_INDEX(HookId)])
242 {
243 Elem = Table->Hooks[HOOKID_TO_INDEX(HookId)].Flink;
244 while (Elem != &Table->Hooks[HOOKID_TO_INDEX(HookId)])
245 {
246 HookObj = CONTAINING_RECORD(Elem, HOOK, Chain);
247 Elem = Elem->Flink;
248 if (NULL == HookObj->Proc)
249 {
250 IntFreeHook(Table, HookObj, WinStaObj);
251 }
252 }
253 }
254 ExReleaseFastMutex(&Table->Lock);
255 }
256
257 LRESULT FASTCALL
258 HOOK_CallHooks(INT HookId, INT Code, WPARAM wParam, LPARAM lParam)
259 {
260 PHOOK Hook;
261 PHOOKTABLE Table = MsqGetHooks(PsGetWin32Thread()->MessageQueue);
262 LRESULT Result;
263 PWINSTATION_OBJECT WinStaObj;
264 NTSTATUS Status;
265
266 ASSERT(WH_MINHOOK <= HookId && HookId <= WH_MAXHOOK);
267
268 if (NULL == Table || ! (Hook = IntGetFirstValidHook(Table, HookId)))
269 {
270 /* try global table */
271 Table = GlobalHooks;
272 if (NULL == Table || ! (Hook = IntGetFirstValidHook(Table, HookId)))
273 {
274 return 0; /* no hook set */
275 }
276 }
277
278 if (Hook->Thread != PsGetCurrentThread())
279 {
280 DPRINT1("Calling hooks in other threads not implemented yet");
281 return 0;
282 }
283
284 ExAcquireFastMutex(&Table->Lock);
285 Table->Counts[HOOKID_TO_INDEX(HookId)]++;
286 ExReleaseFastMutex(&Table->Lock);
287 if (Table != GlobalHooks && GlobalHooks != NULL)
288 {
289 ExAcquireFastMutex(&GlobalHooks->Lock);
290 GlobalHooks->Counts[HOOKID_TO_INDEX(HookId)]++;
291 ExReleaseFastMutex(&GlobalHooks->Lock);
292 }
293
294 Result = IntCallHookProc(HookId, Code, wParam, lParam, Hook->Proc,
295 Hook->Ansi, &Hook->ModuleName);
296
297 Status = IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
298 KernelMode,
299 0,
300 &WinStaObj);
301
302 if(! NT_SUCCESS(Status))
303 {
304 DPRINT1("Invalid window station????\n");
305 }
306 else
307 {
308 IntReleaseHookChain(MsqGetHooks(PsGetWin32Thread()->MessageQueue), HookId, WinStaObj);
309 IntReleaseHookChain(GlobalHooks, HookId, WinStaObj);
310 ObDereferenceObject(WinStaObj);
311 }
312
313 return Result;
314 }
315
316 VOID FASTCALL
317 HOOK_DestroyThreadHooks(PETHREAD Thread)
318 {
319 int HookId;
320 PLIST_ENTRY Elem;
321 PHOOK HookObj;
322 PWINSTATION_OBJECT WinStaObj;
323 NTSTATUS Status;
324
325 if (NULL != GlobalHooks)
326 {
327 Status = IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
328 KernelMode,
329 0,
330 &WinStaObj);
331
332 if(! NT_SUCCESS(Status))
333 {
334 DPRINT1("Invalid window station????\n");
335 return;
336 }
337 ExAcquireFastMutex(&GlobalHooks->Lock);
338 for (HookId = WH_MINHOOK; HookId <= WH_MAXHOOK; HookId++)
339 {
340 /* only low-level keyboard/mouse global hooks can be owned by a thread */
341 switch(HookId)
342 {
343 case WH_KEYBOARD_LL:
344 case WH_MOUSE_LL:
345 Elem = GlobalHooks->Hooks[HOOKID_TO_INDEX(HookId)].Flink;
346 while (Elem != &GlobalHooks->Hooks[HOOKID_TO_INDEX(HookId)])
347 {
348 HookObj = CONTAINING_RECORD(Elem, HOOK, Chain);
349 Elem = Elem->Flink;
350 if (HookObj->Thread == Thread)
351 {
352 IntRemoveHook(HookObj, WinStaObj);
353 }
354 }
355 break;
356 }
357 }
358 ExReleaseFastMutex(&GlobalHooks->Lock);
359 ObDereferenceObject(WinStaObj);
360 }
361 }
362
363 LRESULT
364 STDCALL
365 NtUserCallNextHookEx(
366 HHOOK Hook,
367 int Code,
368 WPARAM wParam,
369 LPARAM lParam)
370 {
371 PHOOK HookObj, NextObj;
372 PWINSTATION_OBJECT WinStaObj;
373 NTSTATUS Status;
374
375 Status = IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
376 KernelMode,
377 0,
378 &WinStaObj);
379
380 if(! NT_SUCCESS(Status))
381 {
382 SetLastNtError(Status);
383 return FALSE;
384 }
385
386 Status = ObmReferenceObjectByHandle(WinStaObj->HandleTable, Hook,
387 otHookProc, (PVOID *) &HookObj);
388 ObDereferenceObject(WinStaObj);
389 if (! NT_SUCCESS(Status))
390 {
391 DPRINT1("Invalid handle passed to NtUserCallNextHookEx\n");
392 SetLastNtError(Status);
393 return 0;
394 }
395 ASSERT(Hook == HookObj->Self);
396
397 if (NULL != HookObj->Thread && (HookObj->Thread != PsGetCurrentThread()))
398 {
399 DPRINT1("Thread mismatch\n");
400 ObmDereferenceObject(HookObj);
401 SetLastWin32Error(ERROR_INVALID_HANDLE);
402 return 0;
403 }
404
405 NextObj = IntGetNextHook(HookObj);
406 ObmDereferenceObject(HookObj);
407 if (NULL != NextObj)
408 {
409 DPRINT1("Calling next hook not implemented\n");
410 UNIMPLEMENTED
411 SetLastWin32Error(ERROR_NOT_SUPPORTED);
412 return 0;
413 }
414
415 return 0;
416 }
417
418 DWORD
419 STDCALL
420 NtUserSetWindowsHookAW(
421 DWORD Unknown0,
422 DWORD Unknown1,
423 DWORD Unknown2)
424 {
425 UNIMPLEMENTED
426
427 return 0;
428 }
429
430 HHOOK
431 STDCALL
432 NtUserSetWindowsHookEx(
433 HINSTANCE Mod,
434 PUNICODE_STRING UnsafeModuleName,
435 DWORD ThreadId,
436 int HookId,
437 HOOKPROC HookProc,
438 BOOL Ansi)
439 {
440 PWINSTATION_OBJECT WinStaObj;
441 BOOLEAN Global;
442 PETHREAD Thread;
443 PHOOK Hook;
444 UNICODE_STRING ModuleName;
445 NTSTATUS Status;
446 HHOOK Handle;
447
448 if (HookId < WH_MINHOOK || WH_MAXHOOK < HookId || NULL == HookProc)
449 {
450 SetLastWin32Error(ERROR_INVALID_PARAMETER);
451 return NULL;
452 }
453
454 if (ThreadId) /* thread-local hook */
455 {
456 if (HookId == WH_JOURNALRECORD ||
457 HookId == WH_JOURNALPLAYBACK ||
458 HookId == WH_KEYBOARD_LL ||
459 HookId == WH_MOUSE_LL ||
460 HookId == WH_SYSMSGFILTER)
461 {
462 /* these can only be global */
463 SetLastWin32Error(ERROR_INVALID_PARAMETER);
464 return NULL;
465 }
466 Mod = NULL;
467 Global = FALSE;
468 if (! NT_SUCCESS(PsLookupThreadByThreadId((PVOID) ThreadId, &Thread)))
469 {
470 DPRINT1("Invalid thread id 0x%x\n", ThreadId);
471 SetLastWin32Error(ERROR_INVALID_PARAMETER);
472 return NULL;
473 }
474 if (Thread->ThreadsProcess != PsGetCurrentProcess())
475 {
476 DPRINT1("Can't specify thread belonging to another process\n");
477 SetLastWin32Error(ERROR_INVALID_PARAMETER);
478 return NULL;
479 }
480 }
481 else /* system-global hook */
482 {
483 if (HookId == WH_KEYBOARD_LL || HookId == WH_MOUSE_LL)
484 {
485 Mod = NULL;
486 Thread = PsGetCurrentThread();
487 }
488 else if (NULL == Mod)
489 {
490 SetLastWin32Error(ERROR_INVALID_PARAMETER);
491 return NULL;
492 }
493 else
494 {
495 Thread = NULL;
496 }
497 Global = TRUE;
498 }
499
500 /* We only (partially) support local WH_CBT hooks for now */
501 if (WH_CBT != HookId || Global)
502 {
503 #if 0 /* Removed to get winEmbed working again */
504 UNIMPLEMENTED
505 #else
506 DPRINT1("Not implemented: HookId %d Global %s\n", HookId, Global ? "TRUE" : "FALSE");
507 #endif
508 SetLastWin32Error(ERROR_NOT_SUPPORTED);
509 return NULL;
510 }
511
512 Status = IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
513 KernelMode,
514 0,
515 &WinStaObj);
516
517 if(! NT_SUCCESS(Status))
518 {
519 SetLastNtError(Status);
520 return (HANDLE) NULL;
521 }
522
523 Hook = IntAddHook(Thread, HookId, Global, WinStaObj);
524 if (NULL == Hook)
525 {
526 ObDereferenceObject(WinStaObj);
527 return NULL;
528 }
529
530 if (NULL != Mod)
531 {
532 Status = MmCopyFromCaller(&ModuleName, UnsafeModuleName, sizeof(UNICODE_STRING));
533 if (! NT_SUCCESS(Status))
534 {
535 ObmDereferenceObject(Hook);
536 IntRemoveHook(Hook, WinStaObj);
537 ObDereferenceObject(WinStaObj);
538 SetLastNtError(Status);
539 return NULL;
540 }
541 Hook->ModuleName.Buffer = ExAllocatePoolWithTag(PagedPool,
542 ModuleName.MaximumLength,
543 TAG_HOOK);
544 if (NULL == Hook->ModuleName.Buffer)
545 {
546 ObmDereferenceObject(Hook);
547 IntRemoveHook(Hook, WinStaObj);
548 ObDereferenceObject(WinStaObj);
549 SetLastWin32Error(ERROR_NOT_ENOUGH_MEMORY);
550 return NULL;
551 }
552 Hook->ModuleName.MaximumLength = ModuleName.MaximumLength;
553 Status = MmCopyFromCaller(Hook->ModuleName.Buffer,
554 ModuleName.Buffer,
555 ModuleName.MaximumLength);
556 if (! NT_SUCCESS(Status))
557 {
558 ObmDereferenceObject(Hook);
559 IntRemoveHook(Hook, WinStaObj);
560 ObDereferenceObject(WinStaObj);
561 SetLastNtError(Status);
562 return NULL;
563 }
564 Hook->ModuleName.Length = ModuleName.Length;
565 }
566
567 Hook->Proc = HookProc;
568 Hook->Ansi = Ansi;
569 Handle = Hook->Self;
570
571 ObmDereferenceObject(Hook);
572 ObDereferenceObject(WinStaObj);
573
574 return Handle;
575 }
576
577 DWORD
578 STDCALL
579 NtUserSetWinEventHook(
580 DWORD Unknown0,
581 DWORD Unknown1,
582 DWORD Unknown2,
583 DWORD Unknown3,
584 DWORD Unknown4,
585 DWORD Unknown5,
586 DWORD Unknown6,
587 DWORD Unknown7)
588 {
589 UNIMPLEMENTED
590
591 return 0;
592 }
593
594 BOOL
595 STDCALL
596 NtUserUnhookWindowsHookEx(
597 HHOOK Hook)
598 {
599 PWINSTATION_OBJECT WinStaObj;
600 PHOOK HookObj;
601 NTSTATUS Status;
602
603 Status = IntValidateWindowStationHandle(PROCESS_WINDOW_STATION(),
604 KernelMode,
605 0,
606 &WinStaObj);
607
608 if(! NT_SUCCESS(Status))
609 {
610 SetLastNtError(Status);
611 return FALSE;
612 }
613
614 Status = ObmReferenceObjectByHandle(WinStaObj->HandleTable, Hook,
615 otHookProc, (PVOID *) &HookObj);
616 if (! NT_SUCCESS(Status))
617 {
618 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
619 ObDereferenceObject(WinStaObj);
620 SetLastNtError(Status);
621 return FALSE;
622 }
623 ASSERT(Hook == HookObj->Self);
624
625 IntRemoveHook(HookObj, WinStaObj);
626
627 ObmDereferenceObject(HookObj);
628 ObDereferenceObject(WinStaObj);
629
630 return TRUE;
631 }
632
633 DWORD
634 STDCALL
635 NtUserUnhookWinEvent(
636 DWORD Unknown0)
637 {
638 UNIMPLEMENTED
639
640 return 0;
641 }
642
643 /* EOF */