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