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