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
, (int)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 (int)info
->tid
, hook_names
[info
->id
-WH_MINHOOK
],
210 info
->code
, (long)info
->wparam
, info
->lparam
);
215 send_internal_message_timeout( info
->pid
, info
->tid
, WM_WINE_KEYBOARD_LL_HOOK
,
216 info
->wparam
, (LPARAM
)&h_extra
, SMTO_ABORTIFHUNG
,
217 get_ll_hook_timeout(), &ret
);
220 send_internal_message_timeout( info
->pid
, info
->tid
, WM_WINE_MOUSE_LL_HOOK
,
221 info
->wparam
, (LPARAM
)&h_extra
, SMTO_ABORTIFHUNG
,
222 get_ll_hook_timeout(), &ret
);
225 ERR("Unknown hook id %d\n", info
->id
);
232 struct user_thread_info
*thread_info
= get_user_thread_info();
233 HHOOK prev
= thread_info
->hook
;
234 BOOL prev_unicode
= thread_info
->hook_unicode
;
235 struct win_hook_params
*params
= info
;
236 ULONG size
= sizeof(*params
);
237 ULONG lparam_ret_size
= params
->lparam_size
;
238 CREATESTRUCTW
*cs
= NULL
;
242 if (params
->lparam_size
)
244 lparam_ret_size
= params
->lparam_size
;
246 if (params
->id
== WH_CBT
&& params
->code
== HCBT_CREATEWND
)
248 cs
= ((CBT_CREATEWNDW
*)params
->lparam
)->lpcs
;
251 params
->lparam_size
= sizeof(*cs
);
252 if (!IS_INTRESOURCE( cs
->lpszName
))
253 params
->lparam_size
+= (wcslen( cs
->lpszName
) + 1) * sizeof(WCHAR
);
254 if (!IS_INTRESOURCE( cs
->lpszClass
))
255 params
->lparam_size
+= (wcslen( cs
->lpszClass
) + 1) * sizeof(WCHAR
);
258 size
+= params
->lparam_size
;
261 if (module
&& module
[0]) size
+= (lstrlenW( module
) + 1) * sizeof(WCHAR
);
262 if (size
!= sizeof(*params
))
264 if (!(params
= malloc( size
))) return 0;
268 if (params
->lparam_size
)
272 CREATESTRUCTW
*params_cs
= (CREATESTRUCTW
*)(params
+ 1);
273 WCHAR
*ptr
= (WCHAR
*)(params_cs
+ 1);
274 const void *inline_ptr
= (void *)0xffffffff;
277 if (!IS_INTRESOURCE( cs
->lpszName
))
279 UINT len
= wcslen( cs
->lpszName
) + 1;
280 memcpy( ptr
, cs
->lpszName
, len
* sizeof(WCHAR
) );
282 params_cs
->lpszName
= inline_ptr
;
284 if (!IS_INTRESOURCE( cs
->lpszClass
))
286 wcscpy( ptr
, cs
->lpszClass
);
287 params_cs
->lpszClass
= inline_ptr
;
292 memcpy( params
+ 1, (const void *)params
->lparam
, params
->lparam_size
);
296 if (module
&& module
[0])
297 wcscpy( (WCHAR
*)((char *)(params
+ 1) + params
->lparam_size
), module
);
300 * Windows protects from stack overflow in recursive hook calls. Different Windows
301 * allow different depths.
303 if (thread_info
->hook_call_depth
>= 25)
305 WARN("Too many hooks called recursively, skipping call.\n");
306 if (params
!= info
) free( params
);
310 TRACE( "calling hook %p %s code %x wp %lx lp %lx module %s\n",
311 params
->proc
, hook_names
[params
->id
-WH_MINHOOK
], params
->code
, (long)params
->wparam
,
312 params
->lparam
, debugstr_w(module
) );
314 thread_info
->hook
= params
->handle
;
315 thread_info
->hook_unicode
= params
->next_unicode
;
316 thread_info
->hook_call_depth
++;
317 ret
= KeUserModeCallback( NtUserCallWindowsHook
, params
, size
, &ret_ptr
, &ret_len
);
318 if (ret_len
&& ret_len
== lparam_ret_size
)
319 memcpy( (void *)params
->lparam
, ret_ptr
, lparam_ret_size
);
320 thread_info
->hook
= prev
;
321 thread_info
->hook_unicode
= prev_unicode
;
322 thread_info
->hook_call_depth
--;
324 if (params
!= info
) free( params
);
327 if (info
->id
== WH_KEYBOARD_LL
|| info
->id
== WH_MOUSE_LL
)
328 InterlockedIncrement( &global_key_state_counter
); /* force refreshing the key state cache */
332 /***********************************************************************
333 * NtUserCallNextHookEx (win32u.@)
335 LRESULT WINAPI
NtUserCallNextHookEx( HHOOK hhook
, INT code
, WPARAM wparam
, LPARAM lparam
)
337 struct user_thread_info
*thread_info
= get_user_thread_info();
338 struct win_hook_params info
;
339 WCHAR module
[MAX_PATH
];
341 memset( &info
, 0, sizeof(info
) );
343 SERVER_START_REQ( get_hook_info
)
345 req
->handle
= wine_server_user_handle( thread_info
->hook
);
347 req
->event
= EVENT_MIN
;
348 wine_server_set_reply( req
, module
, sizeof(module
) - sizeof(WCHAR
) );
349 if (!wine_server_call_err( req
))
351 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
352 info
.handle
= wine_server_ptr_handle( reply
->handle
);
354 info
.pid
= reply
->pid
;
355 info
.tid
= reply
->tid
;
356 info
.proc
= wine_server_get_ptr( reply
->proc
);
357 info
.next_unicode
= reply
->unicode
;
363 info
.wparam
= wparam
;
364 info
.lparam
= lparam
;
365 info
.prev_unicode
= thread_info
->hook_unicode
;
366 return call_hook( &info
, module
);
369 LRESULT
call_current_hook( HHOOK hhook
, INT code
, WPARAM wparam
, LPARAM lparam
)
371 struct win_hook_params info
;
372 WCHAR module
[MAX_PATH
];
374 memset( &info
, 0, sizeof(info
) );
376 SERVER_START_REQ( get_hook_info
)
378 req
->handle
= wine_server_user_handle( hhook
);
380 req
->event
= EVENT_MIN
;
381 wine_server_set_reply( req
, module
, sizeof(module
) );
382 if (!wine_server_call_err( req
))
384 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
385 info
.handle
= wine_server_ptr_handle( reply
->handle
);
387 info
.pid
= reply
->pid
;
388 info
.tid
= reply
->tid
;
389 info
.proc
= wine_server_get_ptr( reply
->proc
);
390 info
.next_unicode
= reply
->unicode
;
396 info
.wparam
= wparam
;
397 info
.lparam
= lparam
;
398 info
.prev_unicode
= TRUE
; /* assume Unicode for this function */
399 return call_hook( &info
, module
);
402 LRESULT
call_hooks( INT id
, INT code
, WPARAM wparam
, LPARAM lparam
, size_t lparam_size
)
404 struct user_thread_info
*thread_info
= get_user_thread_info();
405 struct win_hook_params info
;
406 WCHAR module
[MAX_PATH
];
409 user_check_not_lock();
411 if (!is_hooked( id
))
413 TRACE( "skipping hook %s mask %x\n", hook_names
[id
-WH_MINHOOK
], thread_info
->active_hooks
);
417 memset( &info
, 0, sizeof(info
) );
418 info
.prev_unicode
= TRUE
;
421 SERVER_START_REQ( start_hook_chain
)
424 req
->event
= EVENT_MIN
;
425 wine_server_set_reply( req
, module
, sizeof(module
)-sizeof(WCHAR
) );
426 if (!wine_server_call( req
))
428 module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
429 info
.handle
= wine_server_ptr_handle( reply
->handle
);
430 info
.pid
= reply
->pid
;
431 info
.tid
= reply
->tid
;
432 info
.proc
= wine_server_get_ptr( reply
->proc
);
433 info
.next_unicode
= reply
->unicode
;
434 thread_info
->active_hooks
= reply
->active_hooks
;
438 if (!info
.tid
&& !info
.proc
) return 0;
441 info
.wparam
= wparam
;
442 info
.lparam
= lparam
;
443 info
.lparam_size
= lparam_size
;
444 ret
= call_hook( &info
, module
);
446 SERVER_START_REQ( finish_hook_chain
)
449 wine_server_call( req
);
455 /***********************************************************************
456 * NtUserSetWinEventHook (win32u.@)
458 HWINEVENTHOOK WINAPI
NtUserSetWinEventHook( DWORD event_min
, DWORD event_max
, HMODULE inst
,
459 UNICODE_STRING
*module
, WINEVENTPROC proc
,
460 DWORD pid
, DWORD tid
, DWORD flags
)
462 HWINEVENTHOOK handle
= 0;
464 if ((flags
& WINEVENT_INCONTEXT
) && !inst
)
466 RtlSetLastWin32Error(ERROR_HOOK_NEEDS_HMOD
);
470 if (event_min
> event_max
)
472 RtlSetLastWin32Error(ERROR_INVALID_HOOK_FILTER
);
476 /* FIXME: what if the tid or pid belongs to another process? */
477 if (tid
) inst
= 0; /* thread-local hook */
479 SERVER_START_REQ( set_hook
)
481 req
->id
= WH_WINEVENT
;
484 req
->event_min
= event_min
;
485 req
->event_max
= event_max
;
488 if (inst
) /* make proc relative to the module base */
490 req
->proc
= wine_server_client_ptr( (void *)((char *)proc
- (char *)inst
) );
491 wine_server_add_data( req
, module
->Buffer
, module
->Length
);
493 else req
->proc
= wine_server_client_ptr( proc
);
495 if (!wine_server_call_err( req
))
497 handle
= wine_server_ptr_handle( reply
->handle
);
498 get_user_thread_info()->active_hooks
= reply
->active_hooks
;
503 TRACE("-> %p\n", handle
);
507 /***********************************************************************
508 * NtUserUnhookWinEvent (win32u.@)
510 BOOL WINAPI
NtUserUnhookWinEvent( HWINEVENTHOOK handle
)
514 SERVER_START_REQ( remove_hook
)
516 req
->handle
= wine_server_user_handle( handle
);
517 req
->id
= WH_WINEVENT
;
518 ret
= !wine_server_call_err( req
);
519 if (ret
) get_user_thread_info()->active_hooks
= reply
->active_hooks
;
525 /***********************************************************************
526 * NtUserNotifyWinEvent (win32u.@)
528 void WINAPI
NtUserNotifyWinEvent( DWORD event
, HWND hwnd
, LONG object_id
, LONG child_id
)
530 struct user_thread_info
*thread_info
= get_user_thread_info();
531 struct win_event_hook_params info
;
536 TRACE( "%04x, %p, %d, %d\n", (int)event
, hwnd
, (int)object_id
, (int)child_id
);
538 user_check_not_lock();
542 RtlSetLastWin32Error( ERROR_INVALID_WINDOW_HANDLE
);
546 if (!is_hooked( WH_WINEVENT
))
548 TRACE( "skipping hook mask %x\n", thread_info
->active_hooks
);
554 info
.object_id
= object_id
;
555 info
.child_id
= child_id
;
556 info
.tid
= GetCurrentThreadId();
558 SERVER_START_REQ( start_hook_chain
)
560 req
->id
= WH_WINEVENT
;
562 req
->window
= wine_server_user_handle( hwnd
);
563 req
->object_id
= object_id
;
564 req
->child_id
= child_id
;
565 wine_server_set_reply( req
, info
.module
, sizeof(info
.module
) - sizeof(WCHAR
) );
566 ret
= !wine_server_call( req
) && reply
->proc
;
569 info
.module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
570 info
.handle
= wine_server_ptr_handle( reply
->handle
);
571 info
.proc
= wine_server_get_ptr( reply
->proc
);
572 thread_info
->active_hooks
= reply
->active_hooks
;
580 TRACE( "calling WH_WINEVENT hook %p event %x hwnd %p %x %x module %s\n",
581 info
.proc
, (int)event
, hwnd
, (int)object_id
, (int)child_id
, debugstr_w(info
.module
) );
583 info
.time
= NtGetTickCount();
584 KeUserModeCallback( NtUserCallWinEventHook
, &info
,
585 FIELD_OFFSET( struct win_event_hook_params
, module
[lstrlenW(info
.module
) + 1] ),
586 &ret_ptr
, &ret_len
);
588 SERVER_START_REQ( get_hook_info
)
590 req
->handle
= wine_server_user_handle( info
.handle
);
593 req
->window
= wine_server_user_handle( hwnd
);
594 req
->object_id
= object_id
;
595 req
->child_id
= child_id
;
596 wine_server_set_reply( req
, info
.module
, sizeof(info
.module
) - sizeof(WCHAR
) );
597 ret
= !wine_server_call( req
) && reply
->proc
;
600 info
.module
[wine_server_reply_size(req
) / sizeof(WCHAR
)] = 0;
601 info
.handle
= wine_server_ptr_handle( reply
->handle
);
602 info
.proc
= wine_server_get_ptr( reply
->proc
);
609 SERVER_START_REQ( finish_hook_chain
)
611 req
->id
= WH_WINEVENT
;
612 wine_server_call( req
);