user: Make ExitWindowsEx asynchronous by deferring the real work to the explorer...
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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     /* FIXME: what if the tid belongs to another process? */
146     if (tid)  /* thread-local hook */
147     {
148         if (id == WH_JOURNALRECORD ||
149             id == WH_JOURNALPLAYBACK ||
150             id == WH_KEYBOARD_LL ||
151             id == WH_MOUSE_LL ||
152             id == WH_SYSMSGFILTER)
153         {
154             /* these can only be global */
155             SetLastError( ERROR_INVALID_PARAMETER );
156             return 0;
157         }
158         inst = 0;
159     }
160     else  /* system-global hook */
161     {
162         if (id == WH_KEYBOARD_LL || id == WH_MOUSE_LL) inst = 0;
163         else if (!inst || !(len = GetModuleFileNameW( inst, module, MAX_PATH )) || len >= MAX_PATH)
164         {
165             SetLastError( ERROR_HOOK_NEEDS_HMOD );
166             return 0;
167         }
168     }
169
170     SERVER_START_REQ( set_hook )
171     {
172         req->id        = id;
173         req->pid       = 0;
174         req->tid       = tid;
175         req->event_min = EVENT_MIN;
176         req->event_max = EVENT_MAX;
177         req->flags     = WINEVENT_INCONTEXT;
178         req->unicode   = unicode;
179         if (inst) /* make proc relative to the module base */
180         {
181             req->proc = (void *)((char *)proc - (char *)inst);
182             wine_server_add_data( req, module, strlenW(module) * sizeof(WCHAR) );
183         }
184         else req->proc = proc;
185
186         if (!wine_server_call_err( req ))
187         {
188             handle = reply->handle;
189             get_user_thread_info()->active_hooks = reply->active_hooks;
190         }
191     }
192     SERVER_END_REQ;
193
194     TRACE( "%s %p %lx -> %p\n", hook_names[id-WH_MINHOOK], proc, tid, handle );
195     return handle;
196 }
197
198
199 /***********************************************************************
200  *              call_hook_AtoW
201  */
202 static LRESULT call_hook_AtoW( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam )
203 {
204     LRESULT ret;
205     UNICODE_STRING usBuffer;
206     if (id != WH_CBT || code != HCBT_CREATEWND) ret = proc( code, wparam, lparam );
207     else
208     {
209         CBT_CREATEWNDA *cbtcwA = (CBT_CREATEWNDA *)lparam;
210         CBT_CREATEWNDW cbtcwW;
211         CREATESTRUCTW csW;
212
213         cbtcwW.lpcs = &csW;
214         cbtcwW.hwndInsertAfter = cbtcwA->hwndInsertAfter;
215         csW = *(CREATESTRUCTW *)cbtcwA->lpcs;
216
217         if (HIWORD(cbtcwA->lpcs->lpszName))
218         {
219             RtlCreateUnicodeStringFromAsciiz(&usBuffer,cbtcwA->lpcs->lpszName);
220             csW.lpszName = usBuffer.Buffer;
221         }
222         if (HIWORD(cbtcwA->lpcs->lpszClass))
223         {
224             RtlCreateUnicodeStringFromAsciiz(&usBuffer,cbtcwA->lpcs->lpszClass);
225             csW.lpszClass = usBuffer.Buffer;
226         }
227         ret = proc( code, wparam, (LPARAM)&cbtcwW );
228         cbtcwA->hwndInsertAfter = cbtcwW.hwndInsertAfter;
229         if (HIWORD(csW.lpszName)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszName );
230         if (HIWORD(csW.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszClass );
231     }
232     return ret;
233 }
234
235
236 /***********************************************************************
237  *              call_hook_WtoA
238  */
239 static LRESULT call_hook_WtoA( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam )
240 {
241     LRESULT ret;
242
243     if (id != WH_CBT || code != HCBT_CREATEWND) ret = proc( code, wparam, lparam );
244     else
245     {
246         CBT_CREATEWNDW *cbtcwW = (CBT_CREATEWNDW *)lparam;
247         CBT_CREATEWNDA cbtcwA;
248         CREATESTRUCTA csA;
249         int len;
250
251         cbtcwA.lpcs = &csA;
252         cbtcwA.hwndInsertAfter = cbtcwW->hwndInsertAfter;
253         csA = *(CREATESTRUCTA *)cbtcwW->lpcs;
254
255         if (HIWORD(cbtcwW->lpcs->lpszName)) {
256             len = WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszName, -1, NULL, 0, NULL, NULL );
257             csA.lpszName = HeapAlloc( GetProcessHeap(), 0, len*sizeof(CHAR) );
258             WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszName, -1, (LPSTR)csA.lpszName, len, NULL, NULL );
259         }
260
261         if (HIWORD(cbtcwW->lpcs->lpszClass)) {
262             len = WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszClass, -1, NULL, 0, NULL, NULL );
263             csA.lpszClass = HeapAlloc( GetProcessHeap(), 0, len*sizeof(CHAR) );
264             WideCharToMultiByte( CP_ACP, 0, cbtcwW->lpcs->lpszClass, -1, (LPSTR)csA.lpszClass, len, NULL, NULL );
265         }
266
267         ret = proc( code, wparam, (LPARAM)&cbtcwA );
268         cbtcwW->hwndInsertAfter = cbtcwA.hwndInsertAfter;
269         if (HIWORD(csA.lpszName)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszName );
270         if (HIWORD(csA.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszClass );
271     }
272     return ret;
273 }
274
275
276 /***********************************************************************
277  *              call_hook
278  */
279 static LRESULT call_hook( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam,
280                           BOOL prev_unicode, BOOL next_unicode )
281 {
282     LRESULT ret;
283
284     if (TRACE_ON(relay))
285         DPRINTF( "%04lx:Call hook proc %p (id=%s,code=%x,wp=%08x,lp=%08lx)\n",
286                  GetCurrentThreadId(), proc, hook_names[id-WH_MINHOOK], code, wparam, lparam );
287
288     if (!prev_unicode == !next_unicode) ret = proc( code, wparam, lparam );
289     else if (prev_unicode) ret = call_hook_WtoA( proc, id, code, wparam, lparam );
290     else ret = call_hook_AtoW( proc, id, code, wparam, lparam );
291
292     if (TRACE_ON(relay))
293         DPRINTF( "%04lx:Ret  hook proc %p (id=%s,code=%x,wp=%08x,lp=%08lx) retval=%08lx\n",
294                  GetCurrentThreadId(), proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, ret );
295
296     return ret;
297 }
298
299
300 /***********************************************************************
301  *              get_hook_proc
302  *
303  * Retrieve the hook procedure real value for a module-relative proc
304  */
305 static void *get_hook_proc( void *proc, const WCHAR *module )
306 {
307     HMODULE mod;
308
309     if (!(mod = GetModuleHandleW(module)))
310     {
311         TRACE( "loading %s\n", debugstr_w(module) );
312         /* FIXME: the library will never be freed */
313         if (!(mod = LoadLibraryW(module))) return NULL;
314     }
315     return (char *)mod + (ULONG_PTR)proc;
316 }
317
318
319 /***********************************************************************
320  *              HOOK_CallHooks
321  */
322 LRESULT HOOK_CallHooks( INT id, INT code, WPARAM wparam, LPARAM lparam, BOOL unicode )
323 {
324     struct user_thread_info *thread_info = get_user_thread_info();
325     HOOKPROC proc = NULL;
326     HHOOK handle = 0;
327     DWORD pid = 0, tid = 0;
328     WCHAR module[MAX_PATH];
329     BOOL unicode_hook = FALSE;
330     DWORD_PTR ret = 0;
331
332     USER_CheckNotLock();
333
334     if (!HOOK_IsHooked( id ))
335     {
336         TRACE( "skipping hook %s mask %x\n", hook_names[id-WH_MINHOOK], thread_info->active_hooks );
337         return 0;
338     }
339
340     SERVER_START_REQ( start_hook_chain )
341     {
342         req->id = id;
343         req->event = EVENT_MIN;
344         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
345         if (!wine_server_call( req ))
346         {
347             module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
348             handle       = reply->handle;
349             proc         = reply->proc;
350             pid          = reply->pid;
351             tid          = reply->tid;
352             unicode_hook = reply->unicode;
353             thread_info->active_hooks = reply->active_hooks;
354         }
355     }
356     SERVER_END_REQ;
357
358     if (tid)
359     {
360         TRACE( "calling hook in thread %04lx %s code %x wp %x lp %lx\n",
361                tid, hook_names[id-WH_MINHOOK], code, wparam, lparam );
362
363         switch(id)
364         {
365         case WH_KEYBOARD_LL:
366             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_KEYBOARD_LL_HOOK, wparam, lparam,
367                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
368             break;
369         case WH_MOUSE_LL:
370             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
371                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
372             break;
373         default:
374             ERR("Unknown hook id %d\n", id);
375             assert(0);
376             break;
377         }
378     }
379     else if (proc)
380     {
381         TRACE( "calling hook %p %s code %x wp %x lp %lx module %s\n",
382                proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, debugstr_w(module) );
383
384         if (!module[0] || (proc = get_hook_proc( proc, module )) != NULL)
385         {
386             HHOOK prev = thread_info->hook;
387             thread_info->hook = handle;
388             ret = call_hook( proc, id, code, wparam, lparam, unicode, unicode_hook );
389             thread_info->hook = prev;
390         }
391
392     }
393     else return 0;
394
395     SERVER_START_REQ( finish_hook_chain )
396     {
397         req->id = id;
398         wine_server_call( req );
399     }
400     SERVER_END_REQ;
401     return ret;
402 }
403
404
405 /***********************************************************************
406  *           HOOK_IsHooked
407  */
408 BOOL HOOK_IsHooked( INT id )
409 {
410     struct user_thread_info *thread_info = get_user_thread_info();
411
412     if (!thread_info->active_hooks) return TRUE;
413     return (thread_info->active_hooks & (1 << (id - WH_MINHOOK))) != 0;
414 }
415
416
417 /***********************************************************************
418  *              SetWindowsHookA (USER32.@)
419  */
420 HHOOK WINAPI SetWindowsHookA( INT id, HOOKPROC proc )
421 {
422     return SetWindowsHookExA( id, proc, 0, GetCurrentThreadId() );
423 }
424
425
426 /***********************************************************************
427  *              SetWindowsHookW (USER32.@)
428  */
429 HHOOK WINAPI SetWindowsHookW( INT id, HOOKPROC proc )
430 {
431     return SetWindowsHookExW( id, proc, 0, GetCurrentThreadId() );
432 }
433
434
435 /***********************************************************************
436  *              SetWindowsHookExA (USER32.@)
437  */
438 HHOOK WINAPI SetWindowsHookExA( INT id, HOOKPROC proc, HINSTANCE inst, DWORD tid )
439 {
440     return set_windows_hook( id, proc, inst, tid, FALSE );
441 }
442
443 /***********************************************************************
444  *              SetWindowsHookExW (USER32.@)
445  */
446 HHOOK WINAPI SetWindowsHookExW( INT id, HOOKPROC proc, HINSTANCE inst, DWORD tid )
447 {
448     return set_windows_hook( id, proc, inst, tid, TRUE );
449 }
450
451
452 /***********************************************************************
453  *              UnhookWindowsHook (USER32.@)
454  */
455 BOOL WINAPI UnhookWindowsHook( INT id, HOOKPROC proc )
456 {
457     BOOL ret;
458
459     TRACE( "%s %p\n", hook_names[id-WH_MINHOOK], proc );
460
461     SERVER_START_REQ( remove_hook )
462     {
463         req->handle = 0;
464         req->id   = id;
465         req->proc = proc;
466         ret = !wine_server_call_err( req );
467         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
468     }
469     SERVER_END_REQ;
470     if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
471     return ret;
472 }
473
474
475
476 /***********************************************************************
477  *              UnhookWindowsHookEx (USER32.@)
478  */
479 BOOL WINAPI UnhookWindowsHookEx( HHOOK hhook )
480 {
481     BOOL ret;
482
483     TRACE( "%p\n", hhook );
484
485     SERVER_START_REQ( remove_hook )
486     {
487         req->handle = hhook;
488         req->id     = 0;
489         ret = !wine_server_call_err( req );
490         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
491     }
492     SERVER_END_REQ;
493     if (!ret && GetLastError() == ERROR_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE );
494     return ret;
495 }
496
497
498 /***********************************************************************
499  *              CallNextHookEx (USER32.@)
500  */
501 LRESULT WINAPI CallNextHookEx( HHOOK hhook, INT code, WPARAM wparam, LPARAM lparam )
502 {
503     struct user_thread_info *thread_info = get_user_thread_info();
504     HOOKPROC proc = NULL;
505     WCHAR module[MAX_PATH];
506     HHOOK handle = 0;
507     DWORD pid = 0, tid = 0;
508     INT id = 0;
509     BOOL prev_unicode = FALSE, next_unicode = FALSE;
510     DWORD_PTR ret = 0;
511
512     SERVER_START_REQ( get_next_hook )
513     {
514         req->handle = thread_info->hook;
515         req->event = EVENT_MIN;
516         wine_server_set_reply( req, module, sizeof(module)-sizeof(WCHAR) );
517         if (!wine_server_call_err( req ))
518         {
519             module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
520             handle       = reply->next;
521             id           = reply->id;
522             pid          = reply->pid;
523             tid          = reply->tid;
524             proc         = reply->proc;
525             prev_unicode = reply->prev_unicode;
526             next_unicode = reply->next_unicode;
527         }
528     }
529     SERVER_END_REQ;
530
531     if (tid)
532     {
533         TRACE( "calling hook in thread %04lx %s code %x wp %x lp %lx\n",
534                tid, hook_names[id-WH_MINHOOK], code, wparam, lparam );
535
536         switch(id)
537         {
538         case WH_KEYBOARD_LL:
539             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_KEYBOARD_LL_HOOK, wparam, lparam,
540                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
541             break;
542         case WH_MOUSE_LL:
543             MSG_SendInternalMessageTimeout( pid, tid, WM_WINE_MOUSE_LL_HOOK, wparam, lparam,
544                                             SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret );
545             break;
546         default:
547             ERR("Unknown hook id %d\n", id);
548             assert(0);
549             break;
550         }
551     }
552     else if (proc)
553     {
554         TRACE( "calling hook %p %s code %x wp %x lp %lx module %s\n",
555                proc, hook_names[id-WH_MINHOOK], code, wparam, lparam, debugstr_w(module) );
556
557         if (!module[0] || (proc = get_hook_proc( proc, module )) != NULL)
558         {
559             HHOOK prev = thread_info->hook;
560             thread_info->hook = handle;
561             ret = call_hook( proc, id, code, wparam, lparam, prev_unicode, next_unicode );
562             thread_info->hook = prev;
563         }
564     }
565     return ret;
566 }
567
568
569 /***********************************************************************
570  *              CallMsgFilterA (USER32.@)
571  */
572 BOOL WINAPI CallMsgFilterA( LPMSG msg, INT code )
573 {
574     if (HOOK_CallHooks( WH_SYSMSGFILTER, code, 0, (LPARAM)msg, FALSE )) return TRUE;
575     return HOOK_CallHooks( WH_MSGFILTER, code, 0, (LPARAM)msg, FALSE );
576 }
577
578
579 /***********************************************************************
580  *              CallMsgFilterW (USER32.@)
581  */
582 BOOL WINAPI CallMsgFilterW( LPMSG msg, INT code )
583 {
584     if (HOOK_CallHooks( WH_SYSMSGFILTER, code, 0, (LPARAM)msg, TRUE )) return TRUE;
585     return HOOK_CallHooks( WH_MSGFILTER, code, 0, (LPARAM)msg, TRUE );
586 }
587
588
589 /***********************************************************************
590  *           SetWinEventHook                            [USER32.@]
591  *
592  * Set up an event hook for a set of events.
593  *
594  * PARAMS
595  *  event_min [I] Lowest event handled by pfnProc
596  *  event_max [I] Highest event handled by pfnProc
597  *  inst      [I] DLL containing pfnProc
598  *  proc      [I] Callback event hook function
599  *  pid       [I] Process to get events from, or 0 for all processes
600  *  tid       [I] Thread to get events from, or 0 for all threads
601  *  flags     [I] Flags indicating the status of pfnProc
602  *
603  * RETURNS
604  *  Success: A handle representing the hook.
605  *  Failure: A NULL handle.
606  */
607 HWINEVENTHOOK WINAPI SetWinEventHook(DWORD event_min, DWORD event_max,
608                                      HMODULE inst, WINEVENTPROC proc,
609                                      DWORD pid, DWORD tid, DWORD flags)
610 {
611     HWINEVENTHOOK handle = 0;
612     WCHAR module[MAX_PATH];
613     DWORD len;
614
615     TRACE("%ld,%ld,%p,%p,%08lx,%04lx,%08lx\n", event_min, event_max, inst,
616           proc, pid, tid, flags);
617
618     if (inst)
619     {
620         if (!(len = GetModuleFileNameW(inst, module, MAX_PATH)) || len >= MAX_PATH)
621             inst = 0;
622     }
623
624     if ((flags & WINEVENT_INCONTEXT) && !inst)
625     {
626         SetLastError(ERROR_HOOK_NEEDS_HMOD);
627         return 0;
628     }
629
630     if (event_min > event_max)
631     {
632         SetLastError(ERROR_INVALID_HOOK_FILTER);
633         return 0;
634     }
635
636     /* FIXME: what if the tid or pid belongs to another process? */
637     if (tid)  /* thread-local hook */
638         inst = 0;
639
640     SERVER_START_REQ( set_hook )
641     {
642         req->id        = WH_WINEVENT;
643         req->pid       = pid;
644         req->tid       = tid;
645         req->event_min = event_min;
646         req->event_max = event_max;
647         req->flags     = flags;
648         req->unicode   = 1;
649         if (inst) /* make proc relative to the module base */
650         {
651             req->proc = (void *)((char *)proc - (char *)inst);
652             wine_server_add_data( req, module, strlenW(module) * sizeof(WCHAR) );
653         }
654         else req->proc = proc;
655
656         if (!wine_server_call_err( req ))
657         {
658             handle = reply->handle;
659             get_user_thread_info()->active_hooks = reply->active_hooks;
660         }
661     }
662     SERVER_END_REQ;
663
664     TRACE("-> %p\n", handle);
665     return handle;
666 }
667
668
669 /***********************************************************************
670  *           UnhookWinEvent                             [USER32.@]
671  *
672  * Remove an event hook for a set of events.
673  *
674  * PARAMS
675  *  hEventHook [I] Event hook to remove
676  *
677  * RETURNS
678  *  Success: TRUE. The event hook has been removed.
679  *  Failure: FALSE, if hEventHook is invalid.
680  */
681 BOOL WINAPI UnhookWinEvent(HWINEVENTHOOK hEventHook)
682 {
683     BOOL ret;
684
685     TRACE( "%p\n", hEventHook );
686
687     SERVER_START_REQ( remove_hook )
688     {
689         req->handle = hEventHook;
690         req->id     = WH_WINEVENT;
691         ret = !wine_server_call_err( req );
692         if (ret) get_user_thread_info()->active_hooks = reply->active_hooks;
693     }
694     SERVER_END_REQ;
695     return ret;
696 }
697
698 inline static BOOL find_first_hook(DWORD id, DWORD event, HWND hwnd, LONG object_id,
699                                    LONG child_id, struct hook_info *info)
700 {
701     struct user_thread_info *thread_info = get_user_thread_info();
702     BOOL ret;
703
704     if (!HOOK_IsHooked( id ))
705     {
706         TRACE( "skipping hook %s mask %x\n", hook_names[id-WH_MINHOOK], thread_info->active_hooks );
707         return FALSE;
708     }
709
710     SERVER_START_REQ( start_hook_chain )
711     {
712         req->id = id;
713         req->event = event;
714         req->window = hwnd;
715         req->object_id = object_id;
716         req->child_id = child_id;
717         wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
718         ret = !wine_server_call( req );
719         if (ret)
720         {
721             info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
722             info->handle    = reply->handle;
723             info->proc      = reply->proc;
724             info->tid       = reply->tid;
725             thread_info->active_hooks = reply->active_hooks;
726         }
727     }
728     SERVER_END_REQ;
729     return ret && (info->tid || info->proc);
730 }
731
732 inline static BOOL find_next_hook(DWORD event, HWND hwnd, LONG object_id,
733                                   LONG child_id, struct hook_info *info)
734 {
735     BOOL ret;
736
737     SERVER_START_REQ( get_next_hook )
738     {
739         req->handle = info->handle;
740         req->event = event;
741         req->window = hwnd;
742         req->object_id = object_id;
743         req->child_id = child_id;
744         wine_server_set_reply( req, info->module, sizeof(info->module)-sizeof(WCHAR) );
745         ret = !wine_server_call( req );
746         if (ret)
747         {
748             info->module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0;
749             info->handle    = reply->next;
750             info->proc      = reply->proc;
751             info->tid       = reply->tid;
752         }
753     }
754     SERVER_END_REQ;
755     return ret;
756 }
757
758 inline static void find_hook_close(DWORD id)
759 {
760     SERVER_START_REQ( finish_hook_chain )
761     {
762         req->id = id;
763         wine_server_call( req );
764     }
765     SERVER_END_REQ;
766 }
767
768 /***********************************************************************
769  *           NotifyWinEvent                             [USER32.@]
770  *
771  * Inform the OS that an event has occurred.
772  *
773  * PARAMS
774  *  event     [I] Id of the event
775  *  hwnd      [I] Window holding the object that created the event
776  *  object_id [I] Type of object that created the event
777  *  child_id  [I] Child object of nId, or CHILDID_SELF.
778  *
779  * RETURNS
780  *  Nothing.
781  */
782 void WINAPI NotifyWinEvent(DWORD event, HWND hwnd, LONG object_id, LONG child_id)
783 {
784     struct hook_info info;
785
786     TRACE("%04lx,%p,%ld,%ld\n", event, hwnd, object_id, child_id);
787
788     if (!hwnd)
789     {
790         SetLastError(ERROR_INVALID_WINDOW_HANDLE);
791         return;
792     }
793
794     USER_CheckNotLock();
795
796 #if 0
797     if (event & 0x80000000)
798     {
799         /* FIXME: on 64-bit platforms we need to invent some other way for
800          * passing parameters, nId and nChildId can't hold full [W|L]PARAM.
801          * struct call_hook *hook = (LRESULT *)hWnd;
802          * wparam = hook->wparam;
803          * lparam = hook->lparam;
804          */
805         LRESULT *ret = (LRESULT *)hwnd;
806         INT id, code, unicode;
807
808         id = (dwEvent & 0x7fff0000) >> 16;
809         code = event & 0x7fff;
810         unicode = event & 0x8000;
811         *ret = HOOK_CallHooks(id, code, object_id, child_id, unicode);
812         return;
813     }
814 #endif
815
816     if (!find_first_hook(WH_WINEVENT, event, hwnd, object_id, child_id, &info)) return;
817
818     do
819     {
820         if (info.proc)
821         {
822             TRACE( "calling WH_WINEVENT hook %p event %lx hwnd %p %lx %lx module %s\n",
823                    info.proc, event, hwnd, object_id, child_id, debugstr_w(info.module) );
824
825             if (!info.module[0] || (info.proc = get_hook_proc( info.proc, info.module )) != NULL)
826             {
827                 if (TRACE_ON(relay))
828                     DPRINTF( "%04lx:Call winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
829                              GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
830                              child_id, GetCurrentThreadId(), GetCurrentTime());
831
832                 info.proc(info.handle, event, hwnd, object_id, child_id,
833                           GetCurrentThreadId(), GetCurrentTime());
834
835                 if (TRACE_ON(relay))
836                     DPRINTF( "%04lx:Ret  winevent hook proc %p (hhook=%p,event=%lx,hwnd=%p,object_id=%lx,child_id=%lx,tid=%04lx,time=%lx)\n",
837                              GetCurrentThreadId(), info.proc, info.handle, event, hwnd, object_id,
838                              child_id, GetCurrentThreadId(), GetCurrentTime());
839             }
840         }
841         else
842             break;
843     }
844     while (find_next_hook(event, hwnd, object_id, child_id, &info));
845
846     find_hook_close(WH_WINEVENT);
847 }
848
849
850 /***********************************************************************
851  *           IsWinEventHookInstalled                       [USER32.@]
852  *
853  * Determine if an event hook is installed for an event.
854  *
855  * PARAMS
856  *  dwEvent  [I] Id of the event
857  *
858  * RETURNS
859  *  TRUE,  If there are any hooks installed for the event.
860  *  FALSE, Otherwise.
861  *
862  * BUGS
863  *  Not implemented.
864  */
865 BOOL WINAPI IsWinEventHookInstalled(DWORD dwEvent)
866 {
867     FIXME("(%ld)-stub!\n", dwEvent);
868     return TRUE;
869 }