Run WH_KEYBOARD_LL and WH_MOUSE_LL hooks in the context of the thread
[wine] / server / hook.c
1 /*
2  * Server-side window hooks support
3  *
4  * Copyright (C) 2002 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <assert.h>
25 #include <stdio.h>
26
27 #include "winbase.h"
28 #include "winuser.h"
29
30 #include "object.h"
31 #include "process.h"
32 #include "request.h"
33 #include "user.h"
34
35 struct hook_table;
36
37 struct hook
38 {
39     struct list         chain;    /* hook chain entry */
40     user_handle_t       handle;   /* user handle for this hook */
41     struct thread      *thread;   /* thread owning the hook */
42     int                 index;    /* hook table index */
43     void               *proc;     /* hook function */
44     int                 unicode;  /* is it a unicode hook? */
45     WCHAR              *module;   /* module name for global hooks */
46     size_t              module_size;
47 };
48
49 #define NB_HOOKS (WH_MAXHOOK-WH_MINHOOK+1)
50 #define HOOK_ENTRY(p)  LIST_ENTRY( (p), struct hook, chain )
51
52 struct hook_table
53 {
54     struct object obj;              /* object header */
55     struct list   hooks[NB_HOOKS];  /* array of hook chains */
56     int           counts[NB_HOOKS]; /* use counts for each hook chain */
57 };
58
59 static void hook_table_dump( struct object *obj, int verbose );
60 static void hook_table_destroy( struct object *obj );
61
62 static const struct object_ops hook_table_ops =
63 {
64     sizeof(struct hook_table),    /* size */
65     hook_table_dump,              /* dump */
66     no_add_queue,                 /* add_queue */
67     NULL,                         /* remove_queue */
68     NULL,                         /* signaled */
69     NULL,                         /* satisfied */
70     no_get_fd,                    /* get_fd */
71     hook_table_destroy            /* destroy */
72 };
73
74
75 static struct hook_table *global_hooks;
76
77 /* create a new hook table */
78 static struct hook_table *alloc_hook_table(void)
79 {
80     struct hook_table *table;
81     int i;
82
83     if ((table = alloc_object( &hook_table_ops )))
84     {
85         for (i = 0; i < NB_HOOKS; i++)
86         {
87             list_init( &table->hooks[i] );
88             table->counts[i] = 0;
89         }
90     }
91     return table;
92 }
93
94 /* create a new hook and add it to the specified table */
95 static struct hook *add_hook( struct thread *thread, int index, int global )
96 {
97     struct hook *hook;
98     struct hook_table *table = global ? global_hooks : get_queue_hooks(thread);
99
100     if (!table)
101     {
102         if (!(table = alloc_hook_table())) return NULL;
103         if (global) global_hooks = table;
104         else set_queue_hooks( thread, table );
105     }
106     if (!(hook = mem_alloc( sizeof(*hook) ))) return NULL;
107
108     if (!(hook->handle = alloc_user_handle( hook, USER_HOOK )))
109     {
110         free( hook );
111         return NULL;
112     }
113     hook->thread = thread ? (struct thread *)grab_object( thread ) : NULL;
114     hook->index  = index;
115     list_add_head( &table->hooks[index], &hook->chain );
116     return hook;
117 }
118
119 /* free a hook, removing it from its chain */
120 static void free_hook( struct hook *hook )
121 {
122     free_user_handle( hook->handle );
123     if (hook->module) free( hook->module );
124     if (hook->thread) release_object( hook->thread );
125     list_remove( &hook->chain );
126     free( hook );
127 }
128
129 /* find a hook from its index and proc */
130 static struct hook *find_hook( struct thread *thread, int index, void *proc )
131 {
132     struct list *p;
133     struct hook_table *table = get_queue_hooks( thread );
134
135     if (table)
136     {
137         LIST_FOR_EACH( p, &table->hooks[index] )
138         {
139             struct hook *hook = HOOK_ENTRY( p );
140             if (hook->proc == proc) return hook;
141         }
142     }
143     return NULL;
144 }
145
146 /* get the hook table that a given hook belongs to */
147 inline static struct hook_table *get_table( struct hook *hook )
148 {
149     if (!hook->thread) return global_hooks;
150     if (hook->index + WH_MINHOOK == WH_KEYBOARD_LL) return global_hooks;
151     if (hook->index + WH_MINHOOK == WH_MOUSE_LL) return global_hooks;
152     return get_queue_hooks(hook->thread);
153 }
154
155 /* get the first hook in the chain */
156 inline static struct hook *get_first_hook( struct hook_table *table, int index )
157 {
158     struct list *elem = list_head( &table->hooks[index] );
159     return elem ? HOOK_ENTRY( elem ) : NULL;
160 }
161
162 /* find the first non-deleted hook in the chain */
163 inline static struct hook *get_first_valid_hook( struct hook_table *table, int index )
164 {
165     struct hook *hook = get_first_hook( table, index );
166     while (hook && !hook->proc)
167         hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) );
168     return hook;
169 }
170
171 /* find the next hook in the chain, skipping the deleted ones */
172 static struct hook *get_next_hook( struct hook *hook )
173 {
174     struct hook_table *table = get_table( hook );
175     int index = hook->index;
176
177     while ((hook = HOOK_ENTRY( list_next( &table->hooks[index], &hook->chain ) )))
178     {
179         if (hook->proc) return hook;
180     }
181     if (global_hooks && table != global_hooks)  /* now search through the global table */
182     {
183         hook = get_first_valid_hook( global_hooks, index );
184     }
185     return hook;
186 }
187
188 static void hook_table_dump( struct object *obj, int verbose )
189 {
190     struct hook_table *table = (struct hook_table *)obj;
191     if (table == global_hooks) fprintf( stderr, "Global hook table\n" );
192     else fprintf( stderr, "Hook table\n" );
193 }
194
195 static void hook_table_destroy( struct object *obj )
196 {
197     int i;
198     struct hook *hook;
199     struct hook_table *table = (struct hook_table *)obj;
200
201     for (i = 0; i < NB_HOOKS; i++)
202     {
203         while ((hook = get_first_hook( table, i )) != NULL) free_hook( hook );
204     }
205 }
206
207 /* free the global hooks table */
208 void close_global_hooks(void)
209 {
210     if (global_hooks) release_object( global_hooks );
211 }
212
213 /* remove a hook, freeing it if the chain is not in use */
214 static void remove_hook( struct hook *hook )
215 {
216     struct hook_table *table = get_table( hook );
217     if (table->counts[hook->index])
218         hook->proc = NULL; /* chain is in use, just mark it and return */
219     else
220         free_hook( hook );
221 }
222
223 /* release a hook chain, removing deleted hooks if the use count drops to 0 */
224 static void release_hook_chain( struct hook_table *table, int index )
225 {
226     if (!table->counts[index])  /* use count shouldn't already be 0 */
227     {
228         set_error( STATUS_INVALID_PARAMETER );
229         return;
230     }
231     if (!--table->counts[index])
232     {
233         struct hook *hook = get_first_hook( table, index );
234         while (hook)
235         {
236             struct hook *next = HOOK_ENTRY( list_next( &table->hooks[hook->index], &hook->chain ) );
237             if (!hook->proc) free_hook( hook );
238             hook = next;
239         }
240     }
241 }
242
243 /* remove all global hooks owned by a given thread */
244 void remove_thread_hooks( struct thread *thread )
245 {
246     int index;
247
248     if (!global_hooks) return;
249
250     /* only low-level keyboard/mouse global hooks can be owned by a thread */
251     for (index = WH_KEYBOARD_LL - WH_MINHOOK; index <= WH_MOUSE_LL - WH_MINHOOK; index++)
252     {
253         struct hook *hook = get_first_hook( global_hooks, index );
254         while (hook)
255         {
256             struct hook *next = HOOK_ENTRY( list_next( &global_hooks->hooks[index], &hook->chain ) );
257             if (hook->thread == thread) remove_hook( hook );
258             hook = next;
259         }
260     }
261 }
262
263 /* set a window hook */
264 DECL_HANDLER(set_hook)
265 {
266     struct thread *thread;
267     struct hook *hook;
268     WCHAR *module;
269     int global;
270     size_t module_size = get_req_data_size();
271
272     if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
273     {
274         set_error( STATUS_INVALID_PARAMETER );
275         return;
276     }
277     if (req->id == WH_KEYBOARD_LL || req->id == WH_MOUSE_LL)
278     {
279         /* low-level hardware hooks are special: always global, but without a module */
280         thread = (struct thread *)grab_object( current );
281         module = NULL;
282         global = 1;
283     }
284     else if (!req->tid)
285     {
286         if (!module_size)
287         {
288             set_error( STATUS_INVALID_PARAMETER );
289             return;
290         }
291         if (!(module = memdup( get_req_data(), module_size ))) return;
292         thread = NULL;
293         global = 1;
294     }
295     else
296     {
297         module = NULL;
298         global = 0;
299         if (!(thread = get_thread_from_id( req->tid ))) return;
300     }
301
302     if ((hook = add_hook( thread, req->id - WH_MINHOOK, global )))
303     {
304         hook->proc        = req->proc;
305         hook->unicode     = req->unicode;
306         hook->module      = module;
307         hook->module_size = module_size;
308         reply->handle = hook->handle;
309     }
310     else if (module) free( module );
311
312     if (thread) release_object( thread );
313 }
314
315
316 /* remove a window hook */
317 DECL_HANDLER(remove_hook)
318 {
319     struct hook *hook;
320
321     if (req->handle) hook = get_user_object( req->handle, USER_HOOK );
322     else
323     {
324         if (!req->proc || req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
325         {
326             set_error( STATUS_INVALID_PARAMETER );
327             return;
328         }
329         if (!(hook = find_hook( current, req->id - WH_MINHOOK, req->proc )))
330             set_error( STATUS_INVALID_PARAMETER );
331     }
332     if (hook) remove_hook( hook );
333 }
334
335
336 /* start calling a hook chain */
337 DECL_HANDLER(start_hook_chain)
338 {
339     struct hook *hook;
340     struct hook_table *table = get_queue_hooks( current );
341
342     if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
343     {
344         set_error( STATUS_INVALID_PARAMETER );
345         return;
346     }
347
348     if (!table || !(hook = get_first_valid_hook( table, req->id - WH_MINHOOK )))
349     {
350         /* try global table */
351         if (!(table = global_hooks) ||
352             !(hook = get_first_valid_hook( global_hooks, req->id - WH_MINHOOK )))
353             return;  /* no hook set */
354     }
355
356     if (hook->thread && hook->thread != current)  /* must run in other thread */
357     {
358         reply->pid  = get_process_id( hook->thread->process );
359         reply->tid  = get_thread_id( hook->thread );
360         reply->proc = 0;
361     }
362     else
363     {
364         reply->pid  = 0;
365         reply->tid  = 0;
366         reply->proc = hook->proc;
367     }
368     reply->handle  = hook->handle;
369     reply->unicode = hook->unicode;
370     table->counts[hook->index]++;
371     if (hook->module) set_reply_data( hook->module, hook->module_size );
372 }
373
374
375 /* finished calling a hook chain */
376 DECL_HANDLER(finish_hook_chain)
377 {
378     struct hook_table *table = get_queue_hooks( current );
379     int index = req->id - WH_MINHOOK;
380
381     if (req->id < WH_MINHOOK || req->id > WH_MAXHOOK)
382     {
383         set_error( STATUS_INVALID_PARAMETER );
384         return;
385     }
386     if (table) release_hook_chain( table, index );
387     if (global_hooks) release_hook_chain( global_hooks, index );
388 }
389
390
391 /* get the next hook to call */
392 DECL_HANDLER(get_next_hook)
393 {
394     struct hook *hook, *next;
395
396     if (!(hook = get_user_object( req->handle, USER_HOOK ))) return;
397     if (hook->thread && (hook->thread != current))
398     {
399         set_error( STATUS_INVALID_HANDLE );
400         return;
401     }
402     if ((next = get_next_hook( hook )))
403     {
404         reply->next = next->handle;
405         reply->id   = next->index + WH_MINHOOK;
406         reply->prev_unicode = hook->unicode;
407         reply->next_unicode = next->unicode;
408         if (next->module) set_reply_data( next->module, next->module_size );
409         if (next->thread && next->thread != current)
410         {
411             reply->pid  = get_process_id( next->thread->process );
412             reply->tid  = get_thread_id( next->thread );
413             reply->proc = 0;
414         }
415         else
416         {
417             reply->pid  = 0;
418             reply->tid  = 0;
419             reply->proc = next->proc;
420         }
421     }
422 }