2 * Windows hook functions
4 * Copyright 2002 Alexandre Julliard
5 * Copyright 2005 Dmitry Timoshkov
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.
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.
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
27 #include "win32u_private.h"
28 #include "ntuser_private.h"
29 #include "wine/server.h"
30 #include "wine/debug.h"
32 WINE_DEFAULT_DEBUG_CHANNEL(hook
);
34 #define WH_WINEVENT (WH_MAXHOOK+1)
36 static const char * const hook_names
[WH_WINEVENT
- WH_MINHOOK
+ 1] =
57 static const char *debugstr_hook_id( unsigned int id
)
59 if (id
- WH_MINHOOK
>= ARRAYSIZE(hook_names
)) return wine_dbg_sprintf( "%u", id
);
60 return hook_names
[id
- WH_MINHOOK
];
63 BOOL
is_hooked( INT id
)
65 struct user_thread_info
*thread_info
= get_user_thread_info();
67 if (!thread_info
->active_hooks
) return TRUE
;
68 return (thread_info
->active_hooks
& (1 << (id
- WH_MINHOOK
))) != 0;
71 /***********************************************************************
72 * NtUserSetWindowsHookEx (win32u.@)
74 HHOOK WINAPI
NtUserSetWindowsHookEx( HINSTANCE inst
, UNICODE_STRING
*module
, DWORD tid
, INT id
,
75 HOOKPROC proc
, BOOL ansi
)
81 RtlSetLastWin32Error( ERROR_INVALID_FILTER_PROC
);
85 if (tid
) /* thread-local hook */
87 if (id
== WH_JOURNALRECORD
||
88 id
== WH_JOURNALPLAYBACK
||
89 id
== WH_KEYBOARD_LL
||
91 id
== WH_SYSMSGFILTER
)
93 /* these can only be global */
94 RtlSetLastWin32Error( ERROR_INVALID_PARAMETER
);
98 else /* system-global hook */
100 if (id
== WH_KEYBOARD_LL
|| id
== WH_MOUSE_LL
) inst
= 0;
103 RtlSetLastWin32Error( ERROR_HOOK_NEEDS_HMOD
);
108 SERVER_START_REQ( set_hook
)
113 req
->event_min
= EVENT_MIN
;
114 req
->event_max
= EVENT_MAX
;
115 req
->flags
= WINEVENT_INCONTEXT
;
116 req
->unicode
= !ansi
;
117 if (inst
) /* make proc relative to the module base */
119 req
->proc
= wine_server_client_ptr( (void *)((char *)proc
- (char *)inst
) );
120 wine_server_add_data( req
, module
->Buffer
, module
->Length
);
122 else req
->proc
= wine_server_client_ptr( proc
);
124 if (!wine_server_call_err( req
))
126 handle
= wine_server_ptr_handle( reply
->handle
);
127 get_user_thread_info()->active_hooks
= reply
->active_hooks
;
132 TRACE( "%s %p %x -> %p\n", debugstr_hook_id(id
), proc
, tid
, handle
);
136 /***********************************************************************
137 * NtUserUnhookWindowsHookEx (win32u.@)
139 BOOL WINAPI
NtUserUnhookWindowsHookEx( HHOOK handle
)
143 SERVER_START_REQ( remove_hook
)
145 req
->handle
= wine_server_user_handle( handle
);
147 status
= wine_server_call_err( req
);
148 if (!status
) get_user_thread_info()->active_hooks
= reply
->active_hooks
;
151 if (status
== STATUS_INVALID_HANDLE
) RtlSetLastWin32Error( ERROR_INVALID_HOOK_HANDLE
);
155 /* see UnhookWindowsHook */
156 BOOL
unhook_windows_hook( INT id
, HOOKPROC proc
)
160 TRACE( "%s %p\n", debugstr_hook_id(id
), proc
);
162 SERVER_START_REQ( remove_hook
)
166 req
->proc
= wine_server_client_ptr( proc
);
167 status
= wine_server_call_err( req
);
168 if (!status
) get_user_thread_info()->active_hooks
= reply
->active_hooks
;
171 if (status
== STATUS_INVALID_HANDLE
) RtlSetLastWin32Error( ERROR_INVALID_HOOK_HANDLE
);
175 /***********************************************************************
176 * NtUserCallMsgFilter (win32u.@)
178 BOOL WINAPI
NtUserCallMsgFilter( MSG
*msg
, INT code
)
180 /* FIXME: We should use NtCallbackReturn instead of passing (potentially kernel) pointer
181 * like that, but we need to consequently use syscall thunks first for that to work. */
182 if (call_hooks( WH_SYSMSGFILTER
, code
, 0, (LPARAM
)msg
, sizeof(*msg
) )) return TRUE
;
183 return call_hooks( WH_MSGFILTER
, code
, 0, (LPARAM
)msg
, sizeof(*msg
) );
186 static UINT
get_ll_hook_timeout(void)
188 /* FIXME: should retrieve LowLevelHooksTimeout in HKEY_CURRENT_USER\Control Panel\Desktop */
192 /***********************************************************************
195 * Call hook either in current thread or send message to the destination
198 static LRESULT
call_hook( struct win_hook_params
*info
, const WCHAR
*module
)
204 struct hook_extra_info h_extra
;
205 h_extra
.handle
= info
->handle
;
206 h_extra
.lparam
= info
->lparam
;
208 TRACE( "calling hook in thread %04x %s code %x wp %lx lp %lx\n",
209 info
->tid
, hook_names
[info
->id
-WH_MINHOOK
], info
->code
, info
->wparam
, info
->lparam
);
214 send_internal_message_timeout( info
->pid
, info
->tid
, WM_WINE_KEYBOARD_LL_HOOK
,
215 info
->wparam
, (LPARAM
)&h_extra
, SMTO_ABORTIFHUNG
,
216 get_ll_hook_timeout(), &ret
);
219 send_internal_message_timeout( info
->pid
, info
->tid
, WM_WINE_MOUSE_LL_HOOK
,
220 info
->wparam
, (LPARAM
)&h_extra
, SMTO_ABORTIFHUNG
,
221 get_ll_hook_timeout(), &ret
);
224 ERR("Unknown hook id %d\n", info
->id
);
231 struct user_thread_info
*thread_info
= get_user_thread_info();
232 HHOOK prev
= thread_info
->hook
;
233 BOOL prev_unicode
= thread_info
->hook_unicode
;
234 struct win_hook_params
*params
= info
;
235 ULONG size
= sizeof(*params
);
236 ULONG lparam_ret_size
= params
->lparam_size
;
237 CREATESTRUCTW
*cs
= NULL
;
241 if (params
->lparam_size
)
243 lparam_ret_size
= params
->lparam_size
;
245 if (params
->id
== WH_CBT
&& params
->code
== HCBT_CREATEWND
)
247 cs
= ((CBT_CREATEWNDW
*)params
->lparam
)->lpcs
;
250 params
->lparam_size
= sizeof(*cs
);
251 if (!IS_INTRESOURCE( cs
->lpszName
))
252 params
->lparam_size
+= (wcslen( cs
->lpszName
) + 1) * sizeof(WCHAR
);
253 if (!IS_INTRESOURCE( cs
->lpszClass
))
254 params
->lparam_size
+= (wcslen( cs
->lpszClass
) + 1) * sizeof(WCHAR
);
257 size
+= params
->lparam_size
;
260 if (module
&& module
[0]) size
+= (lstrlenW( module
) + 1) * sizeof(WCHAR
);
261 if (size
!= sizeof(*params
))
263 if (!(params
= malloc( size
))) return 0;
267 if (params
->lparam_size
)
271 CREATESTRUCTW
*params_cs
= (CREATESTRUCTW
*)(params
+ 1);
272 WCHAR
*ptr
= (WCHAR
*)(params_cs
+ 1);
273 const void *inline_ptr
= (void *)0xffffffff;
276 if (!IS_INTRESOURCE( cs
->lpszName
))
278 UINT len
= wcslen( cs
->lpszName
) + 1;
279 memcpy( ptr
, cs
->lpszName
, len
* sizeof(WCHAR
) );
281 params_cs
->lpszName
= inline_ptr
;
283 if (!IS_INTRESOURCE( cs
->lpszClass
))
285 wcscpy( ptr
, cs
->lpszClass
);
286 params_cs
->lpszClass
= inline_ptr
;
291 memcpy( params
+ 1, (const void *)params
->lparam
, params
->lparam_size
);
295 if (module
&& module
[0])
296 wcscpy( (WCHAR
*)((char *)(params
+ 1) + params
->lparam_size
), module
);
299 * Windows protects from stack overflow in recursive hook calls. Different Windows
300 * allow different depths.
302 if (thread_info
->hook_call_depth
>= 25)
304 WARN("Too many hooks called recursively, skipping call.\n");
305 if (params
!= info
) free( params
);
309 TRACE( "calling hook %p %s code %x wp %lx lp %lx module %s\n",
310 params
->proc
, hook_names
[params
->id
-WH_MINHOOK
], params
->code
, params
->wparam
,
311 params
->lparam
, debugstr_w(module
) );
313 thread_info
->hook
= params
->handle
;
314 thread_info
->hook_unicode
= params
->next_unicode
;
315 thread_info
->hook_call_depth
++;
316 ret
= KeUserModeCallback( NtUserCallWindowsHook
, params
, size
, &ret_ptr
, &ret_len
);
317 if (ret_len
&& ret_len
== lparam_ret_size
)
318 memcpy( (void *)params
->lparam
, ret_ptr
, lparam_ret_size
);
319 thread_info
->hook
= prev
;
320 thread_info
->hook_unicode
= prev_unicode
;
321 thread_info
->hook_call_depth
--;
323 if (params
!= info
) free( params
);
326 if (info
->id
== WH_KEYBOARD_LL
|| info
->id
== WH_MOUSE_LL
)
327 InterlockedIncrement( &global_key_state_counter
); /* force refreshing the key state cache */
331 /***********************************************************************
332 * NtUserCallNextHookEx (win32u.@)
334 LRESULT WINAPI
NtUserCallNextHookEx( HHOOK hhook
, INT code
, WPARAM wparam
, LPARAM lparam
)
336 struct user_thread_info
*thread_info
= get_user_thread_info();
337 struct win_hook_params info
;
338 WCHAR module
[MAX_PATH
];
340 memset( &info
, 0, sizeof(info
) );
342 SERVER_START_REQ( get_hook_info
)
344 req
->handle
= wine_server_user_handle( thread_info
->hook
);
346 req
->event
= EVENT_MIN
;
347 wine_server_set_reply( req
, module
, sizeof(module
) - sizeof(WCHAR
) );
348 if (!wine_server_call_err( req
))
350 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
351 info
.handle
= wine_server_ptr_handle( reply
->handle
);
353 info
.pid
= reply
->pid
;
354 info
.tid
= reply
->tid
;
355 info
.proc
= wine_server_get_ptr( reply
->proc
);
356 info
.next_unicode
= reply
->unicode
;
362 info
.wparam
= wparam
;
363 info
.lparam
= lparam
;
364 info
.prev_unicode
= thread_info
->hook_unicode
;
365 return call_hook( &info
, module
);
368 LRESULT
call_current_hook( HHOOK hhook
, INT code
, WPARAM wparam
, LPARAM lparam
)
370 struct win_hook_params info
;
371 WCHAR module
[MAX_PATH
];
373 memset( &info
, 0, sizeof(info
) );
375 SERVER_START_REQ( get_hook_info
)
377 req
->handle
= wine_server_user_handle( hhook
);
379 req
->event
= EVENT_MIN
;
380 wine_server_set_reply( req
, module
, sizeof(module
) );
381 if (!wine_server_call_err( req
))
383 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
384 info
.handle
= wine_server_ptr_handle( reply
->handle
);
386 info
.pid
= reply
->pid
;
387 info
.tid
= reply
->tid
;
388 info
.proc
= wine_server_get_ptr( reply
->proc
);
389 info
.next_unicode
= reply
->unicode
;
395 info
.wparam
= wparam
;
396 info
.lparam
= lparam
;
397 info
.prev_unicode
= TRUE
; /* assume Unicode for this function */
398 return call_hook( &info
, module
);
401 LRESULT
call_hooks( INT id
, INT code
, WPARAM wparam
, LPARAM lparam
, size_t lparam_size
)
403 struct user_thread_info
*thread_info
= get_user_thread_info();
404 struct win_hook_params info
;
405 WCHAR module
[MAX_PATH
];
408 user_check_not_lock();
410 if (!is_hooked( id
))
412 TRACE( "skipping hook %s mask %x\n", hook_names
[id
-WH_MINHOOK
], thread_info
->active_hooks
);
416 memset( &info
, 0, sizeof(info
) );
417 info
.prev_unicode
= TRUE
;
420 SERVER_START_REQ( start_hook_chain
)
423 req
->event
= EVENT_MIN
;
424 wine_server_set_reply( req
, module
, sizeof(module
)-sizeof(WCHAR
) );
425 if (!wine_server_call( req
))
427 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
428 info
.handle
= wine_server_ptr_handle( reply
->handle
);
429 info
.pid
= reply
->pid
;
430 info
.tid
= reply
->tid
;
431 info
.proc
= wine_server_get_ptr( reply
->proc
);
432 info
.next_unicode
= reply
->unicode
;
433 thread_info
->active_hooks
= reply
->active_hooks
;
437 if (!info
.tid
&& !info
.proc
) return 0;
440 info
.wparam
= wparam
;
441 info
.lparam
= lparam
;
442 info
.lparam_size
= lparam_size
;
443 ret
= call_hook( &info
, module
);
445 SERVER_START_REQ( finish_hook_chain
)
448 wine_server_call( req
);
454 /***********************************************************************
455 * NtUserSetWinEventHook (win32u.@)
457 HWINEVENTHOOK WINAPI
NtUserSetWinEventHook( DWORD event_min
, DWORD event_max
, HMODULE inst
,
458 UNICODE_STRING
*module
, WINEVENTPROC proc
,
459 DWORD pid
, DWORD tid
, DWORD flags
)
461 HWINEVENTHOOK handle
= 0;
463 if ((flags
& WINEVENT_INCONTEXT
) && !inst
)
465 RtlSetLastWin32Error(ERROR_HOOK_NEEDS_HMOD
);
469 if (event_min
> event_max
)
471 RtlSetLastWin32Error(ERROR_INVALID_HOOK_FILTER
);
475 /* FIXME: what if the tid or pid belongs to another process? */
476 if (tid
) inst
= 0; /* thread-local hook */
478 SERVER_START_REQ( set_hook
)
480 req
->id
= WH_WINEVENT
;
483 req
->event_min
= event_min
;
484 req
->event_max
= event_max
;
487 if (inst
) /* make proc relative to the module base */
489 req
->proc
= wine_server_client_ptr( (void *)((char *)proc
- (char *)inst
) );
490 wine_server_add_data( req
, module
->Buffer
, module
->Length
);
492 else req
->proc
= wine_server_client_ptr( proc
);
494 if (!wine_server_call_err( req
))
496 handle
= wine_server_ptr_handle( reply
->handle
);
497 get_user_thread_info()->active_hooks
= reply
->active_hooks
;
502 TRACE("-> %p\n", handle
);
506 /***********************************************************************
507 * NtUserUnhookWinEvent (win32u.@)
509 BOOL WINAPI
NtUserUnhookWinEvent( HWINEVENTHOOK handle
)
513 SERVER_START_REQ( remove_hook
)
515 req
->handle
= wine_server_user_handle( handle
);
516 req
->id
= WH_WINEVENT
;
517 ret
= !wine_server_call_err( req
);
518 if (ret
) get_user_thread_info()->active_hooks
= reply
->active_hooks
;
524 /***********************************************************************
525 * NtUserNotifyWinEvent (win32u.@)
527 void WINAPI
NtUserNotifyWinEvent( DWORD event
, HWND hwnd
, LONG object_id
, LONG child_id
)
529 struct user_thread_info
*thread_info
= get_user_thread_info();
530 struct win_event_hook_params info
;
535 TRACE( "%04x, %p, %d, %d\n", event
, hwnd
, object_id
, child_id
);
537 user_check_not_lock();
541 RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE
);
545 if (!is_hooked( WH_WINEVENT
))
547 TRACE( "skipping hook mask %x\n", thread_info
->active_hooks
);
553 info
.object_id
= object_id
;
554 info
.child_id
= child_id
;
555 info
.tid
= GetCurrentThreadId();
557 SERVER_START_REQ( start_hook_chain
)
559 req
->id
= WH_WINEVENT
;
561 req
->window
= wine_server_user_handle( hwnd
);
562 req
->object_id
= object_id
;
563 req
->child_id
= child_id
;
564 wine_server_set_reply( req
, info
.module
, sizeof(info
.module
) - sizeof(WCHAR
) );
565 ret
= !wine_server_call( req
) && reply
->proc
;
568 info
.module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
569 info
.handle
= wine_server_ptr_handle( reply
->handle
);
570 info
.proc
= wine_server_get_ptr( reply
->proc
);
571 thread_info
->active_hooks
= reply
->active_hooks
;
579 TRACE( "calling WH_WINEVENT hook %p event %x hwnd %p %x %x module %s\n",
580 info
.proc
, event
, hwnd
, object_id
, child_id
, debugstr_w(info
.module
) );
582 info
.time
= NtGetTickCount();
583 KeUserModeCallback( NtUserCallWinEventHook
, &info
,
584 FIELD_OFFSET( struct win_event_hook_params
, module
[lstrlenW(info
.module
) + 1] ),
585 &ret_ptr
, &ret_len
);
587 SERVER_START_REQ( get_hook_info
)
589 req
->handle
= wine_server_user_handle( info
.handle
);
592 req
->window
= wine_server_user_handle( hwnd
);
593 req
->object_id
= object_id
;
594 req
->child_id
= child_id
;
595 wine_server_set_reply( req
, info
.module
, sizeof(info
.module
) - sizeof(WCHAR
) );
596 ret
= !wine_server_call( req
) && reply
->proc
;
599 info
.module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
600 info
.handle
= wine_server_ptr_handle( reply
->handle
);
601 info
.proc
= wine_server_get_ptr( reply
->proc
);
608 SERVER_START_REQ( finish_hook_chain
)
610 req
->id
= WH_WINEVENT
;
611 wine_server_call( req
);