1
/*
2
 * AT-SPI - Assistive Technology Service Provider Interface
3
 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4
 *
5
 * Copyright 2011, F123 Consulting & Mais Diferen├žas
6
 * Copyright 2008, 2009, Codethink Ltd.
7
 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.,
8
 * Copyright 2001, 2002, 2003 Ximian, Inc.
9
 *
10
 * This library is free software; you can redistribute it and/or
11
 * modify it under the terms of the GNU Lesser General Public
12
 * License as published by the Free Software Foundation; either
13
 * version 2.1 of the License, or (at your option) any later version.
14
 *
15
 * This library is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
 * Lesser General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Lesser General Public
21
 * License along with this library; if not, write to the
22
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23
 * Boston, MA 02110-1301, USA.
24
 */
25

            
26
#include <ctype.h>
27
#include <string.h>
28

            
29
#define ATK_DISABLE_DEPRECATION_WARNINGS
30
#include <atk/atk.h>
31
#include <atspi/atspi.h>
32
#include <droute/droute.h>
33

            
34
#include "accessible-register.h"
35
#include "bridge.h"
36

            
37
#include "event.h"
38
#include "object.h"
39
#include "spi-dbus.h"
40

            
41
static GArray *listener_ids = NULL;
42

            
43
static gint atk_bridge_key_event_listener_id;
44
static gint atk_bridge_focus_tracker_id;
45

            
46
GMainContext *spi_context = NULL;
47

            
48
/*---------------------------------------------------------------------------*/
49

            
50
#define ITF_EVENT_OBJECT "org.a11y.atspi.Event.Object"
51
#define ITF_EVENT_WINDOW "org.a11y.atspi.Event.Window"
52
#define ITF_EVENT_DOCUMENT "org.a11y.atspi.Event.Document"
53
#define ITF_EVENT_FOCUS "org.a11y.atspi.Event.Focus"
54

            
55
/*---------------------------------------------------------------------------*/
56

            
57
typedef struct _SpiReentrantCallClosure
58
{
59
  DBusConnection *bus;
60
  GMainLoop *loop;
61
  DBusMessage *reply;
62
  guint timeout;
63
} SpiReentrantCallClosure;
64

            
65
static void
66
switch_main_context (GMainContext *cnx)
67
{
68
  GList *list;
69

            
70
  if (spi_global_app_data->server)
71
    atspi_dbus_server_setup_with_g_main (spi_global_app_data->server, cnx);
72
  atspi_dbus_connection_setup_with_g_main (spi_global_app_data->bus, cnx);
73
  atspi_set_main_context (cnx);
74
  for (list = spi_global_app_data->direct_connections; list; list = list->next)
75
    atspi_dbus_connection_setup_with_g_main (list->data, cnx);
76

            
77
  if (_atk_bridge_remove_pending_application_registration (spi_global_app_data))
78
    _atk_bridge_schedule_application_registration (spi_global_app_data);
79
}
80

            
81
guint
82
spi_idle_add (GSourceFunc function, gpointer data)
83
{
84
  GSource *source;
85
  guint id;
86

            
87
  source = g_idle_source_new ();
88
  g_source_set_callback (source, function, data, NULL);
89
  id = g_source_attach (source, spi_context);
90
  g_source_unref (source);
91

            
92
  return id;
93
}
94

            
95
guint
96
161
spi_timeout_add_seconds (gint interval, GSourceFunc function, gpointer data)
97
{
98
  GSource *source;
99
  guint id;
100

            
101
161
  source = g_timeout_source_new_seconds (interval);
102
161
  g_source_set_callback (source, function, data, NULL);
103
161
  id = g_source_attach (source, spi_context);
104
161
  g_source_unref (source);
105

            
106
161
  return id;
107
}
108

            
109
guint
110
161
spi_timeout_add_full (gint priority, guint interval, GSourceFunc function, gpointer data, GDestroyNotify notify)
111
{
112
  GSource *source;
113
  guint id;
114

            
115
161
  source = g_timeout_source_new (interval);
116
161
  g_source_set_priority (source, priority);
117
161
  g_source_set_callback (source, function, data, notify);
118
161
  id = g_source_attach (source, spi_context);
119
161
  g_source_unref (source);
120

            
121
161
  return id;
122
}
123

            
124
static void
125
set_reply (DBusPendingCall *pending, void *user_data)
126
{
127
  SpiReentrantCallClosure *closure = (SpiReentrantCallClosure *) user_data;
128

            
129
  closure->reply = dbus_pending_call_steal_reply (pending);
130
  dbus_pending_call_unref (pending);
131
  switch_main_context (spi_context);
132
  g_main_loop_quit (closure->loop);
133
}
134

            
135
static gboolean
136
timeout_reply (void *data)
137
{
138
  SpiReentrantCallClosure *closure = data;
139

            
140
  switch_main_context (spi_context);
141
  g_main_loop_quit (closure->loop);
142
  closure->timeout = -1;
143
  return FALSE;
144
}
145

            
146
static DBusMessage *
147
send_and_allow_reentry (DBusConnection *bus, DBusMessage *message)
148
{
149
  DBusPendingCall *pending;
150
  SpiReentrantCallClosure closure;
151
  GSource *source;
152

            
153
  closure.bus = bus;
154
  closure.loop = g_main_loop_new (spi_global_app_data->main_context, FALSE);
155
  closure.reply = NULL;
156
  switch_main_context (spi_global_app_data->main_context);
157

            
158
  if (!dbus_connection_send_with_reply (bus, message, &pending, 9000) || !pending)
159
    {
160
      switch_main_context (spi_context);
161
      return NULL;
162
    }
163
  dbus_pending_call_set_notify (pending, set_reply, (void *) &closure, NULL);
164
  source = g_timeout_source_new (500);
165
  g_source_set_callback (source, timeout_reply, &closure, NULL);
166
  closure.timeout = g_source_attach (source, spi_global_app_data->main_context);
167
  g_source_unref (source);
168
  g_main_loop_run (closure.loop);
169
  if (closure.timeout != -1)
170
    g_source_destroy (source);
171

            
172
  g_main_loop_unref (closure.loop);
173
  if (!closure.reply)
174
    dbus_pending_call_cancel (pending);
175
  return closure.reply;
176
}
177

            
178
void
179
atk_bridge_set_event_context (GMainContext *cnx)
180
{
181
  spi_context = cnx;
182
  switch_main_context (spi_context);
183
}
184

            
185
/*---------------------------------------------------------------------------*/
186

            
187
/*
188
 * Functionality related to sending device events from the application.
189
 *
190
 * This is used for forwarding key events on to the registry daemon.
191
 */
192

            
193
static gboolean
194
Accessibility_DeviceEventController_NotifyListenersSync (const AtspiDeviceEvent
195
                                                             *key_event)
196
{
197
  DBusMessage *message;
198
  dbus_bool_t consumed = FALSE;
199

            
200
  message =
201
      dbus_message_new_method_call (SPI_DBUS_NAME_REGISTRY,
202
                                    ATSPI_DBUS_PATH_DEC,
203
                                    ATSPI_DBUS_INTERFACE_DEC,
204
                                    "NotifyListenersSync");
205

            
206
  if (spi_dbus_marshal_deviceEvent (message, key_event))
207
    {
208
      DBusMessage *reply =
209
          send_and_allow_reentry (spi_global_app_data->bus, message);
210
      if (reply)
211
        {
212
          DBusError error;
213
          dbus_error_init (&error);
214
          if (!dbus_message_get_args (reply, &error, DBUS_TYPE_BOOLEAN,
215
                                      &consumed, DBUS_TYPE_INVALID))
216
            {
217
              /* TODO: print a warning */
218
              dbus_error_free (&error);
219
            }
220
          dbus_message_unref (reply);
221
        }
222
    }
223
  dbus_message_unref (message);
224
  return consumed;
225
}
226

            
227
static void
228
spi_init_keystroke_from_atk_key_event (AtspiDeviceEvent *keystroke,
229
                                       AtkKeyEventStruct *event)
230
{
231
  keystroke->id = (dbus_int32_t) event->keyval;
232
  keystroke->hw_code = (dbus_int16_t) event->keycode;
233
  keystroke->timestamp = (dbus_uint32_t) event->timestamp;
234
  keystroke->modifiers = (dbus_uint16_t) (event->state & 0xFFFF);
235
  if (event->string)
236
    {
237
      gunichar c;
238

            
239
      keystroke->event_string = g_strdup (event->string);
240
      c = g_utf8_get_char_validated (event->string, -1);
241
      if (c > 0 && g_unichar_isprint (c))
242
        keystroke->is_text = TRUE;
243
      else
244
        keystroke->is_text = FALSE;
245
    }
246
  else
247
    {
248
      keystroke->event_string = g_strdup ("");
249
      keystroke->is_text = FALSE;
250
    }
251
  switch (event->type)
252
    {
253
    case (ATK_KEY_EVENT_PRESS):
254
      keystroke->type = ATSPI_KEY_PRESSED_EVENT;
255
      break;
256
    case (ATK_KEY_EVENT_RELEASE):
257
      keystroke->type = ATSPI_KEY_RELEASED_EVENT;
258
      break;
259
    default:
260
      g_error ("atk passed us an AtkKeyEventStruct invalid type %d", event->type);
261
      return;
262
    }
263
#if 0
264
  g_print
265
    ("key_event type %d; val=%d code=%d modifiers=%x name=%s is_text=%d, time=%lx\n",
266
     (int) keystroke->type, (int) keystroke->id, (int) keystroke->hw_code,
267
     (int) keystroke->modifiers, keystroke->event_string,
268
     (int) keystroke->is_text, (unsigned long) keystroke->timestamp);
269
#endif
270
}
271

            
272
static gint
273
spi_atk_bridge_key_listener (AtkKeyEventStruct *event, gpointer data)
274
{
275
  gboolean result;
276
  AtspiDeviceEvent key_event;
277

            
278
  spi_init_keystroke_from_atk_key_event (&key_event, event);
279

            
280
  result =
281
      Accessibility_DeviceEventController_NotifyListenersSync (&key_event);
282

            
283
  if (key_event.event_string)
284
    g_free (key_event.event_string);
285

            
286
  return result;
287
}
288

            
289
/*---------------------------------------------------------------------------*/
290

            
291
static const void *
292
331
validate_for_dbus (const gint type,
293
                   const void *val)
294
{
295
331
  switch (type)
296
    {
297
331
    case DBUS_TYPE_STRING:
298
    case DBUS_TYPE_OBJECT_PATH:
299
331
      if (!val)
300
        return "";
301
331
      else if (!g_utf8_validate (val, -1, NULL))
302
        {
303
          g_warning ("atk-bridge: Received bad UTF-8 string when emitting event");
304
          return "";
305
        }
306
      else
307
331
        return val;
308
    default:
309
      return val;
310
    }
311
}
312

            
313
static void
314
331
append_basic (DBusMessageIter *iter,
315
              const char *type,
316
              const void *val)
317
{
318
  DBusMessageIter sub;
319

            
320
331
  dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, type, &sub);
321

            
322
331
  val = validate_for_dbus ((int) *type, val);
323
331
  dbus_message_iter_append_basic (&sub, (int) *type, &val);
324

            
325
331
  dbus_message_iter_close_container (iter, &sub);
326
331
}
327

            
328
static void
329
append_rect (DBusMessageIter *iter,
330
             const char *type,
331
             const void *val)
332
{
333
  DBusMessageIter variant, sub;
334
  const AtkRectangle *rect = (const AtkRectangle *) val;
335

            
336
  dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT, type, &variant);
337

            
338
  dbus_message_iter_open_container (&variant, DBUS_TYPE_STRUCT, NULL, &sub);
339

            
340
  dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->x));
341
  dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->y));
342
  dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->width));
343
  dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->height));
344

            
345
  dbus_message_iter_close_container (&variant, &sub);
346

            
347
  dbus_message_iter_close_container (iter, &variant);
348
}
349

            
350
static void
351
append_object (DBusMessageIter *iter,
352
               const char *type,
353
               const void *val)
354
{
355
  spi_object_append_v_reference (iter, ATK_OBJECT (val));
356
}
357

            
358
static gchar *
359
331
signal_name_to_dbus (const gchar *s)
360
{
361
331
  gchar *ret = g_strdup (s);
362
  gchar *t;
363

            
364
331
  if (!ret)
365
    return NULL;
366
331
  ret[0] = toupper (ret[0]);
367
331
  while ((t = strchr (ret, '-')) != NULL)
368
    {
369
      memmove (t, t + 1, strlen (t));
370
      *t = toupper (*t);
371
    }
372
331
  return ret;
373
}
374

            
375
/*
376
 * Converts names of the form "active-descendant-changed" to
377
 * "ActiveDescendantChanged"
378
 */
379
static gchar *
380
ensure_proper_format (const char *name)
381
{
382
  gchar *ret = (gchar *) g_malloc (strlen (name) * 2 + 2);
383
  gchar *p = ret;
384
  gboolean need_upper = TRUE;
385

            
386
  if (!ret)
387
    return NULL;
388
  while (*name)
389
    {
390
      if (need_upper)
391
        {
392
          *p++ = toupper (*name);
393
          need_upper = FALSE;
394
        }
395
      else if (*name == '-')
396
        need_upper = TRUE;
397
      else if (*name == ':')
398
        {
399
          need_upper = TRUE;
400
          *p++ = *name;
401
        }
402
      else
403
        *p++ = *name;
404
      name++;
405
    }
406
  *p = '\0';
407
  return ret;
408
}
409

            
410
void
411
append_properties (GArray *properties, event_data *evdata)
412
{
413
  GSList *ls;
414
  gint i;
415

            
416
  for (ls = evdata->properties; ls; ls = ls->next)
417
    {
418
      gboolean dup = FALSE;
419
      for (i = 0; i < properties->len; i++)
420
        {
421
          if (ls->data == g_array_index (properties, AtspiPropertyDefinition *, i))
422
            {
423
              dup = TRUE;
424
              break;
425
            }
426
        }
427
      if (!dup)
428
        g_array_append_val (properties, ls->data);
429
    }
430
}
431

            
432
static gboolean
433
331
signal_is_needed (AtkObject *obj, const gchar *klass, const gchar *major, const gchar *minor, GArray **properties)
434
{
435
  gchar *data[4];
436
  event_data *evdata;
437
331
  gboolean ret = FALSE;
438
  GList *list;
439
331
  GArray *props = NULL;
440

            
441
331
  if (!spi_global_app_data->events_initialized)
442
331
    return TRUE;
443

            
444
  data[0] = ensure_proper_format (klass[0] ? klass + 21 : klass);
445
  data[1] = ensure_proper_format (major);
446
  data[2] = ensure_proper_format (minor);
447
  data[3] = NULL;
448

            
449
  /* Hack: Always pass events that update the cache.
450
   * TODO: FOr 2.2, have at-spi2-core define a special "cache listener" for
451
   * this instead, so that we don't send these if no one is listening */
452
  if (!g_strcmp0 (data[1], "ChildrenChanged") ||
453
      ((!g_strcmp0 (data[1], "PropertyChange")) &&
454
       (!g_strcmp0 (data[2], "AccessibleName") ||
455
        !g_strcmp0 (data[2], "AccessibleDescription") ||
456
        !g_strcmp0 (data[2], "AccessibleParent") ||
457
        !g_strcmp0 (data[2], "AccessibleRole"))) ||
458
      !g_strcmp0 (data[1], "StateChanged"))
459
    {
460
      if (minor && !g_strcmp0 (minor, "defunct"))
461
        ret = TRUE;
462
      else
463
        {
464
          AtkStateSet *set = atk_object_ref_state_set (obj);
465
          AtkState state = ((!g_strcmp0 (data[1], "ChildrenChanged")) ? ATK_STATE_MANAGES_DESCENDANTS : ATK_STATE_TRANSIENT);
466
          ret = !atk_state_set_contains_state (set, state);
467
          g_object_unref (set);
468
        }
469
    }
470

            
471
  /* Hack: events such as "object::text-changed::insert:system" as
472
     generated by Gecko */
473
  data[2][strcspn (data[2], ":")] = '\0';
474

            
475
  for (list = spi_global_app_data->events; list; list = list->next)
476
    {
477
      evdata = list->data;
478
      if (spi_event_is_subtype (data, evdata->data))
479
        {
480
          ret = TRUE;
481
          if (!props)
482
            props = g_array_new (TRUE, TRUE, sizeof (AtspiPropertyDefinition *));
483
          append_properties (props, evdata);
484
        }
485
    }
486

            
487
  g_free (data[2]);
488
  g_free (data[1]);
489
  g_free (data[0]);
490
  *properties = props;
491
  return ret;
492
}
493

            
494
/* Convert a : to a / so that listeners can use arg0path to match only
495
 *  * the prefix */
496
static char *
497
331
adapt_minor_for_dbus (const char *source)
498
{
499
331
  gchar *ret = g_strdup (source);
500
331
  int i = strcspn (ret, ":");
501
331
  if (ret[i] == ':')
502
    ret[i] = '/';
503
331
  return ret;
504
}
505

            
506
/*
507
 * Emits an AT-SPI event.
508
 * AT-SPI events names are split into three parts:
509
 * class:major:minor
510
 * This is mapped onto D-Bus events as:
511
 * D-Bus Interface:Signal Name:Detail argument
512
 *
513
 * Marshals a basic type into the 'any_data' attribute of
514
 * the AT-SPI event.
515
 */
516
static void
517
331
emit_event (AtkObject *obj,
518
            const char *klass,
519
            const char *major,
520
            const char *minor,
521
            dbus_int32_t detail1,
522
            dbus_int32_t detail2,
523
            const char *type,
524
            const void *val,
525
            void (*append_variant) (DBusMessageIter *, const char *, const void *))
526
{
527
331
  DBusConnection *bus = spi_global_app_data->bus;
528
  char *path;
529
  char *minor_dbus;
530

            
531
  gchar *cname;
532
  DBusMessage *sig;
533
  DBusMessageIter iter, iter_dict, iter_dict_entry;
534
331
  GArray *properties = NULL;
535

            
536
331
  if (!klass)
537
    klass = "";
538
331
  if (!major)
539
    major = "";
540
331
  if (!minor)
541
331
    minor = "";
542
331
  if (!type)
543
    type = "u";
544

            
545
331
  if (!signal_is_needed (obj, klass, major, minor, &properties))
546
    return;
547

            
548
331
  path = spi_register_object_to_path (spi_global_register, G_OBJECT (obj));
549
331
  g_return_if_fail (path != NULL);
550

            
551
  /*
552
   * This is very annoying, but as '-' isn't a legal signal
553
   * name in D-Bus (Why not??!?) The names need converting
554
   * on this side, and again on the client side.
555
   */
556
331
  cname = signal_name_to_dbus (major);
557
331
  sig = dbus_message_new_signal (path, klass, cname);
558

            
559
331
  dbus_message_iter_init_append (sig, &iter);
560

            
561
331
  minor_dbus = adapt_minor_for_dbus (minor);
562
331
  dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &minor_dbus);
563
331
  g_free (minor_dbus);
564
331
  dbus_message_iter_append_basic (&iter, DBUS_TYPE_INT32, &detail1);
565
331
  dbus_message_iter_append_basic (&iter, DBUS_TYPE_INT32, &detail2);
566
331
  append_variant (&iter, type, val);
567

            
568
331
  dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_dict);
569
  /* Add requested properties, unless the object is being marked defunct, in
570
     which case it's safest not to touch it */
571
331
  if (minor == NULL || strcmp (minor, "defunct") != 0 || detail1 == 0)
572
    {
573
331
      if (properties)
574
        {
575
          gint i;
576
          for (i = 0; i < properties->len; i++)
577
            {
578
              AtspiPropertyDefinition *prop = g_array_index (properties, AtspiPropertyDefinition *, i);
579
              dbus_message_iter_open_container (&iter_dict, DBUS_TYPE_DICT_ENTRY, NULL,
580
                                                &iter_dict_entry);
581
              dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING, &prop->name);
582
              prop->func (&iter_dict_entry, obj);
583
              dbus_message_iter_close_container (&iter_dict, &iter_dict_entry);
584
            }
585
          g_array_free (properties, TRUE);
586
        }
587
    }
588
331
  dbus_message_iter_close_container (&iter, &iter_dict);
589

            
590
331
  dbus_connection_send (bus, sig, NULL);
591
331
  dbus_message_unref (sig);
592

            
593
331
  if (g_strcmp0 (cname, "ChildrenChanged") != 0)
594
331
    spi_object_lease_if_needed (G_OBJECT (obj));
595

            
596
331
  g_free (cname);
597
331
  g_free (path);
598
}
599

            
600
/*---------------------------------------------------------------------------*/
601

            
602
/*
603
 * The focus listener handles the ATK 'focus' signal and forwards it
604
 * as the AT-SPI event, 'focus:'
605
 */
606
static void
607
focus_tracker (AtkObject *accessible)
608
{
609
  emit_event (accessible, ITF_EVENT_FOCUS, "focus", "", 0, 0,
610
              DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
611
}
612

            
613
/*---------------------------------------------------------------------------*/
614

            
615
#define PCHANGE "PropertyChange"
616

            
617
/*
618
 * This handler handles the following ATK signals and
619
 * converts them to AT-SPI events:
620
 *
621
 * Gtk:AtkObject:property-change -> object:property-change:(property-name)
622
 *
623
 * The property-name is part of the ATK property-change signal.
624
 */
625
static gboolean
626
property_event_listener (GSignalInvocationHint *signal_hint,
627
                         guint n_param_values,
628
                         const GValue *param_values,
629
                         gpointer data)
630
{
631
  AtkObject *accessible;
632
  AtkPropertyValues *values;
633

            
634
  const gchar *pname = NULL;
635

            
636
  AtkObject *otemp;
637
  const gchar *s1;
638
  gint i;
639

            
640
  accessible = g_value_get_object (&param_values[0]);
641
  values = (AtkPropertyValues *) g_value_get_pointer (&param_values[1]);
642

            
643
  pname = values[0].property_name;
644

            
645
  /* TODO Could improve this control statement by matching
646
   * on only the end of the signal names,
647
   */
648
  if (strcmp (pname, "accessible-name") == 0)
649
    {
650
      s1 = atk_object_get_name (accessible);
651
      if (s1 != NULL)
652
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
653
                    DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
654
    }
655
  else if (strcmp (pname, "accessible-description") == 0)
656
    {
657
      s1 = atk_object_get_description (accessible);
658
      if (s1 != NULL)
659
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
660
                    DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
661
    }
662
  else if (strcmp (pname, "accessible-parent") == 0)
663
    {
664
      otemp = atk_object_get_parent (accessible);
665
      if (otemp != NULL)
666
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
667
                    "(so)", otemp, append_object);
668
    }
669
  else if (strcmp (pname, "accessible-role") == 0)
670
    {
671
      i = atk_object_get_role (accessible);
672
      emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
673
                  DBUS_TYPE_UINT32_AS_STRING, GINT_TO_POINTER (i), append_basic);
674
    }
675
  else if (strcmp (pname, "accessible-table-summary") == 0)
676
    {
677
      otemp = atk_table_get_summary (ATK_TABLE (accessible));
678
      if (otemp != NULL)
679
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
680
                    "(so)", otemp, append_object);
681
    }
682
  else if (strcmp (pname, "accessible-table-column-header") == 0)
683
    {
684
      i = g_value_get_int (&(values->new_value));
685
      otemp = atk_table_get_column_header (ATK_TABLE (accessible), i);
686
      if (otemp != NULL)
687
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
688
                    "(so)", otemp, append_object);
689
    }
690
  else if (strcmp (pname, "accessible-table-row-header") == 0)
691
    {
692
      i = g_value_get_int (&(values->new_value));
693
      otemp = atk_table_get_row_header (ATK_TABLE (accessible), i);
694
      if (otemp != NULL)
695
        emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
696
                    "(so)", otemp, append_object);
697
    }
698
  else if (strcmp (pname, "accessible-table-row-description") == 0)
699
    {
700
      i = g_value_get_int (&(values->new_value));
701
      s1 = atk_table_get_row_description (ATK_TABLE (accessible), i);
702
      emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
703
                  DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
704
    }
705
  else if (strcmp (pname, "accessible-table-column-description") == 0)
706
    {
707
      i = g_value_get_int (&(values->new_value));
708
      s1 = atk_table_get_column_description (ATK_TABLE (accessible), i);
709
      emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
710
                  DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
711
    }
712
  else if (strcmp (pname, "accessible-table-caption-object") == 0)
713
    {
714
      otemp = atk_table_get_caption (ATK_TABLE (accessible));
715
      emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
716
                  "(so)", otemp, append_object);
717
    }
718
  else
719
    {
720
      emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
721
                  DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
722
    }
723
  return TRUE;
724
}
725

            
726
/*---------------------------------------------------------------------------*/
727

            
728
#define STATE_CHANGED "state-changed"
729

            
730
/*
731
 * The state event listener handles 'Gtk:AtkObject:state-change' ATK signals
732
 * and forwards them as object:state-changed:(param-name) AT-SPI events. Where
733
 * the param-name is part of the ATK state-change signal.
734
 */
735
static gboolean
736
state_event_listener (GSignalInvocationHint *signal_hint,
737
                      guint n_param_values,
738
                      const GValue *param_values,
739
                      gpointer data)
740
{
741
  AtkObject *accessible;
742
  const gchar *pname;
743
  guint detail1;
744

            
745
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
746
  pname = g_value_get_string (&param_values[1]);
747

            
748
  detail1 = (g_value_get_boolean (&param_values[2])) ? 1 : 0;
749
  emit_event (accessible, ITF_EVENT_OBJECT, STATE_CHANGED, pname, detail1, 0,
750
              DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
751

            
752
  if (!g_strcmp0 (pname, "defunct") && detail1)
753
    spi_register_deregister_object (spi_global_register, G_OBJECT (accessible),
754
                                    TRUE);
755
  return TRUE;
756
}
757

            
758
/*---------------------------------------------------------------------------*/
759

            
760
/*
761
 * The window event listener handles the following ATK signals and forwards
762
 * them as AT-SPI events:
763
 *
764
 * window:create     -> window:create
765
 * window:destroy    -> window:destroy
766
 * window:minimize   -> window:minimize
767
 * window:maximize   -> window:maximize
768
 * window:activate   -> window:activate
769
 * window:deactivate -> window:deactivate
770
 */
771
static gboolean
772
window_event_listener (GSignalInvocationHint *signal_hint,
773
                       guint n_param_values,
774
                       const GValue *param_values,
775
                       gpointer data)
776
{
777
  AtkObject *accessible;
778
  GSignalQuery signal_query;
779
  const gchar *name, *s;
780

            
781
  g_signal_query (signal_hint->signal_id, &signal_query);
782
  name = signal_query.signal_name;
783

            
784
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
785
  s = atk_object_get_name (accessible);
786
  emit_event (accessible, ITF_EVENT_WINDOW, name, "", 0, 0,
787
              DBUS_TYPE_STRING_AS_STRING, s, append_basic);
788

            
789
  return TRUE;
790
}
791

            
792
/*---------------------------------------------------------------------------*/
793

            
794
/*
795
 * The document event listener handles the following ATK signals
796
 * and converts them to AT-SPI events:
797
 *
798
 * Gtk:AtkDocument:load-complete ->  document:load-complete
799
 * Gtk:AtkDocument:load-stopped  ->  document:load-stopped
800
 * Gtk:AtkDocument:reload        ->  document:reload
801
 * Gtk:AtkDocument:page-changed  ->  document:page-changed
802
 */
803
static gboolean
804
document_event_listener (GSignalInvocationHint *signal_hint,
805
                         guint n_param_values,
806
                         const GValue *param_values,
807
                         gpointer data)
808
{
809
  AtkObject *accessible;
810
  GSignalQuery signal_query;
811
  const gchar *name, *s;
812
  gint detail1 = 0;
813

            
814
  g_signal_query (signal_hint->signal_id, &signal_query);
815
  name = signal_query.signal_name;
816

            
817
  if (n_param_values > 0) // on the case of page-changed
818
    if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
819
      detail1 = g_value_get_int (&param_values[1]);
820

            
821
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
822
  s = atk_object_get_name (accessible);
823
  emit_event (accessible, ITF_EVENT_DOCUMENT, name, "", detail1, 0,
824
              DBUS_TYPE_STRING_AS_STRING, s, append_basic);
825

            
826
  return TRUE;
827
}
828

            
829
/*---------------------------------------------------------------------------*/
830

            
831
/*
832
 * Signal handler for  "Gtk:AtkComponent:bounds-changed". Converts
833
 * this to an AT-SPI event - "object:bounds-changed".
834
 */
835
static gboolean
836
bounds_event_listener (GSignalInvocationHint *signal_hint,
837
                       guint n_param_values,
838
                       const GValue *param_values,
839
                       gpointer data)
840
{
841
  AtkObject *accessible;
842
  AtkRectangle *atk_rect;
843
  GSignalQuery signal_query;
844
  const gchar *name;
845

            
846
  g_signal_query (signal_hint->signal_id, &signal_query);
847
  name = signal_query.signal_name;
848

            
849
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
850

            
851
  if (G_VALUE_HOLDS_BOXED (param_values + 1))
852
    {
853
      atk_rect = g_value_get_boxed (param_values + 1);
854

            
855
      emit_event (accessible, ITF_EVENT_OBJECT, name, "", 0, 0,
856
                  "(iiii)", atk_rect, append_rect);
857
    }
858
  return TRUE;
859
}
860

            
861
/*---------------------------------------------------------------------------*/
862

            
863
/*
864
 * Handles the ATK signal 'Gtk:AtkObject:active-descendant-changed' and
865
 * converts it to the AT-SPI signal - 'object:active-descendant-changed'.
866
 *
867
 */
868
static gboolean
869
active_descendant_event_listener (GSignalInvocationHint *signal_hint,
870
                                  guint n_param_values,
871
                                  const GValue *param_values,
872
                                  gpointer data)
873
{
874
  AtkObject *accessible;
875
  AtkObject *child;
876
  GSignalQuery signal_query;
877
  const gchar *name;
878
  gint detail1;
879

            
880
  g_signal_query (signal_hint->signal_id, &signal_query);
881
  name = signal_query.signal_name;
882

            
883
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
884
  child = ATK_OBJECT (g_value_get_pointer (&param_values[1]));
885
  g_return_val_if_fail (ATK_IS_OBJECT (child), TRUE);
886

            
887
  detail1 = atk_object_get_index_in_parent (child);
888

            
889
  emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, 0,
890
              "(so)", child, append_object);
891
  return TRUE;
892
}
893

            
894
static gboolean
895
announcement_event_listener (GSignalInvocationHint *signal_hint,
896
                             guint n_param_values,
897
                             const GValue *param_values,
898
                             gpointer data)
899
{
900
  AtkObject *accessible;
901
  const gchar *text;
902
  GSignalQuery signal_query;
903
  const gchar *name;
904

            
905
  g_signal_query (signal_hint->signal_id, &signal_query);
906
  name = signal_query.signal_name;
907

            
908
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
909
  text = g_value_get_string (&param_values[1]);
910
  g_return_val_if_fail (text != NULL, TRUE);
911

            
912
  emit_event (accessible, ITF_EVENT_OBJECT, name, "", ATSPI_LIVE_POLITE, 0,
913
              "s", text, append_basic);
914
  return TRUE;
915
}
916

            
917
static gboolean
918
notification_event_listener (GSignalInvocationHint *signal_hint,
919
                             guint n_param_values,
920
                             const GValue *param_values,
921
                             gpointer data)
922
{
923
  AtkObject *accessible;
924
  const gchar *text;
925
  gint politeness;
926

            
927
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
928
  text = g_value_get_string (&param_values[1]);
929
  g_return_val_if_fail (text != NULL, TRUE);
930
  politeness = g_value_get_int (&param_values[2]);
931

            
932
  emit_event (accessible, ITF_EVENT_OBJECT, "announcement", "", politeness, 0,
933
              "s", text, append_basic);
934
  return TRUE;
935
}
936

            
937
/*---------------------------------------------------------------------------*/
938

            
939
/*
940
 * Handles the ATK signal 'Gtk:AtkHypertext:link-selected' and
941
 * converts it to the AT-SPI signal - 'object:link-selected'
942
 *
943
 */
944
static gboolean
945
link_selected_event_listener (GSignalInvocationHint *signal_hint,
946
                              guint n_param_values,
947
                              const GValue *param_values,
948
                              gpointer data)
949
{
950
  AtkObject *accessible;
951
  GSignalQuery signal_query;
952
  const gchar *name, *minor;
953
  gint detail1 = 0;
954

            
955
  g_signal_query (signal_hint->signal_id, &signal_query);
956
  name = signal_query.signal_name;
957

            
958
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
959
  minor = g_quark_to_string (signal_hint->detail);
960

            
961
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
962
    detail1 = g_value_get_int (&param_values[1]);
963

            
964
  emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, 0,
965
              DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
966
  return TRUE;
967
}
968

            
969
/*---------------------------------------------------------------------------*/
970

            
971
/*
972
 * Handles the ATK signal 'Gtk:AtkText:text-changed' and
973
 * converts it to the AT-SPI signal - 'object:text-changed'
974
 * This signal is deprecated by Gtk:AtkText:text-insert
975
 * and Gtk:AtkText:text-remove
976
 *
977
 */
978
static gboolean
979
text_changed_event_listener (GSignalInvocationHint *signal_hint,
980
                             guint n_param_values,
981
                             const GValue *param_values,
982
                             gpointer data)
983
{
984
  AtkObject *accessible;
985
  GSignalQuery signal_query;
986
  const gchar *name, *minor;
987
  gchar *selected;
988
  gint detail1 = 0, detail2 = 0;
989

            
990
  g_signal_query (signal_hint->signal_id, &signal_query);
991
  name = signal_query.signal_name;
992

            
993
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
994
  minor = g_quark_to_string (signal_hint->detail);
995

            
996
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
997
    detail1 = g_value_get_int (&param_values[1]);
998

            
999
  if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
    detail2 = g_value_get_int (&param_values[2]);
  selected =
      atk_text_get_text (ATK_TEXT (accessible), detail1, detail1 + detail2);
  emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
              DBUS_TYPE_STRING_AS_STRING, selected, append_basic);
  g_free (selected);
  return TRUE;
}
/*
 * Handles the ATK signal 'Gtk:AtkText:text-insert' and
 * converts it to the AT-SPI signal - 'object:text-changed'
 *
 */
static gboolean
text_insert_event_listener (GSignalInvocationHint *signal_hint,
                            guint n_param_values,
                            const GValue *param_values,
                            gpointer data)
{
  AtkObject *accessible;
  guint text_changed_signal_id;
  GSignalQuery signal_query;
  const gchar *name;
  const gchar *minor_raw, *text;
  gchar *minor;
  gint detail1 = 0, detail2 = 0;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  /* Get signal name for 'Gtk:AtkText:text-changed' so
   * we convert it to the AT-SPI signal - 'object:text-changed'
   */
  text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
  g_signal_query (text_changed_signal_id, &signal_query);
  name = signal_query.signal_name;
  /* Add the insert and keep any detail coming from atk */
  minor_raw = g_quark_to_string (signal_hint->detail);
  if (minor_raw)
    minor = g_strconcat ("insert:", minor_raw, NULL);
  else
    minor = g_strdup ("insert");
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
    detail1 = g_value_get_int (&param_values[1]);
  if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
    detail2 = g_value_get_int (&param_values[2]);
  if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
    text = g_value_get_string (&param_values[3]);
  else
    text = "";
  emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
              DBUS_TYPE_STRING_AS_STRING, text, append_basic);
  g_free (minor);
  return TRUE;
}
/*
 * Handles the ATK signal 'Gtk:AtkText:text-remove' and
 * converts it to the AT-SPI signal - 'object:text-changed'
 *
 */
static gboolean
text_remove_event_listener (GSignalInvocationHint *signal_hint,
                            guint n_param_values,
                            const GValue *param_values,
                            gpointer data)
{
  AtkObject *accessible;
  guint text_changed_signal_id;
  GSignalQuery signal_query;
  const gchar *name;
  const gchar *minor_raw, *text;
  gchar *minor;
  gint detail1 = 0, detail2 = 0;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  /* Get signal name for 'Gtk:AtkText:text-changed' so
   * we convert it to the AT-SPI signal - 'object:text-changed'
   */
  text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
  g_signal_query (text_changed_signal_id, &signal_query);
  name = signal_query.signal_name;
  minor_raw = g_quark_to_string (signal_hint->detail);
  /* Add the delete and keep any detail coming from atk */
  if (minor_raw)
    minor = g_strconcat ("delete:", minor_raw, NULL);
  else
    minor = g_strdup ("delete");
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
    detail1 = g_value_get_int (&param_values[1]);
  if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
    detail2 = g_value_get_int (&param_values[2]);
  if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
    text = g_value_get_string (&param_values[3]);
  else
    text = "";
  emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
              DBUS_TYPE_STRING_AS_STRING, text, append_basic);
  g_free (minor);
  return TRUE;
}
/*---------------------------------------------------------------------------*/
/*
 * Handles the ATK signal 'Gtk:AtkText:text-selection-changed' and
 * converts it to the AT-SPI signal - 'object:text-selection-changed'
 *
 */
static gboolean
text_selection_changed_event_listener (GSignalInvocationHint *signal_hint,
                                       guint n_param_values,
                                       const GValue *param_values,
                                       gpointer data)
{
  AtkObject *accessible;
  GSignalQuery signal_query;
  const gchar *name, *minor;
  g_signal_query (signal_hint->signal_id, &signal_query);
  name = signal_query.signal_name;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  minor = g_quark_to_string (signal_hint->detail);
  emit_event (accessible, ITF_EVENT_OBJECT, name, minor, 0, 0,
              DBUS_TYPE_STRING_AS_STRING, "", append_basic);
  return TRUE;
}
/*---------------------------------------------------------------------------*/
/*
 * Children changed signal converter and forwarder.
 *
 * Klass (Interface) org.a11y.atspi.Event.Object
 * Major is the signal name.
 * Minor is 'add' or 'remove'
 * detail1 is the index.
 * detail2 is 0.
 * any_data is the child reference.
 */
static gboolean
children_changed_event_listener (GSignalInvocationHint *signal_hint,
                                 guint n_param_values,
                                 const GValue *param_values,
                                 gpointer data)
{
  GSignalQuery signal_query;
  const gchar *name, *minor;
  gint detail1 = 0, detail2 = 0;
  AtkObject *accessible, *ao = NULL;
  gpointer child;
  AtkStateSet *set;
  gboolean ret;
  g_signal_query (signal_hint->signal_id, &signal_query);
  name = signal_query.signal_name;
  /* If the accessible is on STATE_MANAGES_DESCENDANTS state,
     children-changed signal are not forwarded. */
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  set = atk_object_ref_state_set (accessible);
  ret = atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS);
  g_object_unref (set);
  if (ret)
    return TRUE;
  minor = g_quark_to_string (signal_hint->detail);
  detail1 = g_value_get_uint (param_values + 1);
  child = g_value_get_pointer (param_values + 2);
  if (ATK_IS_OBJECT (child))
    {
      ao = ATK_OBJECT (child);
      emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
                  "(so)", ao, append_object);
    }
  else if ((minor != NULL) && (strcmp (minor, "add") == 0))
    {
      ao = atk_object_ref_accessible_child (accessible,
                                            detail1);
      emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
                  "(so)", ao, append_object);
      g_object_unref (ao);
    }
  else
    {
      emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
                  "(so)", ao, append_object);
    }
  return TRUE;
}
/*
 * Handles the ATK signal 'Gtk:AtkObject:attribute-changed' and
 * converts it to the AT-SPI signal - 'object:attributes-changed'
 *
 */
static gboolean
attribute_changed_event_listener (GSignalInvocationHint *signal_hint,
                                  guint n_param_values,
                                  const GValue *param_values,
                                  gpointer data)
{
  AtkObject *accessible;
  const gchar *key = NULL, *value = NULL;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_STRING)
    key = g_value_get_string (&param_values[1]);
  else
    key = "";
  if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_STRING)
    value = g_value_get_string (&param_values[2]);
  else
    value = "";
  emit_event (accessible, ITF_EVENT_OBJECT, "attributes-changed", key, 0, 0,
              DBUS_TYPE_STRING_AS_STRING, value, append_basic);
  return TRUE;
}
/*
 * Handles the ATK signal 'Gtk:AtkDocument:attribute-changed' and
 * converts it to the AT-SPI signal - 'document:attributes-changed'
 *
 */
static gboolean
document_attribute_changed_event_listener (GSignalInvocationHint *signal_hint,
                                           guint n_param_values,
                                           const GValue *param_values,
                                           gpointer data)
{
  AtkObject *accessible;
  const gchar *key = NULL, *value = NULL;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_STRING)
    key = g_value_get_string (&param_values[1]);
  else
    key = "";
  if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_STRING)
    value = g_value_get_string (&param_values[2]);
  else
    value = "";
  emit_event (accessible, ITF_EVENT_DOCUMENT, "attributes-changed", key, 0, 0,
              DBUS_TYPE_STRING_AS_STRING, value, append_basic);
  return TRUE;
}
/*---------------------------------------------------------------------------*/
/*
 * Generic signal converter and forwarder.
 *
 * Klass (Interface) org.a11y.atspi.Event.Object
 * Major is the signal name.
 * Minor is NULL.
 * detail1 is 0.
 * detail2 is 0.
 * any_data is NULL.
 */
static gboolean
generic_event_listener (GSignalInvocationHint *signal_hint,
                        guint n_param_values,
                        const GValue *param_values,
                        gpointer data)
{
  AtkObject *accessible;
  GSignalQuery signal_query;
  const gchar *name;
  int detail1 = 0, detail2 = 0;
  g_signal_query (signal_hint->signal_id, &signal_query);
  name = signal_query.signal_name;
  accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
  if (n_param_values > 1 && G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
    detail1 = g_value_get_int (&param_values[1]);
  if (n_param_values > 2 && G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
    detail2 = g_value_get_int (&param_values[2]);
  emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, detail2,
              DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
  return TRUE;
}
/*---------------------------------------------------------------------------*/
/*
 * Registers the provided function as a handler for the given signal name
 * and stores the signal id returned so that the function may be
 * de-registered later.
 */
static guint
5957
add_signal_listener (GSignalEmissionHook listener, const char *signal_name)
{
  guint id;
5957
  id = atk_add_global_event_listener (listener, signal_name);
5957
  if (id > 0) /* id == 0 is a failure */
5796
    g_array_append_val (listener_ids, id);
5957
  return id;
}
/*
 * Initialization for the signal handlers.
 *
 * Registers all required signal handlers.
 */
void
161
spi_atk_register_event_listeners (void)
{
  /*
   * Kludge to make sure the Atk interface types are registered, otherwise
   * the AtkText signal handlers below won't get registered
   */
161
  GObject *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
161
  AtkObject *bo = atk_no_op_object_new (ao);
161
  guint id = 0;
161
  g_object_unref (G_OBJECT (bo));
161
  g_object_unref (ao);
161
  if (listener_ids)
    {
      g_warning ("atk_bridge: spi_atk-register_event_listeners called multiple times");
      return;
    }
  /* Register for focus event notifications, and register app with central registry  */
161
  listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
161
  atk_bridge_focus_tracker_id = atk_add_focus_tracker (focus_tracker);
161
  add_signal_listener (property_event_listener,
                       "Gtk:AtkObject:property-change");
  /* window events: we tentative try to register using the old format */
161
  id = add_signal_listener (window_event_listener, "window:create");
161
  if (id != 0)
    {
      /* If we are able to register using the old format, we assume
       * that the ATK implementor is managing window events without
       * AtkWindow. We can't use the opposite test because after
       * including AtkWindow on ATK you would be able to register to
       * that event, although the ATK implementor could or not use it.
       */
      add_signal_listener (window_event_listener, "window:destroy");
      add_signal_listener (window_event_listener, "window:minimize");
      add_signal_listener (window_event_listener, "window:maximize");
      add_signal_listener (window_event_listener, "window:restore");
      add_signal_listener (window_event_listener, "window:activate");
      add_signal_listener (window_event_listener, "window:deactivate");
    }
  else
    {
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:create");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:destroy");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:minimize");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:maximize");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:restore");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:activate");
161
      add_signal_listener (window_event_listener, "Atk:AtkWindow:deactivate");
    }
161
  add_signal_listener (document_event_listener,
                       "Gtk:AtkDocument:load-complete");
161
  add_signal_listener (document_event_listener, "Gtk:AtkDocument:reload");
161
  add_signal_listener (document_event_listener,
                       "Gtk:AtkDocument:load-stopped");
161
  add_signal_listener (document_event_listener,
                       "Gtk:AtkDocument:page-changed");
161
  add_signal_listener (document_attribute_changed_event_listener,
                       "Gtk:AtkDocument:document-attribute-changed");
  /* TODO Fake this event on the client side */
161
  add_signal_listener (state_event_listener, "Gtk:AtkObject:state-change");
  /* TODO */
161
  add_signal_listener (active_descendant_event_listener,
                       "Gtk:AtkObject:active-descendant-changed");
161
  add_signal_listener (announcement_event_listener,
                       "Gtk:AtkObject:announcement");
161
  add_signal_listener (notification_event_listener,
                       "Gtk:AtkObject:notification");
161
  add_signal_listener (attribute_changed_event_listener,
                       "Gtk:AtkObject:attribute-changed");
161
  add_signal_listener (bounds_event_listener,
                       "Gtk:AtkComponent:bounds-changed");
161
  add_signal_listener (text_selection_changed_event_listener,
                       "Gtk:AtkText:text-selection-changed");
161
  add_signal_listener (text_changed_event_listener,
                       "Gtk:AtkText:text-changed");
161
  add_signal_listener (text_insert_event_listener,
                       "Gtk:AtkText:text-insert");
161
  add_signal_listener (text_remove_event_listener,
                       "Gtk:AtkText:text-remove");
161
  add_signal_listener (link_selected_event_listener,
                       "Gtk:AtkHypertext:link-selected");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkObject:visible-data-changed");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkSelection:selection-changed");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkText:text-attributes-changed");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkText:text-caret-moved");
161
  add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-inserted");
161
  add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-reordered");
161
  add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-deleted");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkTable:column-inserted");
161
  add_signal_listener (generic_event_listener,
                       "Gtk:AtkTable:column-reordered");
161
  add_signal_listener (generic_event_listener, "Gtk:AtkTable:column-deleted");
161
  add_signal_listener (generic_event_listener, "Gtk:AtkTable:model-changed");
161
  add_signal_listener (children_changed_event_listener, "Gtk:AtkObject:children-changed");
#if 0
  g_signal_connect (G_OBJECT (spi_global_app_data->root),
                    "children-changed::add",
                    (GCallback) toplevel_added_event_listener, NULL);
  g_signal_connect (G_OBJECT (spi_global_app_data->root),
                    "children-changed::remove",
                    (GCallback) toplevel_removed_event_listener, NULL);
#endif
  /*
   * May add the following listeners to implement preemptive key listening for GTK+
   *
   * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
   * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
   */
161
  atk_bridge_key_event_listener_id =
161
      atk_add_key_event_listener (spi_atk_bridge_key_listener, NULL);
}
/*---------------------------------------------------------------------------*/
/*
 * De-registers all ATK signal handlers.
 */
void
161
spi_atk_deregister_event_listeners (void)
{
  gint i;
161
  GArray *ids = listener_ids;
161
  listener_ids = NULL;
161
  if (atk_bridge_focus_tracker_id)
    {
161
      atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
161
      atk_bridge_focus_tracker_id = 0;
    }
161
  if (ids)
    {
5957
      for (i = 0; i < ids->len; i++)
        {
5796
          atk_remove_global_event_listener (g_array_index (ids, guint, i));
        }
161
      g_array_free (ids, TRUE);
    }
161
  if (atk_bridge_key_event_listener_id)
    {
      atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
      atk_bridge_key_event_listener_id = 0;
    }
161
}
/*---------------------------------------------------------------------------*/
/*
 * TODO This function seems out of place here.
 *
 * Emits fake deactivate signals on all top-level windows.
 * Used when shutting down AT-SPI, ensuring that all
 * windows have been removed on the client side.
 */
void
161
spi_atk_tidy_windows (void)
{
  AtkObject *root;
  gint n_children;
  gint i;
161
  root = atk_get_root ();
161
  n_children = atk_object_get_n_accessible_children (root);
491
  for (i = 0; i < n_children; i++)
    {
      AtkObject *child;
      AtkStateSet *stateset;
      const gchar *name;
330
      child = atk_object_ref_accessible_child (root, i);
330
      stateset = atk_object_ref_state_set (child);
330
      name = atk_object_get_name (child);
330
      if (atk_state_set_contains_state (stateset, ATK_STATE_ACTIVE))
        {
1
          emit_event (child, ITF_EVENT_WINDOW, "deactivate", NULL, 0, 0,
                      DBUS_TYPE_STRING_AS_STRING, name, append_basic);
        }
330
      g_object_unref (stateset);
330
      emit_event (child, ITF_EVENT_WINDOW, "destroy", NULL, 0, 0,
                  DBUS_TYPE_STRING_AS_STRING, name, append_basic);
330
      g_object_unref (child);
    }
161
}
gboolean
spi_event_is_subtype (gchar **needle, gchar **haystack)
{
  while (*haystack && **haystack)
    {
      if (g_strcmp0 (*needle, *haystack))
        return FALSE;
      needle++;
      haystack++;
    }
  return TRUE;
}
/*END------------------------------------------------------------------------*/