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