- Simplify PsGetNextProcess so it works like PsGetNextProcessThread.
[reactos.git] / reactos / subsystems / win32 / 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 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 PHOOK
90 IntAddHook(PETHREAD Thread, int HookId, BOOLEAN Global, PWINSTATION_OBJECT WinStaObj)
91 {
92 PHOOK Hook;
93 PHOOKTABLE Table = Global ? GlobalHooks : MsqGetHooks(((PW32THREAD)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(((PW32THREAD)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(((PW32THREAD)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 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(((PW32THREAD)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 = PsGetCurrentThreadWin32Thread();
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(PsGetCurrentThreadWin32Thread()->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 ExFreePool(Hook->ModuleName.Buffer);
656 ObmDereferenceObject(Hook);
657 IntRemoveHook(Hook, WinStaObj, FALSE);
658 if (NULL != Thread)
659 {
660 ObDereferenceObject(Thread);
661 }
662 ObDereferenceObject(WinStaObj);
663 SetLastNtError(Status);
664 RETURN( NULL);
665 }
666 Hook->ModuleName.Length = ModuleName.Length;
667 }
668
669 Hook->Proc = HookProc;
670 Hook->Ansi = Ansi;
671 Handle = Hook->Self;
672
673 ObmDereferenceObject(Hook);
674 ObDereferenceObject(WinStaObj);
675
676 RETURN( Handle);
677
678 CLEANUP:
679 DPRINT("Leave NtUserSetWindowsHookEx, ret=%i\n",_ret_);
680 UserLeave();
681 END_CLEANUP;
682 }
683
684 DWORD
685 STDCALL
686 NtUserSetWinEventHook(
687 DWORD Unknown0,
688 DWORD Unknown1,
689 DWORD Unknown2,
690 DWORD Unknown3,
691 DWORD Unknown4,
692 DWORD Unknown5,
693 DWORD Unknown6,
694 DWORD Unknown7)
695 {
696 UNIMPLEMENTED
697
698 return 0;
699 }
700
701 BOOL
702 STDCALL
703 NtUserUnhookWindowsHookEx(
704 HHOOK Hook)
705 {
706 PWINSTATION_OBJECT WinStaObj;
707 PHOOK HookObj;
708 NTSTATUS Status;
709 DECLARE_RETURN(BOOL);
710
711 DPRINT("Enter NtUserUnhookWindowsHookEx\n");
712 UserEnterExclusive();
713
714 Status = IntValidateWindowStationHandle(PsGetCurrentProcess()->Win32WindowStation,
715 KernelMode,
716 0,
717 &WinStaObj);
718
719 if (! NT_SUCCESS(Status))
720 {
721 SetLastNtError(Status);
722 RETURN( FALSE);
723 }
724
725 // Status = ObmReferenceObjectByHandle(gHandleTable, Hook,
726 // otHookProc, (PVOID *) &HookObj);
727 if (!(HookObj = IntGetHookObject(Hook)))
728 {
729 DPRINT1("Invalid handle passed to NtUserUnhookWindowsHookEx\n");
730 ObDereferenceObject(WinStaObj);
731 // SetLastNtError(Status);
732 RETURN( FALSE);
733 }
734 ASSERT(Hook == HookObj->Self);
735
736 IntRemoveHook(HookObj, WinStaObj, FALSE);
737
738 ObmDereferenceObject(HookObj);
739 ObDereferenceObject(WinStaObj);
740
741 RETURN( TRUE);
742
743 CLEANUP:
744 DPRINT("Leave NtUserUnhookWindowsHookEx, ret=%i\n",_ret_);
745 UserLeave();
746 END_CLEANUP;
747 }
748
749 DWORD
750 STDCALL
751 NtUserUnhookWinEvent(
752 DWORD Unknown0)
753 {
754 UNIMPLEMENTED
755
756 return 0;
757 }
758
759 /* EOF */