- fixme when unexpected children found
[wine] / dlls / user / hook.c
1 /*
2  * Windows hook functions
3  *
4  * Copyright 2002 Alexandre Julliard
5  * Copyright 2005 Dmitry Timoshkov
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * NOTES:
22  *   Status of the various hooks:
23  *     WH_MSGFILTER                 OK
24  *     WH_JOURNALRECORD             Partially implemented
25  *     WH_JOURNALPLAYBACK           Partially implemented
26  *     WH_KEYBOARD                  OK
27  *     WH_GETMESSAGE                OK (FIXME: A/W mapping?)
28  *     WH_CALLWNDPROC               OK (FIXME: A/W mapping?)
29  *     WH_CBT
30  *       HCBT_MOVESIZE              OK
31  *       HCBT_MINMAX                OK
32  *       HCBT_QS                    OK
33  *       HCBT_CREATEWND             OK
34  *       HCBT_DESTROYWND            OK
35  *       HCBT_ACTIVATE              OK
36  *       HCBT_CLICKSKIPPED          OK
37  *       HCBT_KEYSKIPPED            OK
38  *       HCBT_SYSCOMMAND            OK
39  *       HCBT_SETFOCUS              OK
40  *     WH_SYSMSGFILTER              OK
41  *     WH_MOUSE                     OK
42  *     WH_HARDWARE                  Not supported in Win32
43  *     WH_DEBUG                     Not implemented
44  *     WH_SHELL
45  *       HSHELL_WINDOWCREATED       OK
46  *       HSHELL_WINDOWDESTROYED     OK
47  *       HSHELL_ACTIVATESHELLWINDOW Not implemented
48  *       HSHELL_WINDOWACTIVATED     Not implemented
49  *       HSHELL_GETMINRECT          Not implemented
50  *       HSHELL_REDRAW              Not implemented
51  *       HSHELL_TASKMAN             Not implemented
52  *       HSHELL_LANGUAGE            Not implemented
53  *       HSHELL_SYSMENU             Not implemented
54  *       HSHELL_ENDTASK             Not implemented
55  *       HSHELL_ACCESSIBILITYSTATE  Not implemented
56  *       HSHELL_APPCOMMAND          Not implemented
57  *       HSHELL_WINDOWREPLACED      Not implemented
58  *       HSHELL_WINDOWREPLACING     Not implemented
59  *     WH_FOREGROUNDIDLE            Not implemented
60  *     WH_CALLWNDPROCRET            OK (FIXME: A/W mapping?)
61  *     WH_KEYBOARD_LL               Implemented but should use SendMessage instead
62  *     WH_MOUSE_LL                  Implemented but should use SendMessage instead
63  */
64
65 #include "config.h"
66 #include "wine/port.h"
67
68 #include <stdarg.h>
69 #include <assert.h>
70
71 #include "windef.h"
72 #include "winbase.h"
73 #include "winuser.h"
74 #include "winerror.h"
75 #include "win.h"
76 #include "user_private.h"
77 #include "wine/server.h"
78 #include "wine/unicode.h"
79 #include "wine/debug.h"
80 #include "winternl.h"
81
82 WINE_DEFAULT_DEBUG_CHANNEL(hook);
83 WINE_DECLARE_DEBUG_CHANNEL(relay);
84
85 struct hook_info
86 {
87     FARPROC proc;
88     void *handle;
89     DWORD tid;
90     WCHAR module[MAX_PATH];
91 };
92
93 #define WH_WINEVENT (WH_MAXHOOK+1)
94
95 static const char * const hook_names[WH_WINEVENT - WH_MINHOOK + 1] =
96 {
97     "WH_MSGFILTER",
98     "WH_JOURNALRECORD",
99     "WH_JOURNALPLAYBACK",
100     "WH_KEYBOARD",
101     "WH_GETMESSAGE",
102     "WH_CALLWNDPROC",
103     "WH_CBT",
104     "WH_SYSMSGFILTER",
105     "WH_MOUSE",
106     "WH_HARDWARE",
107     "WH_DEBUG",
108     "WH_SHELL",
109     "WH_FOREGROUNDIDLE",
110     "WH_CALLWNDPROCRET",
111     "WH_KEYBOARD_LL",
112     "WH_MOUSE_LL",
113     "WH_WINEVENT"
114 };
115
116
117 /***********************************************************************
118  *              get_ll_hook_timeout
119  *
120  */
121 static UINT get_ll_hook_timeout(void)
122 {
123     /* FIXME: should retrieve LowLevelHooksTimeout in HKEY_CURRENT_USER\Control Panel\Desktop */
124     return 2000;
125 }
126
127
128 /***********************************************************************
129  *              set_windows_hook
130  *
131  * Implementation of SetWindowsHookExA and SetWindowsHookExW.
132  */
133 static HHOOK set_windows_hook( INT id, HOOKPROC proc, HINSTANCE inst, DWORD tid, BOOL unicode )
134 {
135     HHOOK handle = 0;
136     WCHAR module[MAX_PATH];
137     DWORD len;
138
139     if (!proc)
140     {
141         SetLastError( ERROR_INVALID_FILTER_PROC );
142         return 0;
143     }
144
145     if (!proc)
146     {
147         SetLastError( ERROR_INVALID_FILTER_PROC );
148         return 0;
149     }
150
151     /* FIXME: what if the tid belongs to another process? */
152     if (tid)  /* thread-local hook */
153     {
154         if (id == WH_JOURNALRECORD ||
155             id == WH_JOURNALPLAYBACK ||
156             id == WH_KEYBOARD_LL ||
157             id == WH_MOUSE_LL ||
158             id == WH_SYSMSGFILTER)
159         {
160             /* these can only be global */
161             SetLastError( ERROR_INVALID_PARAMETER );
162             return 0;
163         }
164         inst = 0;
165     }
166     else  /* system-global hook */
167     {
168         if (id == WH_KEYBOARD_LL || id == WH_MOUSE_LL) inst = 0;
169         else if (!inst || !(len = GetModuleFileNameW( inst, module, MAX_PATH )) || len >= MAX_PATH)
170         {
171             SetLastError( ERROR_HOOK_NEEDS_HMOD );
172             return 0;
173         }
174     }
175
176     SERVER_START_REQ( set_hook )
177     {
178         req->id        = id;
179         req->pid       = 0;
180         req->tid       = tid;
181         req->event_min = EVENT_MIN;
182         req->event_max = EVENT_MAX;
183         req->flags     = WINEVENT_INCONTEXT;
184         req->unicode   = unicode;
185         if (inst) /* make proc relative to the module base */
186         {
187             req->proc = (void *)((char *)proc - (char *)inst);
188             wine_server_add_data( req, module, strlenW(module) * sizeof(WCHAR) );
189         }
190         else req->proc = proc;
191
192         if (!wine_server_call_err( req ))
193         {
194             handle = reply->handle;
195             get_user_thread_info()->active_hooks = reply->active_hooks;
196         }
197     }
198     SERVER_END_REQ;
199
200     TRACE( "%s %p %lx -> %p\n", hook_names[id-WH_MINHOOK], proc, tid, handle );
201     return handle;
202 }
203
204
205 /***********************************************************************
206  *              call_hook_AtoW
207  */
208 static LRESULT call_hook_AtoW( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam )
209 {
210     LRESULT ret;
211     UNICODE_STRING usBuffer;
212     if (id != WH_CBT || code != HCBT_CREATEWND) ret = proc( code, wparam, lparam );
213     else
214     {
215         CBT_CREATEWNDA *cbtcwA = (CBT_CREATEWNDA *)lparam;
216         CBT_CREATEWNDW cbtcwW;
217         CREATESTRUCTW csW;
218
219         cbtcwW.lpcs = &csW;
220         cbtcwW.hwndInsertAfter = cbtcwA->hwndInsertAfter;
221         csW = *(CREATESTRUCTW *)cbtcwA->lpcs;
222
223         if (HIWORD(cbtcwA->lpcs->lpszName))
224         {
225             RtlCreateUnicodeStringFromAsciiz(&usBuffer,cbtcwA->lpcs->lpszName);
226             csW.lpszName = usBuffer.Buffer;
227         }
228         if (HIWORD(cbtcwA->lpcs->lpszClass))
229         {
230             RtlCreateUnicodeStringFromAsciiz(&usBuffer,cbtcwA->lpcs->lpszClass);
231             csW.lpszClass = usBuffer.Buffer;
232         }
233         ret = proc( code, wparam, (LPARAM)&cbtcwW );
234         cbtcwA->hwndInsertAfter = cbtcwW.hwndInsertAfter;
235         if (HIWORD(csW.lpszName)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszName );
236         if (HIWORD(csW.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszClass );
237     }
238     return ret;
239 }
240
241
242 /***********************************************************************
243  *              call_hook_WtoA
244  */
245 static LRESULT call_hook_WtoA( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam )
246 {
247     LRESULT ret;
248
249     if (id != WH_CBT || code != HCBT_CREATEWND) ret = proc( code, wparam, lparam );
250     else
251     {
252         CBT_CREATEWNDW *cbtcwW = (CBT_CREATEWNDW *)lparam;
253         CBT_CREATEWNDA cbtcwA;
254         CREATESTRUCTA csA;
255         int len;
256
257         cbtcwA.lpcs = &csA;
258         cbtcwA.hwndInsertAfter = cbtcwW->hwndInsertAfter;
259         csA = *(CREATESTRUCTA *)cbtcwW->lpcs;
260
261         if (HIWORD(cbtcwW->lpcs->lpszName)) {
262             len = WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszName, -1, NULL, 0, NULL, NULL );
263             csA.lpszName = HeapAlloc( GetProcessHeap(), 0, len*sizeof(CHAR) );
264             WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszName, -1, (LPSTR)csA.lpszName, len, NULL, NULL );
265         }
266
267         if (HIWORD(cbtcwW->lpcs->lpszClass)) {
268             len = WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszClass, -1, NULL, 0, NULL, NULL );
269             csA.lpszClass = HeapAlloc( GetProcessHeap(), 0, len*sizeof(CHAR) );
270             WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszClass, -1, (LPSTR)csA.lpszClass, len, NULL, NULL );
271         }
272
273         ret = proc( code, wparam, (LPARAM)&cbtcwA );
274         cbtcwW->hwndInsertAfter = cbtcwA.hwndInsertAfter;
275         if (HIWORD(csA.lpszName)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszName );
276         if (HIWORD(csA.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszClass );
277     }
278     return ret;
279 }
280
281
282 /***********************************************************************
283  *              call_hook
284  */
285 static LRESULT call_hook( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam,
286                           BOOL prev_unicode, BOOL next_unicode )
287 {
288     LRESULT ret;
289
290     if (TRACE_ON(relay))
291         DPRINTF( "%04lx:Call hook proc %p (id=%s,code=%x,wp=%08x,lp=%08lx)\n",
292                  GetCurrentThreadId(), proc, hook_names[id-WH_MINHOOK], code, wparam, lparam );
293
294     if (!prev_unicode == !next_unicode) ret = proc( code, wparam, lparam );
295     else if (prev_unicode) ret = call_hook_WtoA( proc, id, code, wparam, lparam );
296     else ret = call_hook_AtoW( proc, id, code, wparam, lparam );
297
298     if (TRACE_ON(relay))
299         DPRINTF( "%04lx:Ret  hook proc %p (id=%s,code=%x,wp=%08x,lp=%08lx) retval=%08lx\n",
300                  GetCurrentThreadId(), proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, ret );
301
302     return ret;
303 }
304
305
306 /***********************************************************************
307  *              get_hook_proc
308  *
309  * Retrieve the hook procedure real value for a module-relative proc
310  */
311 static void *get_hook_proc( void *proc, const WCHAR *module )
312 {
313     HMODULE mod;
314
315     if (!(mod = GetModuleHandleW(module)))
316     {
317         TRACE( "loading %s\n", debugstr_w(module) );
318         /* FIXME: the library will never be freed */
319         if (!(mod = LoadLibraryW(module))) return NULL;
320     }
321     return (char *)mod + (ULONG_PTR)proc;
322 }
323
324
325 /***********************************************************************
326  *              HOOK_CallHooks
327  */
328 LRESULT HOOK_CallHooks( INT id, INT code, WPARAM wparam, LPARAM lparam, BOOL unicode )
329 {
330     struct user_thread_info *thread_info = get_user_thread_info();
331     HOOKPROC proc = NULL;
332     HHOOK handle = 0;
333     DWORD pid = 0, tid = 0;
334     WCHAR module[MAX_PATH];
335     BOOL unicode_hook = FALSE;
336     LRESULT ret = 0;
337
338     USER_CheckNotLock();
339
340     if (!HOOK_IsHooked( id ))
341     {
342         TRACE( "skipping hook %s mask %x\n", hook_names[id-WH_MINHOOK], thread_info->active_hooks );
343         return 0;
344     }
345
346     SERVER_START_REQ( start_hook_chain )
347     {
348         req->id = id;
349         req->event = EVENT_MIN;
350         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
351         if (!wine_server_call( req ))
352         {
353             module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
354             handle       = reply->handle;
355             proc         = reply->proc;
356             pid          = reply->pid;
357             tid          = reply->tid;
358             unicode_hook = reply->unicode;
359             thread_info->active_hooks = reply->active_hooks;
360         }
361     }
362     SERVER_END_REQ;
363
364     if (tid)
365     {
366         TRACE( "calling hook in thread %04lx %s code %x wp %x lp %lx\n",
367                tid, hook_names[id-WH_MINHOOK], code, wparam, lparam );
368
369         switch(id)
370         {
371         case WH_KEYBOARD_LL:
372             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_KEYBOARD_LL_HOOK, wparam, lparam,
373                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
374             break;
375         case WH_MOUSE_LL:
376             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
377                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
378             break;
379         default:
380             ERR("Unknown hook id %d\n", id);
381             assert(0);
382             break;
383         }
384     }
385     else if (proc)
386     {
387         TRACE( "calling hook %p %s code %x wp %x lp %lx module %s\n",
388                proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, debugstr_w(module) );
389
390         if (!module[0] || (proc = get_hook_proc( proc, module )) != NULL)
391         {
392             HHOOK prev = thread_info->hook;
393             thread_info->hook = handle;
394             ret = call_hook( proc, id, code, wparam, lparam, unicode, unicode_hook );
395             thread_info->hook = prev;
396         }
397
398     }
399     else return 0;
400
401     SERVER_START_REQ( finish_hook_chain )
402     {
403         req->id = id;
404         wine_server_call( req );
405     }
406     SERVER_END_REQ;
407     return ret;
408 }
409
410
411 /***********************************************************************
412  *           HOOK_IsHooked
413  */
414 BOOL HOOK_IsHooked( INT id )
415 {
416     struct user_thread_info *thread_info = get_user_thread_info();
417
418     if (!thread_info->active_hooks) return TRUE;
419     return (thread_info->active_hooks & (1 << (id - WH_MINHOOK))) != 0;
420 }
421
422
423 /***********************************************************************
424  *              SetWindowsHookA (USER32.@)
425  */
426 HHOOK WINAPI SetWindowsHookA( INT id, HOOKPROC proc )
427 {
428     return SetWindowsHookExA( id, proc, 0, GetCurrentThreadId() );
429 }
430
431
432 /***********************************************************************
433  *              SetWindowsHookW (USER32.@)
434  */
435 HHOOK WINAPI SetWindowsHookW( INT id, HOOKPROC proc )
436 {
437     return SetWindowsHookExW( id, proc, 0, GetCurrentThreadId() );
438 }
439
440
441 /***********************************************************************
442  *              SetWindowsHookExA (USER32.@)
443  */
444 HHOOK WINAPI SetWindowsHookExA( INT id, HOOKPROC proc, HINSTANCE inst, DWORD tid )
445 {
446     return set_windows_hook( id, proc, inst, tid, FALSE );
447 }
448
449 /***********************************************************************
450  *              SetWindowsHookExW (USER32.@)
451  */
452 HHOOK WINAPI SetWindowsHookExW( INT id, HOOKPROC proc, HINSTANCE inst, DWORD tid )
453 {
454     return set_windows_hook( id, proc, inst, tid, TRUE );
455 }
456
457
458 /***********************************************************************
459  *              UnhookWindowsHook (USER32.@)
460  */
461 BOOL WINAPI UnhookWindowsHook( INT id, HOOKPROC proc )
462 {
463     BOOL ret;
464
465     TRACE( "%s %p\n", hook_names[id-WH_MINHOOK], proc );
466
467     SERVER_START_REQ( remove_hook )
468     {
469         req->handle = 0;
470         req->id   = id;
471         req->proc = proc;
472         ret = !wine_server_call_err( req );
473         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
474     }
475     SERVER_END_REQ;
476     if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
477     return ret;
478 }
479
480
481
482 /***********************************************************************
483  *              UnhookWindowsHookEx (USER32.@)
484  */
485 BOOL WINAPI UnhookWindowsHookEx( HHOOK hhook )
486 {
487     BOOL ret;
488
489     TRACE( "%p\n", hhook );
490
491     SERVER_START_REQ( remove_hook )
492     {
493         req->handle = hhook;
494         req->id     = 0;
495         ret = !wine_server_call_err( req );
496         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
497     }
498     SERVER_END_REQ;
499     if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
500     return ret;
501 }
502
503
504 /***********************************************************************
505  *              CallNextHookEx (USER32.@)
506  */
507 LRESULT WINAPI CallNextHookEx( HHOOK hhook, INT code, WPARAM wparam, LPARAM lparam )
508 {
509     struct user_thread_info *thread_info = get_user_thread_info();
510     HOOKPROC proc = NULL;
511     WCHAR module[MAX_PATH];
512     HHOOK handle = 0;
513     DWORD pid = 0, tid = 0;
514     INT id = 0;
515     BOOL prev_unicode = FALSE, next_unicode = FALSE;
516     LRESULT ret = 0;
517
518     SERVER_START_REQ( get_next_hook )
519     {
520         req->handle = thread_info->hook;
521         req->event = EVENT_MIN;
522         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
523         if (!wine_server_call_err( req ))
524         {
525             module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
526             handle       = reply->next;
527             id           = reply->id;
528             pid          = reply->pid;
529             tid          = reply->tid;
530             proc         = reply->proc;
531             prev_unicode = reply->prev_unicode;
532             next_unicode = reply->next_unicode;
533         }
534     }
535     SERVER_END_REQ;
536
537     if (tid)
538     {
539         TRACE( "calling hook in thread %04lx %s code %x wp %x lp %lx\n",
540                tid, hook_names[id-WH_MINHOOK], code, wparam, lparam );
541
542         switch(id)
543         {
544         case WH_KEYBOARD_LL:
545             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_KEYBOARD_LL_HOOK, wparam, lparam,
546                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
547             break;
548         case WH_MOUSE_LL:
549             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
550                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
551             break;
552         default:
553             ERR("Unknown hook id %d\n", id);
554             assert(0);
555             break;
556         }
557     }
558     else if (proc)
559     {
560         TRACE( "calling hook %p %s code %x wp %x lp %lx module %s\n",
561                proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, debugstr_w(module) );
562
563         if (!module[0] || (proc = get_hook_proc( proc, module )) != NULL)
564         {
565             HHOOK prev = thread_info->hook;
566             thread_info->hook = handle;
567             ret = call_hook( proc, id, code, wparam, lparam, prev_unicode, next_unicode );
568             thread_info->hook = prev;
569         }
570     }
571     return ret;
572 }
573
574
575 /***********************************************************************
576  *              CallMsgFilterA (USER32.@)
577  */
578 BOOL WINAPI CallMsgFilterA( LPMSG msg, INT code )
579 {
580     if (HOOK_CallHooks( WH_SYSMSGFILTER, code, 0, (LPARAM)msg, FALSE )) return TRUE;
581     return HOOK_CallHooks( WH_MSGFILTER, code, 0, (LPARAM)msg, FALSE );
582 }
583
584
585 /***********************************************************************
586  *              CallMsgFilterW (USER32.@)
587  */
588 BOOL WINAPI CallMsgFilterW( LPMSG msg, INT code )
589 {
590     if (HOOK_CallHooks( WH_SYSMSGFILTER, code, 0, (LPARAM)msg, TRUE )) return TRUE;
591     return HOOK_CallHooks( WH_MSGFILTER, code, 0, (LPARAM)msg, TRUE );
592 }
593
594
595 /***********************************************************************
596  *           SetWinEventHook                            [USER32.@]
597  *
598  * Set up an event hook for a set of events.
599  *
600  * PARAMS
601  *  event_min [I] Lowest event handled by pfnProc
602  *  event_max [I] Highest event handled by pfnProc
603  *  inst      [I] DLL containing pfnProc
604  *  proc      [I] Callback event hook function
605  *  pid       [I] Process to get events from, or 0 for all processes
606  *  tid       [I] Thread to get events from, or 0 for all threads
607  *  flags     [I] Flags indicating the status of pfnProc
608  *
609  * RETURNS
610  *  Success: A handle representing the hook.
611  *  Failure: A NULL handle.
612  */
613 HWINEVENTHOOK WINAPI SetWinEventHook(DWORD event_min, DWORD event_max,
614                                      HMODULE inst, WINEVENTPROC proc,
615                                      DWORD pid, DWORD tid, DWORD flags)
616 {
617     HWINEVENTHOOK handle = 0;
618     WCHAR module[MAX_PATH];
619     DWORD len;
620
621     TRACE("%ld,%ld,%p,%p,%08lx,%04lx,%08lx\n", event_min, event_max, inst,
622           proc, pid, tid, flags);
623
624     if (inst)
625     {
626         if (!(len = GetModuleFileNameW(inst, module, MAX_PATH)) || len >= MAX_PATH)
627             inst = 0;
628     }
629
630     if ((flags & WINEVENT_INCONTEXT) && !inst)
631     {
632         SetLastError(ERROR_HOOK_NEEDS_HMOD);
633         return 0;
634     }
635
636     if (event_min > event_max)
637     {
638         SetLastError(ERROR_INVALID_HOOK_FILTER);
639         return 0;
640     }
641
642     /* FIXME: what if the tid or pid belongs to another process? */
643     if (tid)  /* thread-local hook */
644         inst = 0;
645
646     SERVER_START_REQ( set_hook )
647     {
648         req->id        = WH_WINEVENT;
649         req->pid       = pid;
650         req->tid       = tid;
651         req->event_min = event_min;
652         req->event_max = event_max;
653         req->flags     = flags;
654         req->unicode   = 1;
655         if (inst) /* make proc relative to the module base */
656         {
657             req->proc = (void *)((char *)proc - (char *)inst);
658             wine_server_add_data( req, module, strlenW(module) * sizeof(WCHAR) );
659         }
660         else req->proc = proc;
661
662         if (!wine_server_call_err( req ))
663         {
664             handle = reply->handle;
665             get_user_thread_info()->active_hooks = reply->active_hooks;
666         }
667     }
668     SERVER_END_REQ;
669
670     TRACE("-> %p\n", handle);
671     return handle;
672 }
673
674
675 /***********************************************************************
676  *           UnhookWinEvent                             [USER32.@]
677  *
678  * Remove an event hook for a set of events.
679  *
680  * PARAMS
681  *  hEventHook [I] Event hook to remove
682  *
683  * RETURNS
684  *  Success: TRUE. The event hook has been removed.
685  *  Failure: FALSE, if hEventHook is invalid.
686  */
687 BOOL WINAPI UnhookWinEvent(HWINEVENTHOOK hEventHook)
688 {
689     BOOL ret;
690
691     TRACE( "%p\n", hEventHook );
692
693     SERVER_START_REQ( remove_hook )
694     {
695         req->handle = hEventHook;
696         req->id     = WH_WINEVENT;
697         ret = !wine_server_call_err( req );
698         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
699     }
700     SERVER_END_REQ;
701     return ret;
702 }
703
704 inline static BOOL find_first_hook(DWORD id, DWORD event, HWND hwnd, LONG object_id,
705                                    LONG child_id, struct hook_info *info)
706 {
707     struct user_thread_info *thread_info = get_user_thread_info();
708     BOOL ret;
709
710     if (!HOOK_IsHooked( id ))
711     {
712         TRACE( "skipping hook %s mask %x\n", hook_names[id-WH_MINHOOK], thread_info->active_hooks );
713         return FALSE;
714     }
715
716     SERVER_START_REQ( start_hook_chain )
717     {
718         req->id = id;
719         req->event = event;
720         req->window = hwnd;
721         req->object_id = object_id;
722         req->child_id = child_id;
723         wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
724         ret = !wine_server_call( req );
725         if (ret)
726         {
727             info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
728             info->handle    = reply->handle;
729             info->proc      = reply->proc;
730             info->tid       = reply->tid;
731             thread_info->active_hooks = reply->active_hooks;
732         }
733     }
734     SERVER_END_REQ;
735     return ret && (info->tid || info->proc);
736 }
737
738 inline static BOOL find_next_hook(DWORD event, HWND hwnd, LONG object_id,
739                                   LONG child_id, struct hook_info *info)
740 {
741     BOOL ret;
742
743     SERVER_START_REQ( get_next_hook )
744     {
745         req->handle = info->handle;
746         req->event = event;
747         req->window = hwnd;
748         req->object_id = object_id;
749         req->child_id = child_id;
750         wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
751         ret = !wine_server_call( req );
752         if (ret)
753         {
754             info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
755             info->handle    = reply->next;
756             info->proc      = reply->proc;
757             info->tid       = reply->tid;
758         }
759     }
760     SERVER_END_REQ;
761     return ret;
762 }
763
764 inline static void find_hook_close(DWORD id)
765 {
766     SERVER_START_REQ( finish_hook_chain )
767     {
768         req->id = id;
769         wine_server_call( req );
770     }
771     SERVER_END_REQ;
772 }
773
774 /***********************************************************************
775  *           NotifyWinEvent                             [USER32.@]
776  *
777  * Inform the OS that an event has occurred.
778  *
779  * PARAMS
780  *  event     [I] Id of the event
781  *  hwnd      [I] Window holding the object that created the event
782  *  object_id [I] Type of object that created the event
783  *  child_id  [I] Child object of nId, or CHILDID_SELF.
784  *
785  * RETURNS
786  *  Nothing.
787  */
788 void WINAPI NotifyWinEvent(DWORD event, HWND hwnd, LONG object_id, LONG child_id)
789 {
790     struct hook_info info;
791
792     TRACE("%04lx,%p,%ld,%ld\n", event, hwnd, object_id, child_id);
793
794     if (!hwnd)
795     {
796         SetLastError(ERROR_INVALID_WINDOW_HANDLE);
797         return;
798     }
799
800     USER_CheckNotLock();
801
802 #if 0
803     if (event & 0x80000000)
804     {
805         /* FIXME: on 64-bit platforms we need to invent some other way for
806          * passing parameters, nId and nChildId can't hold full [W|L]PARAM.
807          * struct call_hook *hook = (LRESULT *)hWnd;
808          * wparam = hook->wparam;
809          * lparam = hook->lparam;
810          */
811         LRESULT *ret = (LRESULT *)hwnd;
812         INT id, code, unicode;
813
814         id = (dwEvent & 0x7fff0000) >> 16;
815         code = event & 0x7fff;
816         unicode = event & 0x8000;
817         *ret = HOOK_CallHooks(id, code, object_id, child_id, unicode);
818         return;
819     }
820 #endif
821
822     if (!find_first_hook(WH_WINEVENT, event, hwnd, object_id, child_id, &info)) return;
823
824     do
825     {
826         if (info.proc)
827         {
828             TRACE( "calling WH_WINEVENT hook %p event %lx hwnd %p %lx %lx module %s\n",
829                    info.proc, event, hwnd, object_id, child_id, debugstr_w(info.module) );
830
831             if (!info.module[0] || (info.proc = get_hook_proc( info.proc, info.module )) != NULL)
832             {
833                 if (TRACE_ON(relay))
834                     DPRINTF( "%04lx:Call winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
835                              GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
836                              child_id, GetCurrentThreadId(), GetCurrentTime());
837
838                 info.proc(info.handle, event, hwnd, object_id, child_id,
839                           GetCurrentThreadId(), GetCurrentTime());
840
841                 if (TRACE_ON(relay))
842                     DPRINTF( "%04lx:Ret  winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
843                              GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
844                              child_id, GetCurrentThreadId(), GetCurrentTime());
845             }
846         }
847         else
848             break;
849     }
850     while (find_next_hook(event, hwnd, object_id, child_id, &info));
851
852     find_hook_close(WH_WINEVENT);
853 }
854
855
856 /***********************************************************************
857  *           IsWinEventHookInstalled                       [USER32.@]
858  *
859  * Determine if an event hook is installed for an event.
860  *
861  * PARAMS
862  *  dwEvent  [I] Id of the event
863  *
864  * RETURNS
865  *  TRUE,  If there are any hooks installed for the event.
866  *  FALSE, Otherwise.
867  *
868  * BUGS
869  *  Not implemented.
870  */
871 BOOL WINAPI IsWinEventHookInstalled(DWORD dwEvent)
872 {
873     FIXME("(%ld)-stub!\n", dwEvent);
874     return TRUE;
875 }