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