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) (closure user_data) (destroy callback_destroyed) (nullable): an
221
 *   #AtspiDeviceListenerCB callback function, or NULL.
222
 * @user_data: 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) (closure user_data) (destroy callback_destroyed): an
270
 *   #AtspiDeviceListenerCB function pointer.
271
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
272
 *   and data associated with the callback should be freed. It can be NULL.
273
 * @user_data: a pointer to data which will be passed to the
274
 *   callback when invoked.
275
 *
276
 * Adds an in-process callback function to an existing #AtspiDeviceListener.
277
 *
278
 **/
279
void
280
atspi_device_listener_add_callback (AtspiDeviceListener *listener,
281
                                    AtspiDeviceListenerCB callback,
282
                                    GDestroyNotify callback_destroyed,
283
                                    void *user_data)
284
{
285
  g_return_if_fail (ATSPI_IS_DEVICE_LISTENER (listener));
286
  DeviceEventHandler *new_handler;
287

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

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

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

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

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

            
323
  dbus_message_iter_recurse (iter, &iter_struct);
324

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

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

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

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

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

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

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

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

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

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

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

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

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

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