Various fixes
[platform/upstream/at-spi2-core.git] / atspi / atspi-device-listener.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2002 Ximian Inc.
6  * Copyright 2002 Sun Microsystems, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include "atspi-private.h"
25
26 typedef struct
27 {
28   AtspiDeviceListenerCB    callback;
29   gpointer user_data;
30   GDestroyNotify callback_destroyed;
31 } DeviceEventHandler;
32
33 GObjectClass *device_parent_class;
34
35 static guint32 _e_id = 0;
36
37 /*
38  * Misc. helpers.
39  */
40
41 static DeviceEventHandler *
42 device_event_handler_new (AtspiDeviceListenerCB callback,
43                           GDestroyNotify callback_destroyed,
44                           gpointer user_data)
45 {
46   DeviceEventHandler *eh = g_new0 (DeviceEventHandler, 1);
47
48   eh->callback = callback;
49   eh->callback_destroyed = callback_destroyed;
50   eh->user_data = user_data;
51
52   return eh;
53 }
54
55 static gboolean
56 device_remove_datum (const AtspiDeviceEvent *event, void *user_data)
57 {
58   AtspiDeviceListenerSimpleCB cb = user_data;
59   return cb (event);
60 }
61   
62 static void
63 device_event_handler_free (DeviceEventHandler *eh)
64 {
65 #if 0
66   /* TODO; Test this; it will probably crash with pyatspi for unknown reasons */
67   if (eh->callback_destroyed)
68   {
69     gpointer rea_callback = (eh->callback == device_remove_datum ?
70                             eh->user_data : eh->callback);
71     (*eh->callback_destroyed) (real_callback);
72   }
73 #endif
74   g_free (eh);
75 }
76
77 static GList *
78 event_list_remove_by_cb (GList *list, AtspiDeviceListenerCB callback)
79 {
80   GList *l, *next;
81         
82   for (l = list; l; l = next)
83     {
84       DeviceEventHandler *eh = l->data;
85       next = l->next;
86
87       if (eh->callback == callback)
88       {
89         list = g_list_delete_link (list, l);
90         device_event_handler_free (eh);
91       }
92     }
93
94   return list;
95 }
96
97 /*
98  * Standard event dispatcher
99  */
100
101 static guint listener_id = 0;
102 static GList *device_listeners = NULL;
103
104 static gboolean
105 id_is_free (guint id)
106 {
107   GList *l;
108
109   for (l = device_listeners; l; l = g_list_next (l))
110   {
111     AtspiDeviceListener *listener = l->data;
112     if (listener->id == id) return FALSE;
113   }
114   return TRUE;
115 }
116
117 static void
118 remove_listener (GObject *obj, gpointer data)
119 {
120   device_listeners = g_list_remove (device_listeners, obj);
121 }
122
123 static AtspiDeviceEvent *
124 atspi_device_event_copy (AtspiDeviceEvent *src)
125 {
126   AtspiDeviceEvent *dst = g_new0 (AtspiDeviceEvent, 1);
127   if (!dst)
128     return NULL;
129   dst->type = src->type;
130   dst->id = src->id;
131   dst->hw_code = src->hw_code;
132   dst->modifiers = src->modifiers;
133   dst->timestamp = src->timestamp;
134   if (src->event_string)
135     dst->event_string = g_strdup (src->event_string);
136   dst->is_text = src->is_text;
137   return dst;
138 }
139
140 void
141 atspi_device_event_free (AtspiDeviceEvent *event)
142 {
143   if (event->event_string)
144     g_free (event->event_string);
145   g_free (event);
146 }
147
148 /* 
149  * Device event handler
150  */
151 static gboolean
152 atspi_device_event_dispatch (AtspiDeviceListener               *listener,
153                    const AtspiDeviceEvent *event)
154 {
155   GList *l;
156   gboolean handled = FALSE;
157
158   /* FIXME: re-enterancy hazard on this list */
159   for (l = listener->callbacks; l; l = l->next)
160     {
161       DeviceEventHandler *eh = l->data;
162
163       if ((handled = eh->callback (atspi_device_event_copy (event), eh->user_data)))
164         {
165           break;
166         }
167     }
168
169   return handled;
170 }
171
172 static void
173 atspi_device_listener_init (AtspiDeviceListener *listener)
174 {
175   GList *new_list;
176
177   do
178   {
179     listener->id = listener_id++;
180   } while (!id_is_free (listener->id));
181   new_list = g_list_append (device_listeners, listener);
182   if (new_list) device_listeners = new_list;
183 }
184
185 static void
186 atspi_device_listener_finalize (GObject *object)
187 {
188   AtspiDeviceListener *listener = (AtspiDeviceListener *) object;
189   GList *l;
190   
191   for (l = listener->callbacks; l; l = l->next)
192     {
193       device_event_handler_free (l->data);
194     }
195   
196   g_list_free (listener->callbacks);
197
198   device_parent_class->finalize (object);
199 }
200
201 static void
202 atspi_device_listener_class_init (AtspiDeviceListenerClass *klass)
203 {
204   GObjectClass *object_class = (GObjectClass *) klass;
205
206   device_parent_class = g_type_class_peek_parent (klass);
207   object_class->finalize = atspi_device_listener_finalize;
208
209   klass->device_event = atspi_device_event_dispatch;
210 }
211
212 G_DEFINE_TYPE (AtspiDeviceListener, atspi_device_listener,
213                           G_TYPE_OBJECT)
214
215 /**
216  * atspi_device_listener_new:
217  * @callback: (scope notify): an #AtspiDeviceListenerCB callback function,
218  *            or NULL.
219  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
220  * and data associated with the callback should be freed.  Can be NULL.
221  * @user_data: (closure): a pointer to data which will be passed to the
222  * callback when invoked.
223  *
224  * Create a new #AtspiDeviceListener with a specified callback function.
225  *
226  * Returns: a pointer to a newly-created #AtspiDeviceListener.
227  *
228  **/
229 AtspiDeviceListener *
230 atspi_device_listener_new (AtspiDeviceListenerCB callback,
231                            GDestroyNotify callback_destroyed,
232                            void *user_data)
233 {
234   AtspiDeviceListener *listener = g_object_new (atspi_device_listener_get_type (), NULL);
235
236   if (callback)
237     atspi_device_listener_add_callback (listener, callback, callback_destroyed,
238                                        user_data);
239   return listener;
240 }
241
242 /**
243  * atspi_device_listener_new_simple:
244  * @callback: (scope notify): an #AtspiDeviceListenerCB callback function,
245  *            or NULL.
246  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
247  * and data associated with the callback should be freed.  Can be NULL.
248  *
249  * Create a new #AtspiDeviceListener with a specified callback function.
250  * Like atspi_device_listener_new, but callback takes no user data.
251  *
252  * Returns: a pointer to a newly-created #AtspiDeviceListener.
253  *
254  **/
255 AtspiDeviceListener *
256 atspi_device_listener_new_simple (AtspiDeviceListenerSimpleCB callback,
257                            GDestroyNotify callback_destroyed)
258 {
259   return atspi_device_listener_new (device_remove_datum, callback_destroyed, callback);
260 }
261
262 /**
263  * atspi_device_listener_add_callback:
264  * @listener: the #AtspiDeviceListener instance to modify.
265  * @callback: (scope notify): an #AtspiDeviceListenerCB function pointer.
266  * @user_data: (closure): a pointer to data which will be passed to the
267  *             callback when invoked.
268  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
269  * and data associated with the callback should be freed.  Can be NULL.
270  *
271  * Add an in-process callback function to an existing #AtspiDeviceListener.
272  *
273  * Returns: #TRUE if successful, otherwise #FALSE.
274  *
275  **/
276 void
277 atspi_device_listener_add_callback (AtspiDeviceListener  *listener,
278                              AtspiDeviceListenerCB callback,
279                              GDestroyNotify callback_destroyed,
280                              void                      *user_data)
281 {
282   g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
283   DeviceEventHandler *new_handler;
284
285   new_handler = device_event_handler_new (callback,
286                                           callback_destroyed, user_data);
287
288   if (new_handler)
289   {
290     GList *new_list;
291     new_list = g_list_prepend (listener->callbacks, new_handler);
292     if (new_list)
293       listener->callbacks = new_list;
294   }
295 }
296
297 /**
298  * atspi_device_listener_remove_callback:
299  * @listener: the #AtspiDeviceListener instance to modify.
300  * @callback: (scope call): an #AtspiDeviceListenerCB function pointer.
301  *
302  * Remove an in-process callback function from an existing #AtspiDeviceListener.
303  *
304  * Returns: #TRUE if successful, otherwise #FALSE.
305  *
306  **/
307 void
308 atspi_device_listener_remove_callback (AtspiDeviceListener  *listener,
309                                 AtspiDeviceListenerCB callback)
310 {
311   g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
312
313   listener->callbacks = event_list_remove_by_cb (listener->callbacks, (void *) callback);
314 }
315
316 static const char *device_event_type = "(uinnisb)";
317
318 static void
319 read_device_event_from_iter (DBusMessageIter *iter, AtspiDeviceEvent *event)
320 {
321   dbus_uint32_t type;
322   dbus_int32_t id;
323   dbus_int16_t hw_code;
324   dbus_int16_t modifiers;
325   dbus_int32_t timestamp;
326   char *event_string;
327   dbus_bool_t is_text;
328   DBusMessageIter iter_struct;
329
330   dbus_message_iter_recurse (iter, &iter_struct);
331
332   dbus_message_iter_get_basic (&iter_struct, &type);
333   event->type = type;
334   dbus_message_iter_next (&iter_struct);
335
336   dbus_message_iter_get_basic (&iter_struct, &id);
337   event->id = id;
338   dbus_message_iter_next (&iter_struct);
339
340   dbus_message_iter_get_basic (&iter_struct, &hw_code);
341   event->hw_code = hw_code;
342   dbus_message_iter_next (&iter_struct);
343
344   dbus_message_iter_get_basic (&iter_struct, &modifiers);
345   event->modifiers = modifiers;
346   dbus_message_iter_next (&iter_struct);
347
348   dbus_message_iter_get_basic (&iter_struct, &timestamp);
349   event->timestamp = timestamp;
350   dbus_message_iter_next (&iter_struct);
351
352   dbus_message_iter_get_basic (&iter_struct, &event->event_string);
353   dbus_message_iter_next (&iter_struct);
354
355   dbus_message_iter_get_basic (&iter_struct, &is_text);
356   event->is_text = is_text;
357 }
358
359 /*
360  * atspi_dbus_handle_DeviceEvent: (skip)
361  */
362 DBusHandlerResult
363 atspi_dbus_handle_DeviceEvent (DBusConnection *bus, DBusMessage *message, void *data)
364 {
365   const char *path = dbus_message_get_path (message);
366   int id;
367   AtspiDeviceEvent event;
368     AtspiDeviceListener *listener;
369   DBusMessageIter iter;
370   AtspiDeviceListenerClass *klass;
371   dbus_bool_t retval = FALSE;
372   GList *l;
373   DBusMessage *reply;
374   void *p = &event;
375
376   if (strcmp (dbus_message_get_signature (message), "(uinnisb)") != 0)
377   {
378     g_warning ("Atspi: Unknown signature for an event");
379     goto done;
380   }
381
382   if (sscanf (path, "/org/a11y/atspi/listeners/%d", &id) != 1)
383   {
384     g_warning ("Atspi: Bad listener path: %s\n", path);
385     goto done;
386   }
387
388   for (l = device_listeners; l; l = g_list_next (l))
389   {
390     listener = l->data;
391     if (listener->id == id) break;
392   }
393
394   if (!l)
395   {
396     goto done;
397   }
398   dbus_message_iter_init (message, &iter);
399   read_device_event_from_iter (&iter, &event);
400   klass = ATSPI_DEVICE_LISTENER_GET_CLASS (listener);
401   if (klass->device_event)
402   {
403     retval = (*klass->device_event) (listener, &event);
404   }
405 done:
406   reply = dbus_message_new_method_return (message);
407   if (reply)
408   {
409     dbus_message_append_args (reply, DBUS_TYPE_BOOLEAN, &retval, DBUS_TYPE_INVALID);
410     dbus_connection_send (_atspi_bus(), reply, NULL);
411     dbus_message_unref (reply);
412   }
413   return DBUS_HANDLER_RESULT_HANDLED;
414 }
415
416 gchar *
417 _atspi_device_listener_get_path (AtspiDeviceListener *listener)
418 {
419   return g_strdup_printf ("/org/a11y/atspi/listeners/%d", listener->id);
420 }
421
422 G_DEFINE_BOXED_TYPE (AtspiDeviceEvent,
423                      atspi_device_event,
424                      atspi_device_event_copy,
425                      atspi_device_event_free)