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