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