shell32: Pass an object instead of an iface to a helper function.
[wine] / server / hook.c
index 3a483c1..3abd8d9 100644 (file)
@@ -2,6 +2,7 @@
  * Server-side window hooks support
  *
  * Copyright (C) 2002 Alexandre Julliard
+ * Copyright (C) 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
  *
  * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
 #include "config.h"
 #include "wine/port.h"
 
 #include <assert.h>
+#include <stdarg.h>
 #include <stdio.h>
 
+#include "ntstatus.h"
+#define WIN32_NO_STATUS
+#include "windef.h"
 #include "winbase.h"
 #include "winuser.h"
+#include "winternl.h"
 
 #include "object.h"
+#include "process.h"
 #include "request.h"
 #include "user.h"
 
@@ -37,15 +44,23 @@ struct hook
 {
     struct list         chain;    /* hook chain entry */
     user_handle_t       handle;   /* user handle for this hook */
-    struct thread      *thread;   /* thread owning the hook */
+    struct process     *process;  /* process the hook is set to */
+    struct thread      *thread;   /* thread the hook is set to */
+    struct thread      *owner;    /* owner of the out of context hook */
+    struct hook_table  *table;    /* hook table that contains this hook */
     int                 index;    /* hook table index */
-    void               *proc;     /* hook function */
+    int                 event_min;
+    int                 event_max;
+    int                 flags;
+    client_ptr_t        proc;     /* hook function */
     int                 unicode;  /* is it a unicode hook? */
     WCHAR              *module;   /* module name for global hooks */
-    size_t              module_size;
+    data_size_t         module_size;
 };
 
-#define NB_HOOKS (WH_MAXHOOK-WH_MINHOOK+1)
+#define WH_WINEVENT (WH_MAXHOOK+1)
+
+#define NB_HOOKS (WH_WINEVENT-WH_MINHOOK+1)
 #define HOOK_ENTRY(p)  LIST_ENTRY( (p), struct hook, chain )
 
 struct hook_table
@@ -62,29 +77,30 @@ static const struct object_ops hook_table_ops =
 {
     sizeof(struct hook_table),    /* size */
     hook_table_dump,              /* dump */
+    no_get_type,                  /* get_type */
     no_add_queue,                 /* add_queue */
     NULL,                         /* remove_queue */
     NULL,                         /* signaled */
     NULL,                         /* satisfied */
-    NULL,                         /* get_poll_events */
-    NULL,                         /* poll_event */
+    no_signal,                    /* signal */
     no_get_fd,                    /* get_fd */
-    no_flush,                     /* flush */
-    no_get_file_info,             /* get_file_info */
-    NULL,                         /* queue_async */
+    no_map_access,                /* map_access */
+    default_get_sd,               /* get_sd */
+    default_set_sd,               /* set_sd */
+    no_lookup_name,               /* lookup_name */
+    no_open_file,                 /* open_file */
+    no_close_handle,              /* close_handle */
     hook_table_destroy            /* destroy */
 };
 
 
-static struct hook_table *global_hooks;
-
 /* create a new hook table */
 static struct hook_table *alloc_hook_table(void)
 {
     struct hook_table *table;
     int i;
 
-    if ((table = alloc_object( &hook_table_ops, -1 )))
+    if ((table = alloc_object( &hook_table_ops )))
     {
         for (i = 0; i < NB_HOOKS; i++)
         {
@@ -95,17 +111,29 @@ static struct hook_table *alloc_hook_table(void)
     return table;
 }
 
+static struct hook_table *get_global_hooks( struct thread *thread )
+{
+    struct hook_table *table;
+    struct desktop *desktop;
+
+    if (!thread->desktop) return NULL;
+    if (!(desktop = get_thread_desktop( thread, 0 ))) return NULL;
+    table = desktop->global_hooks;
+    release_object( desktop );
+    return table;
+}
+
 /* create a new hook and add it to the specified table */
-static struct hook *add_hook( struct thread *thread, int index )
+static struct hook *add_hook( struct desktop *desktop, struct thread *thread, int index, int global )
 {
     struct hook *hook;
-    struct hook_table *table = thread ? thread->hooks : global_hooks;
+    struct hook_table *table = global ? desktop->global_hooks : get_queue_hooks(thread);
 
     if (!table)
     {
         if (!(table = alloc_hook_table())) return NULL;
-        if (thread) thread->hooks = table;
-        else global_hooks = table;
+        if (global) desktop->global_hooks = table;
+        else set_queue_hooks( thread, table );
     }
     if (!(hook = mem_alloc( sizeof(*hook) ))) return NULL;
 
@@ -115,8 +143,10 @@ static struct hook *add_hook( struct thread *thread, int index )
         return NULL;
     }
     hook->thread = thread ? (struct thread *)grab_object( thread ) : NULL;
+    hook->table  = table;
     hook->index  = index;
     list_add_head( &table->hooks[index], &hook->chain );
+    if (thread) thread->desktop_users++;
     return hook;
 }
 
@@ -124,17 +154,24 @@ static struct hook *add_hook( struct thread *thread, int index )
 static void free_hook( struct hook *hook )
 {
     free_user_handle( hook->handle );
-    if (hook->module) free( hook->module );
-    if (hook->thread) release_object( hook->thread );
+    free( hook->module );
+    if (hook->thread)
+    {
+        assert( hook->thread->desktop_users > 0 );
+        hook->thread->desktop_users--;
+        release_object( hook->thread );
+    }
+    if (hook->process) release_object( hook->process );
+    release_object( hook->owner );
     list_remove( &hook->chain );
     free( hook );
 }
 
 /* find a hook from its index and proc */
-static struct hook *find_hook( struct thread *thread, int index, void *proc )
+static struct hook *find_hook( struct thread *thread, int index, client_ptr_t proc )
 {
     struct list *p;
-    struct hook_table *table = thread->hooks;
+    struct hook_table *table = get_queue_hooks( thread );
 
     if (table)
     {
@@ -147,50 +184,96 @@ static struct hook *find_hook( struct thread *thread, int index, void *proc )
     return NULL;
 }
 
-/* get the hook table that a given hook belongs to */
-inline static struct hook_table *get_table( struct hook *hook )
-{
-    return hook->thread ? hook->thread->hooks : global_hooks;
-}
-
 /* get the first hook in the chain */
-inline static struct hook *get_first_hook( struct hook_table *table, int index )
+static inline struct hook *get_first_hook( struct hook_table *table, int index )
 {
     struct list *elem = list_head( &table->hooks[index] );
     return elem ? HOOK_ENTRY( elem ) : NULL;
 }
 
+/* check if a given hook should run in the owner thread instead of the current thread */
+static inline int run_hook_in_owner_thread( struct hook *hook )
+{
+    if ((hook->index == WH_MOUSE_LL - WH_MINHOOK ||
+         hook->index == WH_KEYBOARD_LL - WH_MINHOOK))
+        return hook->owner != current;
+    return 0;
+}
+
+/* check if a given hook should run in the current thread */
+static inline int run_hook_in_current_thread( struct hook *hook )
+{
+    if (hook->process && hook->process != current->process) return 0;
+    if ((hook->flags & WINEVENT_SKIPOWNPROCESS) && hook->process == current->process) return 0;
+    if (hook->thread && hook->thread != current) return 0;
+    if ((hook->flags & WINEVENT_SKIPOWNTHREAD) && hook->thread == current) return 0;
+    /* don't run low-level hooks in processes suspended for debugging */
+    if (run_hook_in_owner_thread( hook ) && hook->owner->process->suspend) return 0;
+    return 1;
+}
+
 /* find the first non-deleted hook in the chain */
-inline static struct hook *get_first_valid_hook( struct hook_table *table, int index )
+static inline struct hook *get_first_valid_hook( struct hook_table *table, int index,
+                                                 int event, user_handle_t win,
+                                                 int object_id, int child_id )
 {
     struct hook *hook = get_first_hook( table, index );
-    while (hook && !hook->proc)
+
+    while (hook)
+    {
+        if (hook->proc && run_hook_in_current_thread( hook ))
+        {
+            if (event >= hook->event_min && event <= hook->event_max)
+            {
+                if (hook->flags & WINEVENT_INCONTEXT) return hook;
+
+                /* only winevent hooks may be out of context */
+                assert(hook->index + WH_MINHOOK == WH_WINEVENT);
+                post_win_event( hook->owner, event, win, object_id, child_id,
+                                hook->proc, hook->module, hook->module_size,
+                                hook->handle );
+            }
+        }
         hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
+    }
     return hook;
 }
 
 /* find the next hook in the chain, skipping the deleted ones */
-static struct hook *get_next_hook( struct hook *hook )
+static struct hook *get_next_hook( struct thread *thread, struct hook *hook, int event,
+                                   user_handle_t win, int object_id, int child_id )
 {
-    struct hook_table *table = get_table( hook );
+    struct hook_table *global_hooks, *table = hook->table;
     int index = hook->index;
 
     while ((hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) )))
     {
-        if (hook->proc) return hook;
+        if (hook->proc && run_hook_in_current_thread( hook ))
+        {
+            if (event >= hook->event_min && event <= hook->event_max)
+            {
+                if (hook->flags & WINEVENT_INCONTEXT) return hook;
+
+                /* only winevent hooks may be out of context */
+                assert(hook->index + WH_MINHOOK == WH_WINEVENT);
+                post_win_event( hook->owner, event, win, object_id, child_id,
+                                hook->proc, hook->module, hook->module_size,
+                                hook->handle );
+            }
+        }
     }
+    global_hooks = get_global_hooks( thread );
     if (global_hooks && table != global_hooks)  /* now search through the global table */
     {
-        hook = get_first_valid_hook( global_hooks, index );
+        hook = get_first_valid_hook( global_hooks, index, event, win, object_id, child_id );
     }
     return hook;
 }
 
 static void hook_table_dump( struct object *obj, int verbose )
 {
-    struct hook_table *table = (struct hook_table *)obj;
-    if (table == global_hooks) fprintf( stderr, "Global hook table\n" );
-    else fprintf( stderr, "Hook table\n" );
+    /* struct hook_table *table = (struct hook_table *)obj; */
+    fprintf( stderr, "Hook table\n" );
 }
 
 static void hook_table_destroy( struct object *obj )
@@ -205,18 +288,11 @@ static void hook_table_destroy( struct object *obj )
     }
 }
 
-/* free the global hooks table */
-void close_global_hooks(void)
-{
-    if (global_hooks) release_object( global_hooks );
-}
-
 /* remove a hook, freeing it if the chain is not in use */
 static void remove_hook( struct hook *hook )
 {
-    struct hook_table *table = get_table( hook );
-    if (table->counts[hook->index])
-        hook->proc = NULL; /* chain is in use, just mark it and return */
+    if (hook->table->counts[hook->index])
+        hook->proc = 0; /* chain is in use, just mark it and return */
     else
         free_hook( hook );
 }
@@ -241,47 +317,157 @@ static void release_hook_chain( struct hook_table *table, int index )
     }
 }
 
+/* remove all global hooks owned by a given thread */
+void remove_thread_hooks( struct thread *thread )
+{
+    struct hook_table *global_hooks = get_global_hooks( thread );
+    int index;
+
+    if (!global_hooks) return;
+
+    /* only low-level keyboard/mouse global hooks can be owned by a thread */
+    for (index = WH_KEYBOARD_LL - WH_MINHOOK; index <= WH_MOUSE_LL - WH_MINHOOK; index++)
+    {
+        struct hook *hook = get_first_hook( global_hooks, index );
+        while (hook)
+        {
+            struct hook *next = HOOK_ENTRY( list_next( &global_hooks->hooks[index], &hook->chain ) );
+            if (hook->thread == thread) remove_hook( hook );
+            hook = next;
+        }
+    }
+}
+
+/* get a bitmap of active hooks in a hook table */
+static int is_hook_active( struct hook_table *table, int index )
+{
+    struct hook *hook = get_first_hook( table, index );
+
+    while (hook)
+    {
+        if (hook->proc && run_hook_in_current_thread( hook )) return 1;
+        hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
+    }
+    return 0;
+}
+
+/* get a bitmap of all active hooks for the current thread */
+unsigned int get_active_hooks(void)
+{
+    struct hook_table *table = get_queue_hooks( current );
+    struct hook_table *global_hooks = get_global_hooks( current );
+    unsigned int ret = 1 << 31;  /* set high bit to indicate that the bitmap is valid */
+    int id;
+
+    for (id = WH_MINHOOK; id <= WH_WINEVENT; id++)
+    {
+        if ((table && is_hook_active( table, id - WH_MINHOOK )) ||
+            (global_hooks && is_hook_active( global_hooks, id - WH_MINHOOK )))
+            ret |= 1 << (id - WH_MINHOOK);
+    }
+    return ret;
+}
+
+/* return the thread that owns the first global hook */
+struct thread *get_first_global_hook( int id )
+{
+    struct hook *hook;
+    struct hook_table *global_hooks = get_global_hooks( current );
+
+    if (!global_hooks) return NULL;
+    if (!(hook = get_first_valid_hook( global_hooks, id - WH_MINHOOK, EVENT_MIN, 0, 0, 0 ))) return NULL;
+    return hook->owner;
+}
 
 /* set a window hook */
 DECL_HANDLER(set_hook)
 {
-    struct thread *thread;
+    struct process *process = NULL;
+    struct thread *thread = NULL;
+    struct desktop *desktop;
     struct hook *hook;
     WCHAR *module;
-    size_t module_size = get_req_data_size();
+    int global;
+    data_size_t module_size = get_req_data_size();
 
-    if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
-    if (!req->tid)
+
+    if (!(desktop = get_thread_desktop( current, DESKTOP_HOOKCONTROL ))) return;
+
+    if (req->pid && !(process = get_process_from_id( req->pid ))) goto done;
+
+    if (req->tid)
     {
-        if (!module_size)
+        if (!(thread = get_thread_from_id( req->tid ))) goto done;
+        if (process && process != thread->process)
         {
             set_error( STATUS_INVALID_PARAMETER );
-            return;
+            goto done;
         }
-        if (!(module = memdup( get_req_data(), module_size ))) return;
-        thread = NULL;
     }
-    else
+
+    if (req->id == WH_KEYBOARD_LL || req->id == WH_MOUSE_LL)
     {
+        /* low-level hardware hooks are special: always global, but without a module */
+        if (thread)
+        {
+            set_error( STATUS_INVALID_PARAMETER );
+            goto done;
+        }
         module = NULL;
-        if (!(thread = get_thread_from_id( req->tid ))) return;
+        global = 1;
+    }
+    else if (!req->tid)
+    {
+        /* out of context hooks do not need a module handle */
+        if (!module_size && (req->flags & WINEVENT_INCONTEXT))
+        {
+            set_error( STATUS_INVALID_PARAMETER );
+            goto done;
+        }
+        if (!(module = memdup( get_req_data(), module_size ))) goto done;
+        global = 1;
+    }
+    else
+    {
+        /* module is optional only if hook is in current process */
+        if (!module_size)
+        {
+            module = NULL;
+            if (thread->process != current->process)
+            {
+                set_error( STATUS_INVALID_PARAMETER );
+                goto done;
+            }
+        }
+        else if (!(module = memdup( get_req_data(), module_size ))) goto done;
+        global = 0;
     }
 
-    if ((hook = add_hook( thread, req->id - WH_MINHOOK )))
+    if ((hook = add_hook( desktop, thread, req->id - WH_MINHOOK, global )))
     {
+        hook->owner = (struct thread *)grab_object( current );
+        hook->process = process ? (struct process *)grab_object( process ) : NULL;
+        hook->event_min   = req->event_min;
+        hook->event_max   = req->event_max;
+        hook->flags       = req->flags;
         hook->proc        = req->proc;
         hook->unicode     = req->unicode;
         hook->module      = module;
         hook->module_size = module_size;
         reply->handle = hook->handle;
+        reply->active_hooks = get_active_hooks();
     }
-    else if (module) free( module );
+    else free( module );
 
+done:
+    if (process) release_object( process );
     if (thread) release_object( thread );
+    release_object( desktop );
 }
 
 
@@ -290,18 +476,29 @@ DECL_HANDLER(remove_hook)
 {
     struct hook *hook;
 
-    if (req->handle) hook = get_user_object( req->handle, USER_HOOK );
+    if (req->handle)
+    {
+        if (!(hook = get_user_object( req->handle, USER_HOOK )))
+        {
+            set_error( STATUS_INVALID_HANDLE );
+            return;
+        }
+    }
     else
     {
-        if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+        if (!req->proc || req->id < WH_MINHOOK || req->id > WH_WINEVENT)
         {
             set_error( STATUS_INVALID_PARAMETER );
             return;
         }
         if (!(hook = find_hook( current, req->id - WH_MINHOOK, req->proc )))
+        {
             set_error( STATUS_INVALID_PARAMETER );
+            return;
+        }
     }
-    if (hook) remove_hook( hook );
+    remove_hook( hook );
+    reply->active_hooks = get_active_hooks();
 }
 
 
@@ -309,25 +506,41 @@ DECL_HANDLER(remove_hook)
 DECL_HANDLER(start_hook_chain)
 {
     struct hook *hook;
-    struct hook_table *table = current->hooks;
+    struct hook_table *table = get_queue_hooks( current );
+    struct hook_table *global_table = get_global_hooks( current );
 
-    if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
     }
 
-    if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK )))
+    reply->active_hooks = get_active_hooks();
+
+    if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK, req->event,
+                                                 req->window, req->object_id, req->child_id )))
     {
         /* try global table */
-        if (!(table = global_hooks) ||
-            !(hook = get_first_valid_hook( global_hooks, req->id - WH_MINHOOK )))
+        if (!global_table || !(hook = get_first_valid_hook( global_table, req->id - WH_MINHOOK, req->event,
+                                                            req->window, req->object_id, req->child_id )))
             return;  /* no hook set */
     }
-    reply->handle  = hook->handle;
+
+    if (run_hook_in_owner_thread( hook ))
+    {
+        reply->pid  = get_process_id( hook->owner->process );
+        reply->tid  = get_thread_id( hook->owner );
+    }
+    else
+    {
+        reply->pid  = 0;
+        reply->tid  = 0;
+    }
     reply->proc    = hook->proc;
+    reply->handle  = hook->handle;
     reply->unicode = hook->unicode;
-    table->counts[hook->index]++;
+    if (table) table->counts[hook->index]++;
+    if (global_table) global_table->counts[hook->index]++;
     if (hook->module) set_reply_data( hook->module, hook->module_size );
 }
 
@@ -335,10 +548,11 @@ DECL_HANDLER(start_hook_chain)
 /* finished calling a hook chain */
 DECL_HANDLER(finish_hook_chain)
 {
-    struct hook_table *table = current->hooks;
+    struct hook_table *table = get_queue_hooks( current );
+    struct hook_table *global_hooks = get_global_hooks( current );
     int index = req->id - WH_MINHOOK;
 
-    if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
+    if (req->id < WH_MINHOOK || req->id > WH_WINEVENT)
     {
         set_error( STATUS_INVALID_PARAMETER );
         return;
@@ -348,10 +562,10 @@ DECL_HANDLER(finish_hook_chain)
 }
 
 
-/* get the next hook to call */
-DECL_HANDLER(get_next_hook)
+/* get the hook information */
+DECL_HANDLER(get_hook_info)
 {
-    struct hook *hook, *next;
+    struct hook *hook;
 
     if (!(hook = get_user_object( req->handle, USER_HOOK ))) return;
     if (hook->thread && (hook->thread != current))
@@ -359,13 +573,23 @@ DECL_HANDLER(get_next_hook)
         set_error( STATUS_INVALID_HANDLE );
         return;
     }
-    if ((next = get_next_hook( hook )))
+    if (req->get_next && !(hook = get_next_hook( current, hook, req->event, req->window,
+                                                 req->object_id, req->child_id )))
+        return;
+
+    reply->handle  = hook->handle;
+    reply->id      = hook->index + WH_MINHOOK;
+    reply->unicode = hook->unicode;
+    if (hook->module) set_reply_data( hook->module, min(hook->module_size,get_reply_max_size()) );
+    if (run_hook_in_owner_thread( hook ))
+    {
+        reply->pid  = get_process_id( hook->owner->process );
+        reply->tid  = get_thread_id( hook->owner );
+    }
+    else
     {
-        reply->next = next->handle;
-        reply->id   = next->index + WH_MINHOOK;
-        reply->proc = next->proc;
-        reply->prev_unicode = hook->unicode;
-        reply->next_unicode = next->unicode;
-        if (next->module) set_reply_data( next->module, next->module_size );
+        reply->pid  = 0;
+        reply->tid  = 0;
     }
+    reply->proc = hook->proc;
 }