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