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 Lesser General Public
11
 * License as published by the Free Software Foundation; either
12
 * version 2.1 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
 * Lesser General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Lesser General Public
20
 * License along with this library; if not, write to the
21
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22
 * Boston, MA 02110-1301, USA.
23
 */
24

            
25
#include "atspi-private.h"
26
#include <stdio.h>
27

            
28
/**
29
 * AtspiDevicelistener:
30
 *
31
 * An interface for creating and manipulating
32
 * device listeners.
33
 *
34
 * An interface for creating and manipulating
35
 * device listeners with callback functions.
36
 */
37

            
38
typedef struct
39
{
40
  AtspiDeviceListenerCB callback;
41
  gpointer user_data;
42
  GDestroyNotify callback_destroyed;
43
} DeviceEventHandler;
44

            
45
GObjectClass *device_listener_parent_class;
46

            
47
/*
48
 * Misc. helpers.
49
 */
50

            
51
static DeviceEventHandler *
52
device_event_handler_new (AtspiDeviceListenerCB callback,
53
                          GDestroyNotify callback_destroyed,
54
                          gpointer user_data)
55
{
56
  DeviceEventHandler *eh = g_new0 (DeviceEventHandler, 1);
57

            
58
  eh->callback = callback;
59
  eh->callback_destroyed = callback_destroyed;
60
  eh->user_data = user_data;
61

            
62
  return eh;
63
}
64

            
65
static gboolean
66
device_remove_datum (AtspiDeviceEvent *event, void *user_data)
67
{
68
  AtspiDeviceListenerSimpleCB cb = user_data;
69
  return cb (event);
70
}
71

            
72
static void
73
device_event_handler_free (DeviceEventHandler *eh)
74
{
75
#if 0
76
  /* TODO; Test this; it will probably crash with pyatspi for unknown reasons */
77
  if (eh->callback_destroyed)
78
  {
79
    gpointer rea_callback = (eh->callback == device_remove_datum ?
80
                            eh->user_data : eh->callback);
81
    (*eh->callback_destroyed) (real_callback);
82
  }
83
#endif
84
  g_free (eh);
85
}
86

            
87
static GList *
88
event_list_remove_by_cb (GList *list, AtspiDeviceListenerCB callback)
89
{
90
  GList *l, *next;
91

            
92
  for (l = list; l; l = next)
93
    {
94
      DeviceEventHandler *eh = l->data;
95
      next = l->next;
96

            
97
      if (eh->callback == callback)
98
        {
99
          list = g_list_delete_link (list, l);
100
          device_event_handler_free (eh);
101
        }
102
    }
103

            
104
  return list;
105
}
106

            
107
/*
108
 * Standard event dispatcher
109
 */
110

            
111
static guint listener_id = 0;
112
static GList *device_listeners = NULL;
113

            
114
static gboolean
115
id_is_free (guint id)
116
{
117
  GList *l;
118

            
119
  for (l = device_listeners; l; l = g_list_next (l))
120
    {
121
      AtspiDeviceListener *listener = l->data;
122
      if (listener->id == id)
123
        return FALSE;
124
    }
125
  return TRUE;
126
}
127

            
128
static AtspiDeviceEvent *
129
atspi_device_event_copy (const AtspiDeviceEvent *src)
130
{
131
  AtspiDeviceEvent *dst = g_new0 (AtspiDeviceEvent, 1);
132
  dst->type = src->type;
133
  dst->id = src->id;
134
  dst->hw_code = src->hw_code;
135
  dst->modifiers = src->modifiers;
136
  dst->timestamp = src->timestamp;
137
  if (src->event_string)
138
    dst->event_string = g_strdup (src->event_string);
139
  dst->is_text = src->is_text;
140
  return dst;
141
}
142

            
143
void
144
atspi_device_event_free (AtspiDeviceEvent *event)
145
{
146
  if (event->event_string)
147
    g_free (event->event_string);
148
  g_free (event);
149
}
150

            
151
/*
152
 * Device event handler
153
 */
154
static gboolean
155
atspi_device_event_dispatch (AtspiDeviceListener *listener,
156
                             const AtspiDeviceEvent *event)
157
{
158
  GList *l;
159
  gboolean handled = FALSE;
160

            
161
  /* FIXME: re-enterancy hazard on this list */
162
  for (l = listener->callbacks; l; l = l->next)
163
    {
164
      DeviceEventHandler *eh = l->data;
165

            
166
      if ((handled = eh->callback (atspi_device_event_copy (event), eh->user_data)))
167
        {
168
          break;
169
        }
170
    }
171

            
172
  return handled;
173
}
174

            
175
static void
176
atspi_device_listener_init (AtspiDeviceListener *listener)
177
{
178

            
179
  do
180
    {
181
      listener->id = listener_id++;
182
    }
183
  while (!id_is_free (listener->id));
184
  device_listeners = g_list_append (device_listeners, listener);
185
}
186

            
187
static void
188
atspi_device_listener_finalize (GObject *object)
189
{
190
  AtspiDeviceListener *listener = (AtspiDeviceListener *) object;
191
  GList *l;
192

            
193
  device_listeners = g_list_remove (device_listeners, listener);
194

            
195
  for (l = listener->callbacks; l; l = l->next)
196
    {
197
      device_event_handler_free (l->data);
198
    }
199

            
200
  g_list_free (listener->callbacks);
201

            
202
  device_listener_parent_class->finalize (object);
203
}
204

            
205
static void
206
atspi_device_listener_class_init (AtspiDeviceListenerClass *klass)
207
{
208
  GObjectClass *object_class = (GObjectClass *) klass;
209

            
210
  device_listener_parent_class = g_type_class_peek_parent (klass);
211
  object_class->finalize = atspi_device_listener_finalize;
212

            
213
  klass->device_event = atspi_device_event_dispatch;
214
}
215

            
216
G_DEFINE_TYPE (AtspiDeviceListener, atspi_device_listener, G_TYPE_OBJECT)
217

            
218
/**
219
 * atspi_device_listener_new:
220
 * @callback: (scope notified): an #AtspiDeviceListenerCB callback function,
221
 *            or NULL.
222
 * @user_data: (closure): a pointer to data which will be passed to the
223
 * callback when invoked.
224
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
225
 * and data associated with the callback should be freed. It can be NULL.
226
 *
227
 * Creates a new #AtspiDeviceListener with a specified callback function.
228
 *
229
 * Returns: (transfer full): a pointer to a newly-created #AtspiDeviceListener.
230
 *
231
 **/
232
AtspiDeviceListener *
233
atspi_device_listener_new (AtspiDeviceListenerCB callback,
234
                           void *user_data,
235
                           GDestroyNotify callback_destroyed)
236
{
237
  AtspiDeviceListener *listener = g_object_new (atspi_device_listener_get_type (), NULL);
238

            
239
  if (callback)
240
    atspi_device_listener_add_callback (listener, callback, callback_destroyed,
241
                                        user_data);
242
  return listener;
243
}
244

            
245
/**
246
 * atspi_device_listener_new_simple: (skip)
247
 * @callback: (scope notified): an #AtspiDeviceListenerCB callback function,
248
 *            or NULL.
249
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
250
 * and data associated with the callback should be freed.  It can be NULL.
251
 *
252
 * Creates a new #AtspiDeviceListener with a specified callback function.
253
 * This method is similar to #atspi_device_listener_new, but callback
254
 * takes no user data.
255
 *
256
 * Returns: a pointer to a newly-created #AtspiDeviceListener.
257
 *
258
 **/
259
AtspiDeviceListener *
260
atspi_device_listener_new_simple (AtspiDeviceListenerSimpleCB callback,
261
                                  GDestroyNotify callback_destroyed)
262
{
263
  return atspi_device_listener_new (device_remove_datum, callback, callback_destroyed);
264
}
265

            
266
/**
267
 * atspi_device_listener_add_callback:
268
 * @listener: the #AtspiDeviceListener instance to modify.
269
 * @callback: (scope notified): an #AtspiDeviceListenerCB function pointer.
270
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
271
 * and data associated with the callback should be freed. It can be NULL.
272
 * @user_data: (closure): a pointer to data which will be passed to the
273
 *             callback when invoked.
274
 *
275
 * Adds an in-process callback function to an existing #AtspiDeviceListener.
276
 *
277
 **/
278
void
279
atspi_device_listener_add_callback (AtspiDeviceListener *listener,
280
                                    AtspiDeviceListenerCB callback,
281
                                    GDestroyNotify callback_destroyed,
282
                                    void *user_data)
283
{
284
  g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
285
  DeviceEventHandler *new_handler;
286

            
287
  new_handler = device_event_handler_new (callback,
288
                                          callback_destroyed, user_data);
289

            
290
  listener->callbacks = g_list_prepend (listener->callbacks, new_handler);
291
}
292

            
293
/**
294
 * atspi_device_listener_remove_callback:
295
 * @listener: the #AtspiDeviceListener instance to modify.
296
 * @callback: (scope call): an #AtspiDeviceListenerCB function pointer.
297
 *
298
 * Removes an in-process callback function from an existing
299
 * #AtspiDeviceListener.
300
 *
301
 **/
302
void
303
atspi_device_listener_remove_callback (AtspiDeviceListener *listener,
304
                                       AtspiDeviceListenerCB callback)
305
{
306
  g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
307

            
308
  listener->callbacks = event_list_remove_by_cb (listener->callbacks, (void *) callback);
309
}
310

            
311
static void
312
read_device_event_from_iter (DBusMessageIter *iter, AtspiDeviceEvent *event)
313
{
314
  dbus_uint32_t type;
315
  dbus_int32_t id;
316
  dbus_int32_t hw_code;
317
  dbus_int32_t modifiers;
318
  dbus_int32_t timestamp;
319
  dbus_bool_t is_text;
320
  DBusMessageIter iter_struct;
321

            
322
  dbus_message_iter_recurse (iter, &iter_struct);
323

            
324
  dbus_message_iter_get_basic (&iter_struct, &type);
325
  event->type = type;
326
  dbus_message_iter_next (&iter_struct);
327

            
328
  dbus_message_iter_get_basic (&iter_struct, &id);
329
  event->id = id;
330
  dbus_message_iter_next (&iter_struct);
331

            
332
  /* TODO: Remove cast from next two on ABI break */
333
  dbus_message_iter_get_basic (&iter_struct, &hw_code);
334
  event->hw_code = (gushort) hw_code;
335
  dbus_message_iter_next (&iter_struct);
336

            
337
  dbus_message_iter_get_basic (&iter_struct, &modifiers);
338
  event->modifiers = (gushort) modifiers;
339
  dbus_message_iter_next (&iter_struct);
340

            
341
  dbus_message_iter_get_basic (&iter_struct, &timestamp);
342
  event->timestamp = timestamp;
343
  dbus_message_iter_next (&iter_struct);
344

            
345
  dbus_message_iter_get_basic (&iter_struct, &event->event_string);
346
  dbus_message_iter_next (&iter_struct);
347

            
348
  dbus_message_iter_get_basic (&iter_struct, &is_text);
349
  event->is_text = is_text;
350
}
351

            
352
DBusHandlerResult
353
_atspi_dbus_handle_DeviceEvent (DBusConnection *bus, DBusMessage *message)
354
{
355
  const char *path = dbus_message_get_path (message);
356
  int id;
357
  AtspiDeviceEvent event;
358
  AtspiDeviceListener *listener;
359
  DBusMessageIter iter;
360
  AtspiDeviceListenerClass *klass;
361
  dbus_bool_t retval = FALSE;
362
  GList *l;
363
  DBusMessage *reply;
364

            
365
  if (strcmp (dbus_message_get_signature (message), "(uiuuisb)") != 0)
366
    {
367
      g_warning ("AT-SPI: Unknown signature for an event");
368
      goto done;
369
    }
370

            
371
  if (sscanf (path, "/org/a11y/atspi/listeners/%d", &id) != 1)
372
    {
373
      g_warning ("AT-SPI: Bad listener path: %s\n", path);
374
      goto done;
375
    }
376

            
377
  for (l = device_listeners; l; l = g_list_next (l))
378
    {
379
      listener = l->data;
380
      if (listener->id == id)
381
        break;
382
    }
383

            
384
  if (!l)
385
    {
386
      goto done;
387
    }
388
  dbus_message_iter_init (message, &iter);
389
  read_device_event_from_iter (&iter, &event);
390
  klass = ATSPI_DEVICE_LISTENER_GET_CLASS (listener);
391
  if (klass->device_event)
392
    {
393
      retval = (*klass->device_event) (listener, &event);
394
      if (retval != 0 && retval != 1)
395
        {
396
          g_warning ("AT-SPI: device event handler returned %d; should be 0 or 1", retval);
397
          retval = 0;
398
        }
399
    }
400
done:
401
  reply = dbus_message_new_method_return (message);
402
  if (reply)
403
    {
404
      dbus_message_append_args (reply, DBUS_TYPE_BOOLEAN, &retval, DBUS_TYPE_INVALID);
405
      dbus_connection_send (_atspi_bus (), reply, NULL);
406
      dbus_message_unref (reply);
407
    }
408
  return DBUS_HANDLER_RESULT_HANDLED;
409
}
410

            
411
gchar *
412
_atspi_device_listener_get_path (AtspiDeviceListener *listener)
413
{
414
  return g_strdup_printf ("/org/a11y/atspi/listeners/%d", listener->id);
415
}
416

            
417
G_DEFINE_BOXED_TYPE (AtspiDeviceEvent,
418
                     atspi_device_event,
419
                     atspi_device_event_copy,
420
                     atspi_device_event_free)