Improving atspi-devicelistener documentation.
[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  * Copyright 2010, 2011 Novell, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include "atspi-private.h"
26 #include <stdio.h>
27
28 typedef struct
29 {
30   AtspiDeviceListenerCB    callback;
31   gpointer user_data;
32   GDestroyNotify callback_destroyed;
33 } DeviceEventHandler;
34
35 GObjectClass *device_parent_class;
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 AtspiDeviceEvent *
118 atspi_device_event_copy (AtspiDeviceEvent *src)
119 {
120   AtspiDeviceEvent *dst = g_new0 (AtspiDeviceEvent, 1);
121   dst->type = src->type;
122   dst->id = src->id;
123   dst->hw_code = src->hw_code;
124   dst->modifiers = src->modifiers;
125   dst->timestamp = src->timestamp;
126   if (src->event_string)
127     dst->event_string = g_strdup (src->event_string);
128   dst->is_text = src->is_text;
129   return dst;
130 }
131
132 void
133 atspi_device_event_free (AtspiDeviceEvent *event)
134 {
135   if (event->event_string)
136     g_free (event->event_string);
137   g_free (event);
138 }
139
140 /* 
141  * Device event handler
142  */
143 static gboolean
144 atspi_device_event_dispatch (AtspiDeviceListener               *listener,
145                    const AtspiDeviceEvent *event)
146 {
147   GList *l;
148   gboolean handled = FALSE;
149
150   /* FIXME: re-enterancy hazard on this list */
151   for (l = listener->callbacks; l; l = l->next)
152     {
153       DeviceEventHandler *eh = l->data;
154
155       if ((handled = eh->callback (atspi_device_event_copy (event), eh->user_data)))
156         {
157           break;
158         }
159     }
160
161   return handled;
162 }
163
164 static void
165 atspi_device_listener_init (AtspiDeviceListener *listener)
166 {
167   GList *new_list;
168
169   do
170   {
171     listener->id = listener_id++;
172   } while (!id_is_free (listener->id));
173   new_list = g_list_append (device_listeners, listener);
174   if (new_list) device_listeners = new_list;
175 }
176
177 static void
178 atspi_device_listener_finalize (GObject *object)
179 {
180   AtspiDeviceListener *listener = (AtspiDeviceListener *) object;
181   GList *l;
182   
183   for (l = listener->callbacks; l; l = l->next)
184     {
185       device_event_handler_free (l->data);
186     }
187   
188   g_list_free (listener->callbacks);
189
190   device_parent_class->finalize (object);
191 }
192
193 static void
194 atspi_device_listener_class_init (AtspiDeviceListenerClass *klass)
195 {
196   GObjectClass *object_class = (GObjectClass *) klass;
197
198   device_parent_class = g_type_class_peek_parent (klass);
199   object_class->finalize = atspi_device_listener_finalize;
200
201   klass->device_event = atspi_device_event_dispatch;
202 }
203
204 G_DEFINE_TYPE (AtspiDeviceListener, atspi_device_listener,
205                           G_TYPE_OBJECT)
206
207 /**
208  * atspi_device_listener_new:
209  * @callback: (scope notified): an #AtspiDeviceListenerCB callback function,
210  *            or NULL.
211  * @user_data: (closure): a pointer to data which will be passed to the
212  * callback when invoked.
213  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
214  * and data associated with the callback should be freed. It can be NULL.
215  *
216  * Creates a new #AtspiDeviceListener with a specified callback function.
217  *
218  * Returns: (transfer full): a pointer to a newly-created #AtspiDeviceListener.
219  *
220  **/
221 AtspiDeviceListener *
222 atspi_device_listener_new (AtspiDeviceListenerCB callback,
223                            void *user_data,
224                            GDestroyNotify callback_destroyed)
225 {
226   AtspiDeviceListener *listener = g_object_new (atspi_device_listener_get_type (), NULL);
227
228   if (callback)
229     atspi_device_listener_add_callback (listener, callback, callback_destroyed,
230                                        user_data);
231   return listener;
232 }
233
234 /**
235  * atspi_device_listener_new_simple:
236  * @callback: (scope notified): an #AtspiDeviceListenerCB callback function,
237  *            or NULL.
238  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
239  * and data associated with the callback should be freed.  It an be NULL.
240  *
241  * Creates a new #AtspiDeviceListener with a specified callback function.
242  * This method is similar to #atspi_device_listener_new, but callback
243  * takes no user data.
244  *
245  * Returns: a pointer to a newly-created #AtspiDeviceListener.
246  *
247  **/
248 AtspiDeviceListener *
249 atspi_device_listener_new_simple (AtspiDeviceListenerSimpleCB callback,
250                            GDestroyNotify callback_destroyed)
251 {
252   return atspi_device_listener_new (device_remove_datum, callback_destroyed, callback);
253 }
254
255 /**
256  * atspi_device_listener_add_callback:
257  * @listener: the #AtspiDeviceListener instance to modify.
258  * @callback: (scope notified): an #AtspiDeviceListenerCB function pointer.
259  * @callback_destroyed: A #GDestroyNotify called when the listener is freed
260  * and data associated with the callback should be freed. It can be NULL.
261  * @user_data: (closure): a pointer to data which will be passed to the
262  *             callback when invoked. 
263  *
264  * Adds an in-process callback function to an existing #AtspiDeviceListener.
265  *
266  * Returns: #TRUE if successful, otherwise #FALSE.
267  *
268  **/
269 void
270 atspi_device_listener_add_callback (AtspiDeviceListener  *listener,
271                              AtspiDeviceListenerCB callback,
272                              GDestroyNotify callback_destroyed,
273                              void                      *user_data)
274 {
275   g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
276   DeviceEventHandler *new_handler;
277
278   new_handler = device_event_handler_new (callback,
279                                           callback_destroyed, user_data);
280
281   if (new_handler)
282   {
283     GList *new_list;
284     new_list = g_list_prepend (listener->callbacks, new_handler);
285     if (new_list)
286       listener->callbacks = new_list;
287   }
288 }
289
290 /**
291  * atspi_device_listener_remove_callback:
292  * @listener: the #AtspiDeviceListener instance to modify.
293  * @callback: (scope call): an #AtspiDeviceListenerCB function pointer.
294  *
295  * Removes an in-process callback function from an existing 
296  * #AtspiDeviceListener.
297  *
298  * Returns: #TRUE if successful, otherwise #FALSE.
299  *
300  **/
301 void
302 atspi_device_listener_remove_callback (AtspiDeviceListener  *listener,
303                                 AtspiDeviceListenerCB callback)
304 {
305   g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
306
307   listener->callbacks = event_list_remove_by_cb (listener->callbacks, (void *) callback);
308 }
309
310 static void
311 read_device_event_from_iter (DBusMessageIter *iter, AtspiDeviceEvent *event)
312 {
313   dbus_uint32_t type;
314   dbus_int32_t id;
315   dbus_int32_t hw_code;
316   dbus_int32_t modifiers;
317   dbus_int32_t timestamp;
318   dbus_bool_t is_text;
319   DBusMessageIter iter_struct;
320
321   dbus_message_iter_recurse (iter, &iter_struct);
322
323   dbus_message_iter_get_basic (&iter_struct, &type);
324   event->type = type;
325   dbus_message_iter_next (&iter_struct);
326
327   dbus_message_iter_get_basic (&iter_struct, &id);
328   event->id = id;
329   dbus_message_iter_next (&iter_struct);
330
331   /* TODO: Remove cast from next two on ABI break */
332   dbus_message_iter_get_basic (&iter_struct, &hw_code);
333   event->hw_code = (gushort) hw_code;
334   dbus_message_iter_next (&iter_struct);
335
336   dbus_message_iter_get_basic (&iter_struct, &modifiers);
337   event->modifiers = (gushort) modifiers;
338   dbus_message_iter_next (&iter_struct);
339
340   dbus_message_iter_get_basic (&iter_struct, &timestamp);
341   event->timestamp = timestamp;
342   dbus_message_iter_next (&iter_struct);
343
344   dbus_message_iter_get_basic (&iter_struct, &event->event_string);
345   dbus_message_iter_next (&iter_struct);
346
347   dbus_message_iter_get_basic (&iter_struct, &is_text);
348   event->is_text = is_text;
349 }
350
351 DBusHandlerResult
352 _atspi_dbus_handle_DeviceEvent (DBusConnection *bus, DBusMessage *message, void *data)
353 {
354   const char *path = dbus_message_get_path (message);
355   int id;
356   AtspiDeviceEvent event;
357     AtspiDeviceListener *listener;
358   DBusMessageIter iter;
359   AtspiDeviceListenerClass *klass;
360   dbus_bool_t retval = FALSE;
361   GList *l;
362   DBusMessage *reply;
363
364   if (strcmp (dbus_message_get_signature (message), "(uiuuisb)") != 0)
365   {
366     g_warning ("Atspi: Unknown signature for an event");
367     goto done;
368   }
369
370   if (sscanf (path, "/org/a11y/atspi/listeners/%d", &id) != 1)
371   {
372     g_warning ("Atspi: Bad listener path: %s\n", path);
373     goto done;
374   }
375
376   for (l = device_listeners; l; l = g_list_next (l))
377   {
378     listener = l->data;
379     if (listener->id == id) break;
380   }
381
382   if (!l)
383   {
384     goto done;
385   }
386   dbus_message_iter_init (message, &iter);
387   read_device_event_from_iter (&iter, &event);
388   klass = ATSPI_DEVICE_LISTENER_GET_CLASS (listener);
389   if (klass->device_event)
390   {
391     retval = (*klass->device_event) (listener, &event);
392   }
393 done:
394   reply = dbus_message_new_method_return (message);
395   if (reply)
396   {
397     dbus_message_append_args (reply, DBUS_TYPE_BOOLEAN, &retval, DBUS_TYPE_INVALID);
398     dbus_connection_send (_atspi_bus(), reply, NULL);
399     dbus_message_unref (reply);
400   }
401   return DBUS_HANDLER_RESULT_HANDLED;
402 }
403
404 gchar *
405 _atspi_device_listener_get_path (AtspiDeviceListener *listener)
406 {  return g_strdup_printf ("/org/a11y/atspi/listeners/%d", listener->id);
407 }
408
409 G_DEFINE_BOXED_TYPE (AtspiDeviceEvent,
410                      atspi_device_event,
411                      atspi_device_event_copy,
412                      atspi_device_event_free)