/* * Windows hook functions * * Copyright 2002 Alexandre Julliard * Copyright 2005 Dmitry Timoshkov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #if 0 #pragma makedep unix #endif #include #include "win32u_private.h" #include "ntuser_private.h" #include "wine/server.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(hook); #define WH_WINEVENT (WH_MAXHOOK+1) static const char * const hook_names[WH_WINEVENT - WH_MINHOOK + 1] = { "WH_MSGFILTER", "WH_JOURNALRECORD", "WH_JOURNALPLAYBACK", "WH_KEYBOARD", "WH_GETMESSAGE", "WH_CALLWNDPROC", "WH_CBT", "WH_SYSMSGFILTER", "WH_MOUSE", "WH_HARDWARE", "WH_DEBUG", "WH_SHELL", "WH_FOREGROUNDIDLE", "WH_CALLWNDPROCRET", "WH_KEYBOARD_LL", "WH_MOUSE_LL", "WH_WINEVENT" }; static const char *debugstr_hook_id( unsigned int id ) { if (id - WH_MINHOOK >= ARRAYSIZE(hook_names)) return wine_dbg_sprintf( "%u", id ); return hook_names[id - WH_MINHOOK]; } static BOOL is_hooked( INT id ) { struct user_thread_info *thread_info = get_user_thread_info(); if (!thread_info->active_hooks) return TRUE; return (thread_info->active_hooks & (1 << (id - WH_MINHOOK))) != 0; } /*********************************************************************** * NtUserSetWindowsHookEx (win32u.@) */ HHOOK WINAPI NtUserSetWindowsHookEx( HINSTANCE inst, UNICODE_STRING *module, DWORD tid, INT id, HOOKPROC proc, BOOL ansi ) { HHOOK handle = 0; if (!proc) { SetLastError( ERROR_INVALID_FILTER_PROC ); return 0; } if (tid) /* thread-local hook */ { if (id == WH_JOURNALRECORD || id == WH_JOURNALPLAYBACK || id == WH_KEYBOARD_LL || id == WH_MOUSE_LL || id == WH_SYSMSGFILTER) { /* these can only be global */ SetLastError( ERROR_INVALID_PARAMETER ); return 0; } } else /* system-global hook */ { if (id == WH_KEYBOARD_LL || id == WH_MOUSE_LL) inst = 0; else if (!inst) { SetLastError( ERROR_HOOK_NEEDS_HMOD ); return 0; } } SERVER_START_REQ( set_hook ) { req->id = id; req->pid = 0; req->tid = tid; req->event_min = EVENT_MIN; req->event_max = EVENT_MAX; req->flags = WINEVENT_INCONTEXT; req->unicode = !ansi; if (inst) /* make proc relative to the module base */ { req->proc = wine_server_client_ptr( (void *)((char *)proc - (char *)inst) ); wine_server_add_data( req, module->Buffer, module->Length ); } else req->proc = wine_server_client_ptr( proc ); if (!wine_server_call_err( req )) { handle = wine_server_ptr_handle( reply->handle ); get_user_thread_info()->active_hooks = reply->active_hooks; } } SERVER_END_REQ; TRACE( "%s %p %x -> %p\n", debugstr_hook_id(id), proc, tid, handle ); return handle; } /*********************************************************************** * NtUserUnhookWindowsHookEx (win32u.@) */ BOOL WINAPI NtUserUnhookWindowsHookEx( HHOOK handle ) { NTSTATUS status; SERVER_START_REQ( remove_hook ) { req->handle = wine_server_user_handle( handle ); req->id = 0; status = wine_server_call_err( req ); if (!status) get_user_thread_info()->active_hooks = reply->active_hooks; } SERVER_END_REQ; if (status == STATUS_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE ); return !status; } /* see UnhookWindowsHook */ BOOL unhook_windows_hook( INT id, HOOKPROC proc ) { NTSTATUS status; TRACE( "%s %p\n", debugstr_hook_id(id), proc ); SERVER_START_REQ( remove_hook ) { req->handle = 0; req->id = id; req->proc = wine_server_client_ptr( proc ); status = wine_server_call_err( req ); if (!status) get_user_thread_info()->active_hooks = reply->active_hooks; } SERVER_END_REQ; if (status == STATUS_INVALID_HANDLE) SetLastError( ERROR_INVALID_HOOK_HANDLE ); return !status; } static UINT get_ll_hook_timeout(void) { /* FIXME: should retrieve LowLevelHooksTimeout in HKEY_CURRENT_USER\Control Panel\Desktop */ return 2000; } /*********************************************************************** * call_hook * * Call hook either in current thread or send message to the destination * thread. */ static LRESULT call_hook( struct win_hook_params *info ) { DWORD_PTR ret = 0; if (info->tid) { struct hook_extra_info h_extra; h_extra.handle = info->handle; h_extra.lparam = info->lparam; TRACE( "calling hook in thread %04x %s code %x wp %lx lp %lx\n", info->tid, hook_names[info->id-WH_MINHOOK], info->code, info->wparam, info->lparam ); switch(info->id) { case WH_KEYBOARD_LL: send_internal_message_timeout( info->pid, info->tid, WM_WINE_KEYBOARD_LL_HOOK, info->wparam, (LPARAM)&h_extra, SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret ); break; case WH_MOUSE_LL: send_internal_message_timeout( info->pid, info->tid, WM_WINE_MOUSE_LL_HOOK, info->wparam, (LPARAM)&h_extra, SMTO_ABORTIFHUNG, get_ll_hook_timeout(), &ret ); break; default: ERR("Unknown hook id %d\n", info->id); assert(0); break; } } else if (info->proc) { struct user_thread_info *thread_info = get_user_thread_info(); HHOOK prev = thread_info->hook; BOOL prev_unicode = thread_info->hook_unicode; size_t len = lstrlenW( info->module ); void *ret_ptr; ULONG ret_len; /* * Windows protects from stack overflow in recursive hook calls. Different Windows * allow different depths. */ if (thread_info->hook_call_depth >= 25) { WARN("Too many hooks called recursively, skipping call.\n"); return 0; } TRACE( "calling hook %p %s code %x wp %lx lp %lx module %s\n", info->proc, hook_names[info->id-WH_MINHOOK], info->code, info->wparam, info->lparam, debugstr_w(info->module) ); thread_info->hook = info->handle; thread_info->hook_unicode = info->next_unicode; thread_info->hook_call_depth++; ret = KeUserModeCallback( NtUserCallWindowsHook, info, FIELD_OFFSET( struct win_hook_params, module[len + 1] ), &ret_ptr, &ret_len ); thread_info->hook = prev; thread_info->hook_unicode = prev_unicode; thread_info->hook_call_depth--; } if (info->id == WH_KEYBOARD_LL || info->id == WH_MOUSE_LL) InterlockedIncrement( &global_key_state_counter ); /* force refreshing the key state cache */ return ret; } /*********************************************************************** * NtUserCallNextHookEx (win32u.@) */ LRESULT WINAPI NtUserCallNextHookEx( HHOOK hhook, INT code, WPARAM wparam, LPARAM lparam ) { struct user_thread_info *thread_info = get_user_thread_info(); struct win_hook_params info; memset( &info, 0, sizeof(info) - sizeof(info.module) ); SERVER_START_REQ( get_hook_info ) { req->handle = wine_server_user_handle( thread_info->hook ); req->get_next = 1; req->event = EVENT_MIN; wine_server_set_reply( req, info.module, sizeof(info.module)-sizeof(WCHAR) ); if (!wine_server_call_err( req )) { info.module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0; info.handle = wine_server_ptr_handle( reply->handle ); info.id = reply->id; info.pid = reply->pid; info.tid = reply->tid; info.proc = wine_server_get_ptr( reply->proc ); info.next_unicode = reply->unicode; } } SERVER_END_REQ; info.code = code; info.wparam = wparam; info.lparam = lparam; info.prev_unicode = thread_info->hook_unicode; return call_hook( &info ); } LRESULT call_current_hook( HHOOK hhook, INT code, WPARAM wparam, LPARAM lparam ) { struct win_hook_params info; memset( &info, 0, sizeof(info) - sizeof(info.module) ); SERVER_START_REQ( get_hook_info ) { req->handle = wine_server_user_handle( hhook ); req->get_next = 0; req->event = EVENT_MIN; wine_server_set_reply( req, info.module, sizeof(info.module)-sizeof(WCHAR) ); if (!wine_server_call_err( req )) { info.module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0; info.handle = wine_server_ptr_handle( reply->handle ); info.id = reply->id; info.pid = reply->pid; info.tid = reply->tid; info.proc = wine_server_get_ptr( reply->proc ); info.next_unicode = reply->unicode; } } SERVER_END_REQ; info.code = code; info.wparam = wparam; info.lparam = lparam; info.prev_unicode = TRUE; /* assume Unicode for this function */ return call_hook( &info ); } LRESULT call_hooks( INT id, INT code, WPARAM wparam, LPARAM lparam, BOOL unicode ) { struct user_thread_info *thread_info = get_user_thread_info(); struct win_hook_params info; DWORD_PTR ret; user_check_not_lock(); if (!is_hooked( id )) { TRACE( "skipping hook %s mask %x\n", hook_names[id-WH_MINHOOK], thread_info->active_hooks ); return 0; } memset( &info, 0, sizeof(info) - sizeof(info.module) ); info.prev_unicode = unicode; info.id = id; SERVER_START_REQ( start_hook_chain ) { req->id = info.id; req->event = EVENT_MIN; wine_server_set_reply( req, info.module, sizeof(info.module)-sizeof(WCHAR) ); if (!wine_server_call( req )) { info.module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0; info.handle = wine_server_ptr_handle( reply->handle ); info.pid = reply->pid; info.tid = reply->tid; info.proc = wine_server_get_ptr( reply->proc ); info.next_unicode = reply->unicode; thread_info->active_hooks = reply->active_hooks; } } SERVER_END_REQ; if (!info.tid && !info.proc) return 0; info.code = code; info.wparam = wparam; info.lparam = lparam; ret = call_hook( &info ); SERVER_START_REQ( finish_hook_chain ) { req->id = id; wine_server_call( req ); } SERVER_END_REQ; return ret; } /*********************************************************************** * NtUserSetWinEventHook (win32u.@) */ HWINEVENTHOOK WINAPI NtUserSetWinEventHook( DWORD event_min, DWORD event_max, HMODULE inst, UNICODE_STRING *module, WINEVENTPROC proc, DWORD pid, DWORD tid, DWORD flags ) { HWINEVENTHOOK handle = 0; if ((flags & WINEVENT_INCONTEXT) && !inst) { SetLastError(ERROR_HOOK_NEEDS_HMOD); return 0; } if (event_min > event_max) { SetLastError(ERROR_INVALID_HOOK_FILTER); return 0; } /* FIXME: what if the tid or pid belongs to another process? */ if (tid) inst = 0; /* thread-local hook */ SERVER_START_REQ( set_hook ) { req->id = WH_WINEVENT; req->pid = pid; req->tid = tid; req->event_min = event_min; req->event_max = event_max; req->flags = flags; req->unicode = 1; if (inst) /* make proc relative to the module base */ { req->proc = wine_server_client_ptr( (void *)((char *)proc - (char *)inst) ); wine_server_add_data( req, module->Buffer, module->Length ); } else req->proc = wine_server_client_ptr( proc ); if (!wine_server_call_err( req )) { handle = wine_server_ptr_handle( reply->handle ); get_user_thread_info()->active_hooks = reply->active_hooks; } } SERVER_END_REQ; TRACE("-> %p\n", handle); return handle; } /*********************************************************************** * NtUserUnhookWinEvent (win32u.@) */ BOOL WINAPI NtUserUnhookWinEvent( HWINEVENTHOOK handle ) { BOOL ret; SERVER_START_REQ( remove_hook ) { req->handle = wine_server_user_handle( handle ); req->id = WH_WINEVENT; ret = !wine_server_call_err( req ); if (ret) get_user_thread_info()->active_hooks = reply->active_hooks; } SERVER_END_REQ; return ret; } /*********************************************************************** * NtUserNotifyWinEvent (win32u.@) */ void WINAPI NtUserNotifyWinEvent( DWORD event, HWND hwnd, LONG object_id, LONG child_id ) { struct user_thread_info *thread_info = get_user_thread_info(); struct win_event_hook_params info; void *ret_ptr; ULONG ret_len; BOOL ret; TRACE( "%04x, %p, %d, %d\n", event, hwnd, object_id, child_id ); user_check_not_lock(); if (!hwnd) { SetLastError( ERROR_INVALID_WINDOW_HANDLE ); return; } if (!is_hooked( WH_WINEVENT )) { TRACE( "skipping hook mask %x\n", thread_info->active_hooks ); return; } info.event = event; info.hwnd = hwnd; info.object_id = object_id; info.child_id = child_id; info.tid = GetCurrentThreadId(); SERVER_START_REQ( start_hook_chain ) { req->id = WH_WINEVENT; req->event = event; req->window = wine_server_user_handle( hwnd ); req->object_id = object_id; req->child_id = child_id; wine_server_set_reply( req, info.module, sizeof(info.module) - sizeof(WCHAR) ); ret = !wine_server_call( req ) && reply->proc; if (ret) { info.module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0; info.handle = wine_server_ptr_handle( reply->handle ); info.proc = wine_server_get_ptr( reply->proc ); thread_info->active_hooks = reply->active_hooks; } } SERVER_END_REQ; if (!ret) return; do { TRACE( "calling WH_WINEVENT hook %p event %x hwnd %p %x %x module %s\n", info.proc, event, hwnd, object_id, child_id, debugstr_w(info.module) ); info.time = NtGetTickCount(); KeUserModeCallback( NtUserCallWinEventHook, &info, FIELD_OFFSET( struct win_hook_params, module[lstrlenW(info.module) + 1] ), &ret_ptr, &ret_len ); SERVER_START_REQ( get_hook_info ) { req->handle = wine_server_user_handle( info.handle ); req->get_next = 1; req->event = event; req->window = wine_server_user_handle( hwnd ); req->object_id = object_id; req->child_id = child_id; wine_server_set_reply( req, info.module, sizeof(info.module) - sizeof(WCHAR) ); ret = !wine_server_call( req ) && reply->proc; if (ret) { info.module[wine_server_reply_size(req) / sizeof(WCHAR)] = 0; info.handle = wine_server_ptr_handle( reply->handle ); info.proc = wine_server_get_ptr( reply->proc ); } } SERVER_END_REQ; } while (ret); SERVER_START_REQ( finish_hook_chain ) { req->id = WH_WINEVENT; wine_server_call( req ); } SERVER_END_REQ; }