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-accessible-private.h"
26
#include "atspi-private.h"
27
#include <ctype.h>
28
#include <string.h>
29
#include <strings.h>
30

            
31
/**
32
 * AtspiEventListener:
33
 *
34
 * A generic interface implemented by objects for the receipt of event
35
 * notifications.
36
 *
37
 * A generic interface implemented by objects for the receipt of event
38
 * notifications. atspi-event-listener is the interface via which clients of
39
 * the atspi-registry receive notification of changes to an application's user
40
 * interface and content.
41
 */
42

            
43
typedef struct
44
{
45
  AtspiEventListenerCB callback;
46
  void *user_data;
47
  GDestroyNotify callback_destroyed;
48
  char *event_type;
49
  char *category;
50
  char *name;
51
  char *detail;
52
  GArray *properties;
53
  AtspiAccessible *app;
54
} EventListenerEntry;
55

            
56
2
G_DEFINE_TYPE (AtspiEventListener, atspi_event_listener, G_TYPE_OBJECT)
57

            
58
void
59
2
atspi_event_listener_init (AtspiEventListener *listener)
60
{
61
2
}
62

            
63
void
64
2
atspi_event_listener_class_init (AtspiEventListenerClass *klass)
65
{
66
2
}
67

            
68
static void
69
remove_datum (AtspiEvent *event, void *user_data)
70
{
71
  AtspiEventListenerSimpleCB cb = user_data;
72
  cb (event);
73
}
74

            
75
typedef struct
76
{
77
  gpointer callback;
78
  GDestroyNotify callback_destroyed;
79
  gint ref_count;
80
} CallbackInfo;
81
static GHashTable *callbacks;
82

            
83
void
84
4
callback_ref (void *callback, GDestroyNotify callback_destroyed)
85
{
86
  CallbackInfo *info;
87

            
88
4
  if (!callbacks)
89
    {
90
2
      callbacks = g_hash_table_new (g_direct_hash, g_direct_equal);
91
2
      if (!callbacks)
92
        return;
93
    }
94

            
95
4
  info = g_hash_table_lookup (callbacks, callback);
96
4
  if (!info)
97
    {
98
2
      info = g_new (CallbackInfo, 1);
99
2
      info->callback = callback;
100
2
      info->callback_destroyed = callback_destroyed;
101
2
      info->ref_count = 1;
102
2
      g_hash_table_insert (callbacks, callback, info);
103
    }
104
  else
105
2
    info->ref_count++;
106
}
107

            
108
void
109
callback_unref (gpointer callback)
110
{
111
  CallbackInfo *info;
112

            
113
  if (!callbacks)
114
    return;
115
  info = g_hash_table_lookup (callbacks, callback);
116
  if (!info)
117
    {
118
      g_warning ("AT-SPI: Dereferencing invalid callback %p\n", callback);
119
      return;
120
    }
121
  info->ref_count--;
122
  if (info->ref_count == 0)
123
    {
124
#if 0
125
    /* TODO: Figure out why this seg faults from Python */
126
    if (info->callback_destroyed)
127
      (*info->callback_destroyed) (info->callback);
128
#endif
129
      g_free (info);
130
      g_hash_table_remove (callbacks, callback);
131
    }
132
}
133

            
134
/**
135
 * atspi_event_listener_new:
136
 * @callback: (scope notified): An #AtspiEventListenerCB to be called
137
 * when an event is fired.
138
 * @user_data: (closure): data to pass to the callback.
139
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
140
 * and data associated with the callback should be freed.  Can be NULL.
141
 *
142
 * Creates a new #AtspiEventListener associated with a specified @callback.
143
 *
144
 * Returns: (transfer full): A new #AtspiEventListener.
145
 */
146
AtspiEventListener *
147
2
atspi_event_listener_new (AtspiEventListenerCB callback,
148
                          gpointer user_data,
149
                          GDestroyNotify callback_destroyed)
150
{
151
2
  AtspiEventListener *listener = g_object_new (ATSPI_TYPE_EVENT_LISTENER, NULL);
152
2
  listener->callback = callback;
153
2
  callback_ref (callback, callback_destroyed);
154
2
  listener->user_data = user_data;
155
2
  listener->cb_destroyed = callback_destroyed;
156
2
  return listener;
157
}
158

            
159
/**
160
 * atspi_event_listener_new_simple: (skip)
161
 * @callback: (scope notified): An #AtspiEventListenerSimpleCB to be called
162
 * when an event is fired.
163
 * @callback_destroyed: A #GDestroyNotify called when the listener is freed
164
 * and data associated with the callback should be freed.  Can be NULL.
165
 *
166
 * Creates a new #AtspiEventListener associated with a specified @callback.
167
 * Returns: (transfer full): A new #AtspiEventListener.
168
 **/
169
AtspiEventListener *
170
atspi_event_listener_new_simple (AtspiEventListenerSimpleCB callback,
171
                                 GDestroyNotify callback_destroyed)
172
{
173
  AtspiEventListener *listener = g_object_new (ATSPI_TYPE_EVENT_LISTENER, NULL);
174
  listener->callback = remove_datum;
175
  callback_ref (remove_datum, callback_destroyed);
176
  listener->user_data = callback;
177
  listener->cb_destroyed = callback_destroyed;
178
  return listener;
179
}
180

            
181
static GList *event_listeners = NULL;
182
static GList *pending_removals = NULL;
183
static int in_send = 0;
184

            
185
static gchar *
186
969
convert_name_from_dbus (const char *name, gboolean path_hack)
187
{
188
  gchar *ret;
189
969
  const char *p = name;
190
  gchar *q;
191

            
192
969
  if (!name)
193
    return g_strdup ("");
194

            
195
969
  ret = g_malloc (g_utf8_strlen (name, -1) * 2 + 1);
196
969
  q = ret;
197

            
198
9204
  while (*p)
199
    {
200
8235
      if (isupper (*p))
201
        {
202
969
          if (q > ret)
203
323
            *q++ = '-';
204
969
          *q++ = tolower (*p++);
205
        }
206
7266
      else if (path_hack && *p == '/')
207
        {
208
          *q++ = ':';
209
          p++;
210
        }
211
      else
212
7266
        *q++ = *p++;
213
    }
214
969
  *q = '\0';
215
969
  return ret;
216
}
217

            
218
static void
219
323
cache_process_children_changed (AtspiEvent *event)
220
{
221
  AtspiAccessible *child;
222

            
223
323
  if (!G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE) ||
224
323
      !(event->source->cached_properties & ATSPI_CACHE_CHILDREN) ||
225
      atspi_state_set_contains (event->source->states, ATSPI_STATE_MANAGES_DESCENDANTS))
226
323
    return;
227

            
228
  child = g_value_get_object (&event->any_data);
229
  if (child == NULL)
230
    return;
231

            
232
  if (!strncmp (event->type, "object:children-changed:add", 27))
233
    {
234
      g_ptr_array_remove (event->source->children, child); /* just to be safe */
235
      if (event->detail1 < 0 || event->detail1 > event->source->children->len)
236
        {
237
          event->source->cached_properties &= ~ATSPI_CACHE_CHILDREN;
238
          return;
239
        }
240
      /* Unfortunately, there's no g_ptr_array_insert or similar */
241
      g_ptr_array_add (event->source->children, NULL);
242
      memmove (event->source->children->pdata + event->detail1 + 1,
243
               event->source->children->pdata + event->detail1,
244
               (event->source->children->len - event->detail1 - 1) * sizeof (gpointer));
245
      g_ptr_array_index (event->source->children, event->detail1) = g_object_ref (child);
246
    }
247
  else
248
    {
249
      g_ptr_array_remove (event->source->children, child);
250
      if (child == child->parent.app->root)
251
        g_object_run_dispose (G_OBJECT (child->parent.app));
252
    }
253
}
254

            
255
static void
256
cache_process_property_change (AtspiEvent *event)
257
{
258
  if (!strcmp (event->type, "object:property-change:accessible-parent"))
259
    {
260
      if (event->source->accessible_parent)
261
        g_object_unref (event->source->accessible_parent);
262
      if (G_VALUE_HOLDS (&event->any_data, ATSPI_TYPE_ACCESSIBLE))
263
        {
264
          event->source->accessible_parent = g_value_dup_object (&event->any_data);
265
          _atspi_accessible_add_cache (event->source, ATSPI_CACHE_PARENT);
266
        }
267
      else
268
        {
269
          event->source->accessible_parent = NULL;
270
          event->source->cached_properties &= ~ATSPI_CACHE_PARENT;
271
        }
272
    }
273
  else if (!strcmp (event->type, "object:property-change:accessible-name"))
274
    {
275
      if (event->source->name)
276
        g_free (event->source->name);
277
      if (G_VALUE_HOLDS_STRING (&event->any_data))
278
        {
279
          event->source->name = g_value_dup_string (&event->any_data);
280
          _atspi_accessible_add_cache (event->source, ATSPI_CACHE_NAME);
281
        }
282
      else
283
        {
284
          event->source->name = NULL;
285
          event->source->cached_properties &= ~ATSPI_CACHE_NAME;
286
        }
287
    }
288
  else if (!strcmp (event->type, "object:property-change:accessible-description"))
289
    {
290
      if (event->source->description)
291
        g_free (event->source->description);
292
      if (G_VALUE_HOLDS_STRING (&event->any_data))
293
        {
294
          event->source->description = g_value_dup_string (&event->any_data);
295
          _atspi_accessible_add_cache (event->source, ATSPI_CACHE_DESCRIPTION);
296
        }
297
      else
298
        {
299
          event->source->description = NULL;
300
          event->source->cached_properties &= ~ATSPI_CACHE_DESCRIPTION;
301
        }
302
    }
303
  else if (!strcmp (event->type, "object:property-change:accessible-role"))
304
    {
305
      if (G_VALUE_HOLDS_INT (&event->any_data))
306
        {
307
          event->source->role = g_value_get_int (&event->any_data);
308
          _atspi_accessible_add_cache (event->source, ATSPI_CACHE_ROLE);
309
        }
310
      else
311
        {
312
          event->source->cached_properties &= ~ATSPI_CACHE_ROLE;
313
        }
314
    }
315
}
316

            
317
static void
318
cache_process_state_changed (AtspiEvent *event)
319
{
320
  if (event->source->states)
321
    atspi_state_set_set_by_name (event->source->states, event->type + 21,
322
                                 event->detail1);
323
}
324

            
325
static void
326
cache_process_attributes_changed (AtspiEvent *event)
327
{
328
  const gchar *name = NULL, *value;
329

            
330
  if (!event->source->attributes)
331
    return;
332

            
333
  if (event->type[25] == ':')
334
    name = event->type + 26;
335
  value = g_value_get_string (&event->any_data);
336

            
337
  if (name && name[0] && value && value[0])
338
    {
339
      g_hash_table_remove (event->source->attributes, name);
340
      g_hash_table_insert (event->source->attributes, g_strdup (name), g_strdup (value));
341
    }
342
  else
343
    {
344
      g_clear_pointer (&event->source->attributes, g_hash_table_unref);
345
      event->source->attributes = NULL;
346
    }
347
}
348

            
349
static dbus_bool_t
350
323
demarshal_rect (DBusMessageIter *iter, AtspiRect *rect)
351
{
352
  dbus_int32_t x, y, width, height;
353
  DBusMessageIter iter_struct;
354

            
355
323
  dbus_message_iter_recurse (iter, &iter_struct);
356
323
  if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32)
357
323
    return FALSE;
358
  dbus_message_iter_get_basic (&iter_struct, &x);
359
  dbus_message_iter_next (&iter_struct);
360
  if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32)
361
    return FALSE;
362
  dbus_message_iter_get_basic (&iter_struct, &y);
363
  dbus_message_iter_next (&iter_struct);
364
  if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32)
365
    return FALSE;
366
  dbus_message_iter_get_basic (&iter_struct, &width);
367
  dbus_message_iter_next (&iter_struct);
368
  if (dbus_message_iter_get_arg_type (&iter_struct) != DBUS_TYPE_INT32)
369
    return FALSE;
370
  dbus_message_iter_get_basic (&iter_struct, &height);
371
  rect->x = x;
372
  rect->y = y;
373
  rect->width = width;
374
  rect->height = height;
375
  return TRUE;
376
}
377

            
378
static gboolean
379
3540
convert_event_type_to_dbus (const char *eventType, char **categoryp, char **namep, char **detailp, AtspiAccessible *app, GPtrArray **matchrule_array)
380
{
381
3540
  gchar *tmp = _atspi_strdup_and_adjust_for_dbus (eventType);
382
3540
  char *category = NULL, *name = NULL, *detail = NULL;
383
3540
  char *saveptr = NULL;
384

            
385
3540
  if (tmp == NULL)
386
    return FALSE;
387
3540
  category = strtok_r (tmp, ":", &saveptr);
388
3540
  if (category)
389
3540
    category = g_strdup (category);
390
3540
  name = strtok_r (NULL, ":", &saveptr);
391
3540
  if (name)
392
    {
393
3540
      name = g_strdup (name);
394
3540
      detail = strtok_r (NULL, ":", &saveptr);
395
3540
      if (detail)
396
3538
        detail = g_strdup (detail);
397
    }
398
3540
  if (matchrule_array)
399
    {
400
      gchar *matchrule;
401
2
      (*matchrule_array) = g_ptr_array_new ();
402
2
      matchrule = g_strdup_printf ("type='signal',interface='org.a11y.atspi.Event.%s'", category);
403
2
      if (app)
404
        {
405
          gchar *new_str = g_strconcat (matchrule, ",sender='",
406
                                        app->parent.app->bus_name, "'",
407
                                        NULL);
408
          g_free (matchrule);
409
          matchrule = new_str;
410
        }
411
2
      if (name && name[0])
412
        {
413
2
          gchar *new_str = g_strconcat (matchrule, ",member='", name, "'", NULL);
414
2
          g_free (matchrule);
415
2
          matchrule = new_str;
416
        }
417
2
      if (detail && detail[0])
418
        {
419
          gchar *new_str = g_strconcat (matchrule, ",arg0='", detail, "'", NULL);
420
          g_ptr_array_add (*matchrule_array, new_str);
421
          new_str = g_strconcat (matchrule, ",arg0path='", detail, "/'", NULL);
422
          g_ptr_array_add (*matchrule_array, new_str);
423
          g_free (matchrule);
424
        }
425
      else
426
2
        g_ptr_array_add (*matchrule_array, matchrule);
427
    }
428
3540
  if (categoryp)
429
3540
    *categoryp = category;
430
  else
431
    g_free (category);
432
3540
  if (namep)
433
3540
    *namep = name;
434
  else if (name)
435
    g_free (name);
436
3540
  if (detailp)
437
3540
    *detailp = detail;
438
  else if (detail)
439
    g_free (detail);
440
3540
  g_free (tmp);
441
3540
  return TRUE;
442
}
443

            
444
static void
445
listener_entry_free (EventListenerEntry *e)
446
{
447
  gpointer callback = (e->callback == remove_datum ? (gpointer) e->user_data : (gpointer) e->callback);
448
  g_free (e->event_type);
449
  g_free (e->category);
450
  g_free (e->name);
451
  if (e->detail)
452
    g_free (e->detail);
453
  callback_unref (callback);
454

            
455
  for (int i = 0; i < e->properties->len; i++)
456
    g_free (g_array_index (e->properties, char *, i));
457

            
458
  g_array_free (e->properties, TRUE);
459

            
460
  if (e->app)
461
    g_object_unref (e->app);
462

            
463
  g_free (e);
464
}
465

            
466
/**
467
 * atspi_event_listener_register:
468
 * @listener: The #AtspiEventListener to register against an event type.
469
 * @event_type: a character string indicating the type of events for which
470
 *            notification is requested.  Format is
471
 *            EventClass:major_type:minor_type:detail
472
 *            where all subfields other than EventClass are optional.
473
 *            EventClasses include "object", "window", "mouse",
474
 *            and toolkit events (e.g. "Gtk", "AWT").
475
 *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
476
 *
477
 * Adds an in-process callback function to an existing #AtspiEventListener.
478
 *
479
 * Legal object event types:
480
 *
481
 *    (property change events)
482
 *
483
 *            object:property-change
484
 *            object:property-change:accessible-name
485
 *            object:property-change:accessible-description
486
 *            object:property-change:accessible-parent
487
 *            object:property-change:accessible-value
488
 *            object:property-change:accessible-role
489
 *            object:property-change:accessible-table-caption
490
 *            object:property-change:accessible-table-column-description
491
 *            object:property-change:accessible-table-column-header
492
 *            object:property-change:accessible-table-row-description
493
 *            object:property-change:accessible-table-row-header
494
 *            object:property-change:accessible-table-summary
495
 *
496
 *    (other object events)
497
 *
498
 *            object:state-changed
499
 *            object:children-changed
500
 *            object:visible-data-changed
501
 *            object:selection-changed
502
 *            object:text-selection-changed
503
 *            object:text-changed
504
 *            object:text-caret-moved
505
 *            object:row-inserted
506
 *            object:row-reordered
507
 *            object:row-deleted
508
 *            object:column-inserted
509
 *            object:column-reordered
510
 *            object:column-deleted
511
 *            object:model-changed
512
 *            object:active-descendant-changed
513
 *            object:announcement
514
 *
515
 *  (screen reader events)
516
 *             screen-reader:region-changed
517
 *
518
 *  (window events)
519
 *
520
 *            window:minimize
521
 *            window:maximize
522
 *            window:restore
523
 *            window:close
524
 *            window:create
525
 *            window:reparent
526
 *            window:desktop-create
527
 *            window:desktop-destroy
528
 *            window:activate
529
 *            window:deactivate
530
 *            window:raise
531
 *            window:lower
532
 *            window:move
533
 *            window:resize
534
 *            window:shade
535
 *            window:unshade
536
 *            window:restyle
537
 *
538
 *  (other events)
539
 *
540
 *            focus:
541
 *            mouse:abs
542
 *            mouse:rel
543
 *            mouse:b1p
544
 *            mouse:b1r
545
 *            mouse:b2p
546
 *            mouse:b2r
547
 *            mouse:b3p
548
 *            mouse:b3r
549
 *
550
 * NOTE: this character string may be UTF-8, but should not contain byte
551
 * value 56
552
 *            (ascii ':'), except as a delimiter, since non-UTF-8 string
553
 *            delimiting functions are used internally.
554
 *            In general, listening to
555
 *            toolkit-specific events is not recommended.
556
 *
557
 * Currently, object:text-reading-position needs to be specified explicitly
558
 * (it is not implied by object:text), since it is generated by the screen
559
 * reader and is thus a special case internally.
560
 *
561
 * Returns: #TRUE if successful, otherwise #FALSE.
562
 **/
563
gboolean
564
2
atspi_event_listener_register (AtspiEventListener *listener,
565
                               const gchar *event_type,
566
                               GError **error)
567
{
568
  /* TODO: Keep track of which events have been registered, so that we
569
   * deregister all of them when the event listener is destroyed */
570

            
571
2
  return atspi_event_listener_register_from_callback (listener->callback,
572
                                                      listener->user_data,
573
                                                      listener->cb_destroyed,
574
                                                      event_type, error);
575
}
576

            
577
/**
578
 * atspi_event_listener_register_full:
579
 * @listener: The #AtspiEventListener to register against an event type.
580
 * @event_type: a character string indicating the type of events for which
581
 *            notification is requested.  See #atspi_event_listener_register
582
 * for a description of the format and legal event types.
583
 * @properties: (element-type gchar*) (transfer none) (allow-none): a list of
584
 *             properties that should be sent along with the event. The
585
 *             properties are valued for the duration of the event callback.
586
 *             TODO: Document.
587
 *
588
 * Adds an in-process callback function to an existing #AtspiEventListener.
589
 *
590
 * Returns: #TRUE if successful, otherwise #FALSE.
591
 **/
592
gboolean
593
atspi_event_listener_register_full (AtspiEventListener *listener,
594
                                    const gchar *event_type,
595
                                    GArray *properties,
596
                                    GError **error)
597
{
598
  /* TODO: Keep track of which events have been registered, so that we
599
   * deregister all of them when the event listener is destroyed */
600

            
601
  return atspi_event_listener_register_from_callback_full (listener->callback,
602
                                                           listener->user_data,
603
                                                           listener->cb_destroyed,
604
                                                           event_type,
605
                                                           properties,
606
                                                           error);
607
}
608

            
609
/**
610
 * atspi_event_listener_register_with_app:
611
 * @listener: The #AtspiEventListener to register against an event type.
612
 * @event_type: a character string indicating the type of events for which
613
 *            notification is requested.  See #atspi_event_listener_register
614
 * for a description of the format and legal event types.
615
 * @properties: (element-type gchar*) (transfer none) (allow-none): a list of
616
 *             properties that should be sent along with the event. The
617
 *             properties are valued for the duration of the event callback.
618
 * @app: (allow-none): the application whose events should be reported, or
619
 *      %null for all applications.
620
 *
621
 * Adds an in-process callback function to an existing #AtspiEventListener.
622
 *
623
 * Returns: #TRUE if successful, otherwise #FALSE.
624
 **/
625
gboolean
626
atspi_event_listener_register_with_app (AtspiEventListener *listener,
627
                                        const gchar *event_type,
628
                                        GArray *properties,
629
                                        AtspiAccessible *app,
630
                                        GError **error)
631
{
632
  return atspi_event_listener_register_from_callback_with_app (listener->callback,
633
                                                               listener->user_data,
634
                                                               listener->cb_destroyed,
635
                                                               event_type,
636
                                                               properties,
637
                                                               app,
638
                                                               error);
639
}
640

            
641
static gboolean
642
2
notify_event_registered (EventListenerEntry *e)
643
{
644
2
  const char *app_path = (e->app ? e->app->parent.app->bus_name : "");
645

            
646
2
  dbind_method_call_reentrant (_atspi_bus (), atspi_bus_registry,
647
                               atspi_path_registry,
648
                               atspi_interface_registry,
649
                               "RegisterEvent",
650
                               NULL, "sass", e->event_type,
651
                               e->properties, app_path);
652

            
653
2
  return TRUE;
654
}
655

            
656
/**
657
 * atspi_event_listener_register_from_callback:
658
 * @callback: (scope notified): the #AtspiEventListenerCB to be registered
659
 * against an event type.
660
 * @user_data: (closure): User data to be passed to the callback.
661
 * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
662
 * @event_type: a character string indicating the type of events for which
663
 *            notification is requested.  See #atspi_event_listener_register
664
 * for a description of the format.
665
 *
666
 * Registers an #AtspiEventListenerCB against an @event_type.
667
 *
668
 * Returns: #TRUE if successfull, otherwise #FALSE.
669
 *
670
 **/
671
gboolean
672
2
atspi_event_listener_register_from_callback (AtspiEventListenerCB callback,
673
                                             void *user_data,
674
                                             GDestroyNotify callback_destroyed,
675
                                             const gchar *event_type,
676
                                             GError **error)
677
{
678
2
  return atspi_event_listener_register_from_callback_with_app (callback,
679
                                                               user_data,
680
                                                               callback_destroyed,
681
                                                               event_type, NULL,
682
                                                               NULL, error);
683
}
684

            
685
static GArray *
686
2
copy_event_properties (GArray *src)
687
{
688
  gint i;
689

            
690
2
  GArray *dst = g_array_new (FALSE, FALSE, sizeof (char *));
691

            
692
2
  if (!src)
693
2
    return dst;
694
  for (i = 0; i < src->len; i++)
695
    {
696
      gchar *dup = g_strdup (g_array_index (src, char *, i));
697
      g_array_append_val (dst, dup);
698
    }
699
  return dst;
700
}
701

            
702
/**
703
 * atspi_event_listener_register_from_callback_full:
704
 * @callback: (scope async): an #AtspiEventListenerCB function pointer.
705
 * @user_data: (closure callback)
706
 * @callback_destroyed: (destroy callback)
707
 * @event_type:
708
 * @properties: (element-type utf8)
709
 * @error:
710
 *
711
 * Returns: #TRUE if successful, otherwise #FALSE.
712
 *
713
 **/
714
gboolean
715
atspi_event_listener_register_from_callback_full (AtspiEventListenerCB callback,
716
                                                  void *user_data,
717
                                                  GDestroyNotify callback_destroyed,
718
                                                  const gchar *event_type,
719
                                                  GArray *properties,
720
                                                  GError **error)
721
{
722
  return atspi_event_listener_register_from_callback_with_app (callback,
723
                                                               user_data,
724
                                                               callback_destroyed,
725
                                                               event_type, NULL,
726
                                                               NULL, error);
727
}
728

            
729
/**
730
 * atspi_event_listener_register_from_callback_with_app:
731
 * @callback: (scope async): an #AtspiEventListenerCB function pointer.
732
 * @user_data: (closure callback)
733
 * @callback_destroyed: (destroy callback)
734
 * @event_type:
735
 * @properties: (element-type utf8)
736
 * @app: (allow-none)
737
 * @error:
738
 *
739
 * Returns: #TRUE if successful, otherwise #FALSE.
740
 *
741
 **/
742
gboolean
743
2
atspi_event_listener_register_from_callback_with_app (AtspiEventListenerCB callback,
744
                                                      void *user_data,
745
                                                      GDestroyNotify callback_destroyed,
746
                                                      const gchar *event_type,
747
                                                      GArray *properties,
748
                                                      AtspiAccessible *app,
749
                                                      GError **error)
750
{
751
  EventListenerEntry *e;
752
  DBusError d_error;
753
  GPtrArray *matchrule_array;
754
  gint i;
755

            
756
2
  if (!callback)
757
    {
758
      return FALSE;
759
    }
760

            
761
2
  if (!event_type)
762
    {
763
      g_warning ("called atspi_event_listener_register_from_callback with a NULL event_type");
764
      return FALSE;
765
    }
766

            
767
2
  e = g_new0 (EventListenerEntry, 1);
768
2
  e->event_type = g_strdup (event_type);
769
2
  e->callback = callback;
770
2
  e->user_data = user_data;
771
2
  e->callback_destroyed = callback_destroyed;
772
2
  callback_ref (callback == remove_datum ? (gpointer) user_data : (gpointer) callback,
773
                callback_destroyed);
774
2
  if (!convert_event_type_to_dbus (event_type, &e->category, &e->name, &e->detail, app, &matchrule_array))
775
    {
776
      g_free (e->event_type);
777
      g_free (e);
778
      return FALSE;
779
    }
780
2
  if (app)
781
    e->app = g_object_ref (app);
782
2
  e->properties = copy_event_properties (properties);
783
2
  event_listeners = g_list_prepend (event_listeners, e);
784
4
  for (i = 0; i < matchrule_array->len; i++)
785
    {
786
2
      char *matchrule = g_ptr_array_index (matchrule_array, i);
787
2
      dbus_error_init (&d_error);
788
2
      dbus_bus_add_match (_atspi_bus (), matchrule, &d_error);
789
2
      if (dbus_error_is_set (&d_error))
790
        {
791
          g_warning ("AT-SPI: Adding match: %s", d_error.message);
792
          dbus_error_free (&d_error);
793
          /* TODO: Set error */
794
        }
795

            
796
2
      g_free (matchrule);
797
    }
798
2
  g_ptr_array_free (matchrule_array, TRUE);
799

            
800
2
  notify_event_registered (e);
801
2
  return TRUE;
802
}
803

            
804
void
805
_atspi_reregister_event_listeners ()
806
{
807
  GList *l;
808
  EventListenerEntry *e;
809

            
810
  for (l = event_listeners; l; l = l->next)
811
    {
812
      e = l->data;
813
      notify_event_registered (e);
814
    }
815
}
816

            
817
/**
818
 * atspi_event_listener_register_no_data: (skip)
819
 * @callback: (scope notified): the #AtspiEventListenerSimpleCB to be
820
 *            registered against an event type.
821
 * @callback_destroyed: A #GDestroyNotify called when the callback is destroyed.
822
 * @event_type: a character string indicating the type of events for which
823
 *            notification is requested.  Format is
824
 *            EventClass:major_type:minor_type:detail
825
 *            where all subfields other than EventClass are optional.
826
 *            EventClasses include "object", "window", "mouse",
827
 *            and toolkit events (e.g. "Gtk", "AWT").
828
 *            Examples: "focus:", "Gtk:GtkWidget:button_press_event".
829
 *
830
 * Registers an #AtspiEventListenetSimpleCB. The method is similar to
831
 * #atspi_event_listener_register, but @callback takes no user_data.
832
 *
833
 * Returns: #TRUE if successfull, otherwise #FALSE.
834
 **/
835
gboolean
836
atspi_event_listener_register_no_data (AtspiEventListenerSimpleCB callback,
837
                                       GDestroyNotify callback_destroyed,
838
                                       const gchar *event_type,
839
                                       GError **error)
840
{
841
  return atspi_event_listener_register_from_callback (remove_datum, callback,
842
                                                      callback_destroyed,
843
                                                      event_type, error);
844
}
845

            
846
static gboolean
847
is_superset (const gchar *super, const gchar *sub)
848
{
849
  if (!super || !super[0])
850
    return TRUE;
851
  if (!sub || !sub[0])
852
    return FALSE;
853
  return (strcmp (super, sub) == 0);
854
}
855

            
856
/**
857
 * atspi_event_listener_deregister:
858
 * @listener: The #AtspiEventListener to deregister.
859
 * @event_type: a string specifying the event type for which this
860
 *             listener is to be deregistered.
861
 *
862
 * Deregisters an #AtspiEventListener from the registry, for a specific
863
 *             event type.
864
 *
865
 * Returns: #TRUE if successful, otherwise #FALSE.
866
 **/
867
gboolean
868
atspi_event_listener_deregister (AtspiEventListener *listener,
869
                                 const gchar *event_type,
870
                                 GError **error)
871
{
872
  return atspi_event_listener_deregister_from_callback (listener->callback,
873
                                                        listener->user_data,
874
                                                        event_type, error);
875
}
876

            
877
/**
878
 * atspi_event_listener_deregister_from_callback:
879
 * @callback: (scope call): the #AtspiEventListenerCB registered against an
880
 *            event type.
881
 * @user_data: (closure): User data that was passed in for this callback.
882
 * @event_type: a string specifying the event type for which this
883
 *             listener is to be deregistered.
884
 *
885
 * Deregisters an #AtspiEventListenerCB from the registry, for a specific
886
 *             event type.
887
 *
888
 * Returns: #TRUE if successful, otherwise #FALSE.
889
 **/
890
gboolean
891
atspi_event_listener_deregister_from_callback (AtspiEventListenerCB callback,
892
                                               void *user_data,
893
                                               const gchar *event_type,
894
                                               GError **error)
895
{
896
  char *category, *name, *detail;
897
  GPtrArray *matchrule_array;
898
  gint i;
899
  GList *l;
900

            
901
  if (!convert_event_type_to_dbus (event_type, &category, &name, &detail, NULL, &matchrule_array))
902
    {
903
      return FALSE;
904
    }
905
  if (!callback)
906
    {
907
      return FALSE;
908
    }
909

            
910
  for (l = event_listeners; l;)
911
    {
912
      EventListenerEntry *e = l->data;
913
      if (e->callback == callback &&
914
          e->user_data == user_data &&
915
          is_superset (category, e->category) &&
916
          is_superset (name, e->name) &&
917
          is_superset (detail, e->detail))
918
        {
919
          DBusMessage *message, *reply;
920
          l = g_list_next (l);
921
          if (in_send)
922
            {
923
              pending_removals = g_list_remove (pending_removals, e);
924
              pending_removals = g_list_append (pending_removals, e);
925
            }
926
          else
927
            event_listeners = g_list_remove (event_listeners, e);
928
          for (i = 0; i < matchrule_array->len; i++)
929
            {
930
              char *matchrule = g_ptr_array_index (matchrule_array, i);
931
              dbus_bus_remove_match (_atspi_bus (), matchrule, NULL);
932
            }
933
          message = dbus_message_new_method_call (atspi_bus_registry,
934
                                                  atspi_path_registry,
935
                                                  atspi_interface_registry,
936
                                                  "DeregisterEvent");
937
          if (!message)
938
            return FALSE;
939
          dbus_message_append_args (message, DBUS_TYPE_STRING, &event_type, DBUS_TYPE_INVALID);
940
          reply = _atspi_dbus_send_with_reply_and_block (message, error);
941
          if (reply)
942
            dbus_message_unref (reply);
943

            
944
          if (!in_send)
945
            listener_entry_free (e);
946
        }
947
      else
948
        l = g_list_next (l);
949
    }
950
  g_free (category);
951
  g_free (name);
952
  if (detail)
953
    g_free (detail);
954
  for (i = 0; i < matchrule_array->len; i++)
955
    g_free (g_ptr_array_index (matchrule_array, i));
956
  g_ptr_array_free (matchrule_array, TRUE);
957
  return TRUE;
958
}
959

            
960
/**
961
 * atspi_event_listener_deregister_no_data: (skip)
962
 * @callback: (scope call): the #AtspiEventListenerSimpleCB registered against
963
 *            an event type.
964
 * @event_type: a string specifying the event type for which this
965
 *             listener is to be deregistered.
966
 *
967
 * deregisters an #AtspiEventListenerSimpleCB from the registry, for a specific
968
 *             event type.
969
 *
970
 * Returns: #TRUE if successful, otherwise #FALSE.
971
 **/
972
gboolean
973
atspi_event_listener_deregister_no_data (AtspiEventListenerSimpleCB callback,
974
                                         const gchar *event_type,
975
                                         GError **error)
976
{
977
  return atspi_event_listener_deregister_from_callback (remove_datum, callback,
978
                                                        event_type,
979
                                                        error);
980
}
981

            
982
static AtspiEvent *
983
323
atspi_event_copy (AtspiEvent *src)
984
{
985
323
  AtspiEvent *dst = g_new0 (AtspiEvent, 1);
986
323
  dst->type = g_strdup (src->type);
987
323
  dst->source = g_object_ref (src->source);
988
323
  dst->detail1 = src->detail1;
989
323
  dst->detail2 = src->detail2;
990
323
  g_value_init (&dst->any_data, G_VALUE_TYPE (&src->any_data));
991
323
  g_value_copy (&src->any_data, &dst->any_data);
992
323
  if (src->sender)
993
323
    dst->sender = g_object_ref (src->sender);
994
323
  return dst;
995
}
996

            
997
static void
998
323
atspi_event_free (AtspiEvent *event)
999
{
323
  g_object_unref (event->source);
323
  g_free (event->type);
323
  g_value_unset (&event->any_data);
323
  g_clear_object (&event->sender);
323
  g_free (event);
323
}
static gboolean
323
detail_matches_listener (const char *event_detail, const char *listener_detail)
{
323
  if (!listener_detail)
323
    return TRUE;
  if (!event_detail)
    return (listener_detail ? FALSE : TRUE);
  return !(listener_detail[strcspn (listener_detail, ":")] == '\0'
               ? strncmp (listener_detail, event_detail,
                          strcspn (event_detail, ":"))
               : strcmp (listener_detail, event_detail));
}
static void
resolve_pending_removal (gpointer data)
{
  event_listeners = g_list_remove (event_listeners, data);
  listener_entry_free (data);
}
void
3538
_atspi_send_event (AtspiEvent *e)
{
  char *category, *name, *detail;
  GList *l;
3538
  GList *called_listeners = NULL;
  /* Ensure that the value is set to avoid a Python exception */
  /* TODO: Figure out how to do this without using a private field */
3538
  if (e->any_data.g_type == 0)
    {
3215
      g_value_init (&e->any_data, G_TYPE_INT);
3215
      g_value_set_int (&e->any_data, 0);
    }
3538
  if (!convert_event_type_to_dbus (e->type, &category, &name, &detail, NULL,
                                   NULL))
    {
      g_warning ("AT-SPI: Couldn't parse event: %s\n", e->type);
      return;
    }
3538
  in_send++;
7076
  for (l = event_listeners; l; l = g_list_next (l))
    {
3538
      EventListenerEntry *entry = l->data;
3538
      if (!strcmp (category, entry->category) &&
3861
          (entry->name == NULL || !strcmp (name, entry->name)) &&
323
          detail_matches_listener (detail, entry->detail) &&
323
          (entry->app == NULL || !strcmp (entry->app->parent.app->bus_name,
                                          e->source->parent.app->bus_name)))
        {
          GList *l2;
323
          for (l2 = called_listeners; l2; l2 = l2->next)
            {
              EventListenerEntry *e2 = l2->data;
              if (entry->callback == e2->callback && entry->user_data == e2->user_data)
                break;
            }
323
          if (!l2)
            {
323
              for (l2 = pending_removals; l2; l2 = l2->next)
                {
                  if (l2->data == entry)
                    break;
                }
            }
323
          if (!l2)
            {
323
              entry->callback (atspi_event_copy (e), entry->user_data);
323
              called_listeners = g_list_prepend (called_listeners, entry);
            }
        }
    }
3538
  in_send--;
3538
  if (detail)
3538
    g_free (detail);
3538
  g_free (name);
3538
  g_free (category);
3538
  g_list_free (called_listeners);
3538
  g_list_free_full (pending_removals, resolve_pending_removal);
3538
  pending_removals = NULL;
}
void
323
_atspi_dbus_handle_event (DBusMessage *message)
{
323
  char *detail = NULL;
323
  const char *category = dbus_message_get_interface (message);
323
  const char *sender = dbus_message_get_sender (message);
323
  const char *member = dbus_message_get_member (message);
323
  const char *signature = dbus_message_get_signature (message);
  gchar *name;
  gchar *converted_type;
  DBusMessageIter iter, iter_variant;
323
  dbus_message_iter_init (message, &iter);
  AtspiEvent e;
  dbus_int32_t detail1, detail2;
  char *p;
323
  GHashTable *cache = NULL;
323
  g_assert (strncmp (category, "org.a11y.atspi.Event.", 21) == 0);
323
  if (strcmp (signature, "siiv(so)") != 0 &&
323
      strcmp (signature, "siiva{sv}") != 0)
    {
      g_warning ("Got invalid signature %s for signal %s from interface %s\n", signature, member, category);
      return;
    }
323
  memset (&e, 0, sizeof (e));
  /* Find the plain interface name, e.g. "org.a11y.atspi.Event.ScreenReader" -> "ScreenReader" */
323
  category = g_utf8_strrchr (category, -1, '.');
323
  g_assert (category != NULL);
323
  category++;
323
  dbus_message_iter_get_basic (&iter, &detail);
323
  dbus_message_iter_next (&iter);
323
  dbus_message_iter_get_basic (&iter, &detail1);
323
  e.detail1 = detail1;
323
  dbus_message_iter_next (&iter);
323
  dbus_message_iter_get_basic (&iter, &detail2);
323
  e.detail2 = detail2;
323
  dbus_message_iter_next (&iter);
323
  converted_type = convert_name_from_dbus (category, FALSE);
323
  name = convert_name_from_dbus (member, FALSE);
323
  detail = convert_name_from_dbus (detail, TRUE);
323
  if (strcasecmp (category, name) != 0)
    {
323
      p = g_strconcat (converted_type, ":", name, NULL);
323
      g_free (converted_type);
323
      converted_type = p;
    }
  else if (detail[0] == '\0')
    {
      p = g_strconcat (converted_type, ":", NULL);
      g_free (converted_type);
      converted_type = p;
    }
323
  if (detail[0] != '\0')
    {
323
      p = g_strconcat (converted_type, ":", detail, NULL);
323
      g_free (converted_type);
323
      converted_type = p;
    }
323
  e.type = converted_type;
323
  if (strcmp (category, "ScreenReader") != 0)
    {
323
      e.source = _atspi_ref_accessible (sender, dbus_message_get_path (message));
323
      if (e.source == NULL)
        {
          g_warning ("Got no valid source accessible for signal %s from interface %s\n", member, category);
          g_free (converted_type);
          g_free (name);
          g_free (detail);
          return;
        }
    }
323
  dbus_message_iter_recurse (&iter, &iter_variant);
323
  switch (dbus_message_iter_get_arg_type (&iter_variant))
    {
323
    case DBUS_TYPE_STRUCT:
      {
        AtspiRect rect;
323
        if (demarshal_rect (&iter_variant, &rect))
          {
            g_value_init (&e.any_data, ATSPI_TYPE_RECT);
            g_value_set_boxed (&e.any_data, &rect);
          }
        else
          {
            AtspiAccessible *accessible;
323
            accessible = _atspi_dbus_consume_accessible (&iter_variant);
323
            if (!strcmp (category, "ScreenReader"))
              {
                g_object_unref (e.source);
                e.source = accessible;
              }
            else
              {
323
                g_value_init (&e.any_data, ATSPI_TYPE_ACCESSIBLE);
323
                g_value_set_instance (&e.any_data, accessible);
323
                if (accessible)
321
                  g_object_unref (accessible); /* value now owns it */
              }
          }
323
        break;
      }
    case DBUS_TYPE_STRING:
      {
        dbus_message_iter_get_basic (&iter_variant, &p);
        g_value_init (&e.any_data, G_TYPE_STRING);
        g_value_set_string (&e.any_data, p);
        break;
      }
    default:
      break;
    }
323
  g_assert (e.source != NULL);
323
  dbus_message_iter_next (&iter);
323
  if (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_ARRAY)
    {
      /* new form -- parse properties sent with event */
323
      cache = _atspi_dbus_update_cache_from_dict (e.source, &iter);
    }
323
  e.sender = _atspi_ref_accessible (sender, ATSPI_DBUS_PATH_ROOT);
323
  if (!strncmp (e.type, "object:children-changed", 23))
    {
323
      cache_process_children_changed (&e);
    }
  else if (!strncmp (e.type, "object:property-change", 22))
    {
      cache_process_property_change (&e);
    }
  else if (!strncmp (e.type, "object:state-changed", 20))
    {
      cache_process_state_changed (&e);
    }
  else if (!strncmp (e.type, "object:attributes-changed", 25))
    {
      cache_process_attributes_changed (&e);
    }
  else if (!strncmp (e.type, "focus", 5))
    {
      /* BGO#663992 - TODO: figure out the real problem */
      e.source->cached_properties &= ~(ATSPI_CACHE_STATES);
    }
323
  _atspi_send_event (&e);
323
  if (cache)
323
    _atspi_accessible_unref_cache (e.source);
323
  g_free (converted_type);
323
  g_free (name);
323
  g_free (detail);
323
  g_object_unref (e.source);
323
  g_object_unref (e.sender);
323
  g_value_unset (&e.any_data);
}
323
G_DEFINE_BOXED_TYPE (AtspiEvent, atspi_event, atspi_event_copy, atspi_event_free)