2.38.0
[platform/upstream/at-spi2-atk.git] / atk-adaptor / event.c
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 <string.h>
27 #include <ctype.h>
28
29 #include <atk/atk.h>
30 #include <droute/droute.h>
31 #include <atspi/atspi.h>
32
33 #include "bridge.h"
34 #include "accessible-register.h"
35
36 #include "spi-dbus.h"
37 #include "event.h"
38 #include "object.h"
39
40 static GArray *listener_ids = NULL;
41
42 static gint atk_bridge_key_event_listener_id;
43 static gint atk_bridge_focus_tracker_id;
44
45 GMainContext *spi_context = NULL;
46
47 /*---------------------------------------------------------------------------*/
48
49 #define ITF_EVENT_OBJECT   "org.a11y.atspi.Event.Object"
50 #define ITF_EVENT_WINDOW   "org.a11y.atspi.Event.Window"
51 #define ITF_EVENT_DOCUMENT "org.a11y.atspi.Event.Document"
52 #define ITF_EVENT_FOCUS    "org.a11y.atspi.Event.Focus"
53
54 /*---------------------------------------------------------------------------*/
55
56 typedef struct _SpiReentrantCallClosure 
57 {
58   DBusConnection *bus;
59   GMainLoop   *loop;
60   DBusMessage *reply;
61   guint timeout;
62 } SpiReentrantCallClosure;
63
64 static void
65 switch_main_context (GMainContext *cnx)
66 {
67   GList *list;
68
69   if (spi_global_app_data->server)
70     atspi_dbus_server_setup_with_g_main (spi_global_app_data->server, cnx);
71   atspi_dbus_connection_setup_with_g_main (spi_global_app_data->bus, cnx);
72   atspi_set_main_context (cnx);
73   for (list = spi_global_app_data->direct_connections; list; list = list->next)
74     atspi_dbus_connection_setup_with_g_main (list->data, cnx);
75
76   if (_atk_bridge_remove_pending_application_registration (spi_global_app_data))
77     _atk_bridge_schedule_application_registration (spi_global_app_data);
78 }
79
80 guint
81 spi_idle_add(GSourceFunc    function, gpointer       data)
82 {
83   GSource *source;
84   guint id;
85
86   source = g_idle_source_new ();
87   g_source_set_callback (source, function, data, NULL);
88   id = g_source_attach (source, spi_context);
89   g_source_unref (source);
90
91   return id;
92 }
93
94 guint
95 spi_timeout_add_seconds (gint interval, GSourceFunc function, gpointer    data)
96 {
97   GSource *source;
98   guint id;
99
100   source = g_timeout_source_new_seconds (interval);
101   g_source_set_callback (source, function, data, NULL);
102   id = g_source_attach (source, spi_context);
103   g_source_unref (source);
104
105   return id;
106 }
107
108 guint
109 spi_timeout_add_full (gint priority, guint interval, GSourceFunc function,
110                       gpointer data, GDestroyNotify notify)
111 {
112   GSource *source;
113   guint id;
114
115   source = g_timeout_source_new (interval);
116   g_source_set_priority (source, priority);
117   g_source_set_callback (source, function, data, notify);
118   id = g_source_attach (source, spi_context);
119   g_source_unref (source);
120
121   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
195                                                          AtspiDeviceEvent
196                                                          * key_event)
197 {
198   DBusMessage *message;
199   dbus_bool_t consumed = FALSE;
200
201   message =
202     dbus_message_new_method_call (SPI_DBUS_NAME_REGISTRY,
203                                   ATSPI_DBUS_PATH_DEC,
204                                   ATSPI_DBUS_INTERFACE_DEC,
205                                   "NotifyListenersSync");
206
207   if (spi_dbus_marshal_deviceEvent (message, key_event))
208     {
209       DBusMessage *reply =
210         send_and_allow_reentry (spi_global_app_data->bus, message);
211       if (reply)
212         {
213           DBusError error;
214           dbus_error_init (&error);
215           if (!dbus_message_get_args (reply, &error, DBUS_TYPE_BOOLEAN,
216               &consumed, DBUS_TYPE_INVALID))
217             {
218               /* TODO: print a warning */
219               dbus_error_free (&error);
220             }
221           dbus_message_unref (reply);
222         }
223     }
224   dbus_message_unref (message);
225   return consumed;
226 }
227
228 static void
229 spi_init_keystroke_from_atk_key_event (AtspiDeviceEvent * keystroke,
230                                        AtkKeyEventStruct * event)
231 {
232   keystroke->id = (dbus_int32_t) event->keyval;
233   keystroke->hw_code = (dbus_int16_t) event->keycode;
234   keystroke->timestamp = (dbus_uint32_t) event->timestamp;
235   keystroke->modifiers = (dbus_uint16_t) (event->state & 0xFFFF);
236   if (event->string)
237     {
238       gunichar c;
239
240       keystroke->event_string = g_strdup (event->string);
241       c = g_utf8_get_char_validated (event->string, -1);
242       if (c > 0 && g_unichar_isprint (c))
243         keystroke->is_text = TRUE;
244       else
245         keystroke->is_text = FALSE;
246     }
247   else
248     {
249       keystroke->event_string = g_strdup ("");
250       keystroke->is_text = FALSE;
251     }
252   switch (event->type)
253     {
254     case (ATK_KEY_EVENT_PRESS):
255       keystroke->type = ATSPI_KEY_PRESSED;
256       break;
257     case (ATK_KEY_EVENT_RELEASE):
258       keystroke->type = ATSPI_KEY_RELEASED;
259       break;
260     default:
261       keystroke->type = 0;
262       break;
263     }
264 #if 0
265   g_print
266     ("key_event type %d; val=%d code=%d modifiers=%x name=%s is_text=%d, time=%lx\n",
267      (int) keystroke->type, (int) keystroke->id, (int) keystroke->hw_code,
268      (int) keystroke->modifiers, keystroke->event_string,
269      (int) keystroke->is_text, (unsigned long) keystroke->timestamp);
270 #endif
271 }
272
273
274 static gint
275 spi_atk_bridge_key_listener (AtkKeyEventStruct * event, gpointer data)
276 {
277   gboolean result;
278   AtspiDeviceEvent key_event;
279
280   spi_init_keystroke_from_atk_key_event (&key_event, event);
281
282   result =
283     Accessibility_DeviceEventController_NotifyListenersSync (&key_event);
284
285   if (key_event.event_string)
286     g_free (key_event.event_string);
287
288   return result;
289 }
290
291 /*---------------------------------------------------------------------------*/
292
293 static const void *
294 validate_for_dbus (const gint type,
295               const void *val)
296 {
297   switch (type)
298     {
299       case DBUS_TYPE_STRING:
300       case DBUS_TYPE_OBJECT_PATH:
301            if (!val)
302               return "";
303            else if (!g_utf8_validate (val, -1, NULL))
304              {
305                g_warning ("atk-bridge: Received bad UTF-8 string when emitting event");
306                return "";
307                }
308            else
309               return val;
310       default:
311            return val;
312     }
313 }
314
315 static void
316 append_basic (DBusMessageIter *iter,
317               const char *type,
318               const void *val)
319 {
320   DBusMessageIter sub;
321
322   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &sub);
323
324     val = validate_for_dbus ((int) *type, val);
325     dbus_message_iter_append_basic(&sub, (int) *type, &val);
326
327   dbus_message_iter_close_container(iter, &sub);
328 }
329
330 static void
331 append_rect (DBusMessageIter *iter,
332              const char *type,
333              const void *val)
334 {
335   DBusMessageIter variant, sub;
336   const AtkRectangle *rect = (const AtkRectangle *) val;
337
338   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &variant);
339
340     dbus_message_iter_open_container (&variant, DBUS_TYPE_STRUCT, NULL, &sub);
341
342       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->x));
343       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->y));
344       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->width));
345       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->height));
346
347     dbus_message_iter_close_container (&variant, &sub);
348
349   dbus_message_iter_close_container(iter, &variant);
350 }
351
352 static void
353 append_object (DBusMessageIter *iter,
354                const char *type,
355                const void *val)
356 {
357   spi_object_append_v_reference (iter, ATK_OBJECT (val));
358 }
359
360 static gchar *
361 signal_name_to_dbus (const gchar *s)
362 {
363   gchar *ret = g_strdup (s);
364   gchar *t;
365
366   if (!ret)
367     return NULL;
368   ret [0] = toupper (ret [0]);
369   while ((t = strchr (ret, '-')) != NULL)
370   {
371     memmove (t, t + 1, strlen (t));
372     *t = toupper (*t);
373   }
374   return ret;
375 }
376
377 /*
378  * Converts names of the form "active-descendant-changed" to
379  * "ActiveDescendantChanged"
380  */
381 static gchar *
382 ensure_proper_format (const char *name)
383 {
384   gchar *ret = (gchar *) g_malloc (strlen (name) * 2 + 2);
385   gchar *p = ret;
386   gboolean need_upper = TRUE;
387
388   if (!ret)
389     return NULL;
390   while (*name)
391     {
392       if (need_upper)
393         {
394           *p++ = toupper (*name);
395           need_upper = FALSE;
396         }
397       else if (*name == '-')
398         need_upper = TRUE;
399       else if (*name == ':')
400         {
401           need_upper = TRUE;
402           *p++ = *name;
403         }
404       else
405         *p++ = *name;
406       name++;
407     }
408   *p = '\0';
409   return ret;
410 }
411
412 void
413 append_properties (GArray *properties, event_data *evdata)
414 {
415   GSList *ls;
416   gint i;
417
418   for (ls = evdata->properties; ls; ls = ls->next)
419   {
420     gboolean dup = FALSE;
421     for (i = 0; i < properties->len; i++)
422     {
423       if (ls->data == g_array_index (properties, AtspiPropertyDefinition *, i))
424       {
425         dup = TRUE;
426         break;
427       }
428     }
429     if (!dup)
430       g_array_append_val (properties, ls->data);
431   }
432 }
433
434 static gboolean
435 signal_is_needed (AtkObject *obj, const gchar *klass, const gchar *major,
436                   const gchar *minor, GArray **properties)
437 {
438   gchar *data [4];
439   event_data *evdata;
440   gboolean ret = FALSE;
441   GList *list;
442   GArray *props = NULL;
443
444   if (!spi_global_app_data->events_initialized)
445     return TRUE;
446
447   data [0] = ensure_proper_format (klass[0] ? klass + 21 : klass);
448   data [1] = ensure_proper_format (major);
449   data [2] = ensure_proper_format (minor);
450   data [3] = NULL;
451
452   /* Hack: Always pass events that update the cache.
453    * TODO: FOr 2.2, have at-spi2-core define a special "cache listener" for
454    * this instead, so that we don't send these if no one is listening */
455   if (!g_strcmp0 (data [1], "ChildrenChanged") ||
456       ((!g_strcmp0 (data [1], "PropertyChange")) &&
457        (!g_strcmp0 (data [2], "AccessibleName") ||
458         !g_strcmp0 (data [2], "AccessibleDescription") ||
459         !g_strcmp0 (data [2], "AccessibleParent") ||
460         !g_strcmp0 (data [2], "AccessibleRole"))) ||
461       !g_strcmp0 (data [1], "StateChanged"))
462   {
463     if (minor && !g_strcmp0 (minor, "defunct"))
464       ret = TRUE;
465     else
466     {
467       AtkStateSet *set = atk_object_ref_state_set (obj);
468       AtkState state = ((!g_strcmp0 (data[1], "ChildrenChanged")) ?
469                         ATK_STATE_MANAGES_DESCENDANTS : ATK_STATE_TRANSIENT);
470       ret = !atk_state_set_contains_state (set, state);
471       g_object_unref (set);
472     }
473   }
474
475   /* Hack: events such as "object::text-changed::insert:system" as
476      generated by Gecko */
477   data [2][strcspn (data [2], ":")] = '\0';
478
479   for (list = spi_global_app_data->events; list; list = list->next)
480     {
481       evdata = list->data;
482       if (spi_event_is_subtype (data, evdata->data))
483         {
484           ret = TRUE;
485           if (!props)
486           props = g_array_new (TRUE, TRUE, sizeof (AtspiPropertyDefinition *));
487           append_properties (props, evdata);
488         }
489     }
490
491   g_free (data [2]);
492   g_free (data [1]);
493   g_free (data [0]);
494   *properties = props;
495   return ret;
496 }
497
498 /* Convert a : to a / so that listeners can use arg0path to match only
499  *  * the prefix */
500 static char *
501 adapt_minor_for_dbus (const char *source)
502 {
503   gchar *ret = g_strdup (source);
504   int i = strcspn (ret, ":");
505   if (ret[i] == ':')
506     ret[i] = '/';
507   return ret;
508 }
509
510 /*
511  * Emits an AT-SPI event.
512  * AT-SPI events names are split into three parts:
513  * class:major:minor
514  * This is mapped onto D-Bus events as:
515  * D-Bus Interface:Signal Name:Detail argument
516  *
517  * Marshals a basic type into the 'any_data' attribute of
518  * the AT-SPI event.
519  */
520 static void 
521 emit_event (AtkObject  *obj,
522             const char *klass,
523             const char *major,
524             const char *minor,
525             dbus_int32_t detail1,
526             dbus_int32_t detail2,
527             const char *type,
528             const void *val,
529             void (*append_variant) (DBusMessageIter *, const char *, const void *))
530 {
531   DBusConnection *bus = spi_global_app_data->bus;
532   char *path;
533   char *minor_dbus;
534
535   gchar *cname;
536   DBusMessage *sig;
537   DBusMessageIter iter, iter_dict, iter_dict_entry;
538   GArray *properties = NULL;
539   
540   if (!klass) klass = "";
541   if (!major) major = "";
542   if (!minor) minor = "";
543   if (!type) type = "u";
544
545   if (!signal_is_needed (obj, klass, major, minor, &properties))
546     return;
547
548   path =  spi_register_object_to_path (spi_global_register, G_OBJECT (obj));
549   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   cname = signal_name_to_dbus (major);
557   sig = dbus_message_new_signal(path, klass, cname);
558
559   dbus_message_iter_init_append(sig, &iter);
560
561   minor_dbus = adapt_minor_for_dbus (minor);
562   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &minor_dbus);
563   g_free (minor_dbus);
564   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail1);
565   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail2);
566   append_variant (&iter, type, val);
567
568   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   if (minor == NULL || strcmp (minor, "defunct") != 0 || detail1 == 0)
572   {
573     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     dbus_message_iter_close_container (&iter, &iter_dict);
589
590   dbus_connection_send(bus, sig, NULL);
591   dbus_message_unref(sig);
592
593   if (g_strcmp0 (cname, "ChildrenChanged") != 0)
594     spi_object_lease_if_needed (G_OBJECT (obj));
595
596   g_free(cname);
597   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, gpointer data)
629 {
630   AtkObject *accessible;
631   AtkPropertyValues *values;
632
633   const gchar *pname = NULL;
634
635   AtkObject *otemp;
636   const gchar *s1;
637   gint i;
638
639   accessible = g_value_get_object (&param_values[0]);
640   values = (AtkPropertyValues *) g_value_get_pointer (&param_values[1]);
641
642   pname = values[0].property_name;
643
644   /* TODO Could improve this control statement by matching
645    * on only the end of the signal names,
646    */
647   if (strcmp (pname, "accessible-name") == 0)
648     {
649       s1 = atk_object_get_name (accessible);
650       if (s1 != NULL)
651         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
652                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
653     }
654   else if (strcmp (pname, "accessible-description") == 0)
655     {
656       s1 = atk_object_get_description (accessible);
657       if (s1 != NULL)
658         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
659                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
660     }
661   else if (strcmp (pname, "accessible-parent") == 0)
662     {
663       otemp = atk_object_get_parent (accessible);
664       if (otemp != NULL)
665         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
666                     "(so)", otemp, append_object);
667     }
668   else if (strcmp (pname, "accessible-role") == 0)
669     {
670       i = atk_object_get_role (accessible);
671       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
672                     DBUS_TYPE_UINT32_AS_STRING, GINT_TO_POINTER(i), append_basic);
673     }
674   else if (strcmp (pname, "accessible-table-summary") == 0)
675     {
676       otemp = atk_table_get_summary (ATK_TABLE (accessible));
677       if (otemp != NULL)
678         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
679                     "(so)", otemp, append_object);
680     }
681   else if (strcmp (pname, "accessible-table-column-header") == 0)
682     {
683       i = g_value_get_int (&(values->new_value));
684       otemp = atk_table_get_column_header (ATK_TABLE (accessible), i);
685       if (otemp != NULL)
686         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
687                     "(so)", otemp, append_object);
688     }
689   else if (strcmp (pname, "accessible-table-row-header") == 0)
690     {
691       i = g_value_get_int (&(values->new_value));
692       otemp = atk_table_get_row_header (ATK_TABLE (accessible), i);
693       if (otemp != NULL)
694         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
695                     "(so)", otemp, append_object);
696     }
697   else if (strcmp (pname, "accessible-table-row-description") == 0)
698     {
699       i = g_value_get_int (&(values->new_value));
700       s1 = atk_table_get_row_description (ATK_TABLE (accessible), i);
701       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
702                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
703     }
704   else if (strcmp (pname, "accessible-table-column-description") == 0)
705     {
706       i = g_value_get_int (&(values->new_value));
707       s1 = atk_table_get_column_description (ATK_TABLE (accessible), i);
708       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
709                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
710     }
711   else if (strcmp (pname, "accessible-table-caption-object") == 0)
712     {
713       otemp = atk_table_get_caption (ATK_TABLE (accessible));
714       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
715                   "(so)", otemp, append_object);
716     }
717   else
718     {
719       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
720             DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
721     }
722   return TRUE;
723 }
724
725 /*---------------------------------------------------------------------------*/
726
727 #define STATE_CHANGED "state-changed"
728
729 /*
730  * The state event listener handles 'Gtk:AtkObject:state-change' ATK signals
731  * and forwards them as object:state-changed:(param-name) AT-SPI events. Where
732  * the param-name is part of the ATK state-change signal.
733  */
734 static gboolean
735 state_event_listener (GSignalInvocationHint * signal_hint,
736                       guint n_param_values,
737                       const GValue * param_values, gpointer data)
738 {
739   AtkObject *accessible;
740   const gchar *pname;
741   guint detail1;
742
743   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
744   pname = g_value_get_string (&param_values[1]);
745
746   detail1 = (g_value_get_boolean (&param_values[2])) ? 1 : 0;
747   emit_event (accessible, ITF_EVENT_OBJECT, STATE_CHANGED, pname, detail1, 0,
748               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
749
750   if (!g_strcmp0 (pname, "defunct") && detail1)
751     spi_register_deregister_object (spi_global_register, G_OBJECT (accessible),
752                                     TRUE);
753   return TRUE;
754 }
755
756 /*---------------------------------------------------------------------------*/
757
758 /*
759  * The window event listener handles the following ATK signals and forwards
760  * them as AT-SPI events:
761  *
762  * window:create     -> window:create
763  * window:destroy    -> window:destroy
764  * window:minimize   -> window:minimize
765  * window:maximize   -> window:maximize
766  * window:activate   -> window:activate
767  * window:deactivate -> window:deactivate
768  */
769 static gboolean
770 window_event_listener (GSignalInvocationHint * signal_hint,
771                        guint n_param_values,
772                        const GValue * param_values, gpointer data)
773 {
774   AtkObject *accessible;
775   GSignalQuery signal_query;
776   const gchar *name, *s;
777
778   g_signal_query (signal_hint->signal_id, &signal_query);
779   name = signal_query.signal_name;
780
781   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
782   s = atk_object_get_name (accessible);
783   emit_event (accessible, ITF_EVENT_WINDOW, name, "", 0, 0,
784               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
785
786   return TRUE;
787 }
788
789 /*---------------------------------------------------------------------------*/
790
791 /* 
792  * The document event listener handles the following ATK signals
793  * and converts them to AT-SPI events:
794  *
795  * Gtk:AtkDocument:load-complete ->  document:load-complete
796  * Gtk:AtkDocument:load-stopped  ->  document:load-stopped
797  * Gtk:AtkDocument:reload        ->  document:reload
798  * Gtk:AtkDocument:page-changed  ->  document:page-changed
799  */
800 static gboolean
801 document_event_listener (GSignalInvocationHint * signal_hint,
802                          guint n_param_values,
803                          const GValue * param_values, gpointer data)
804 {
805   AtkObject *accessible;
806   GSignalQuery signal_query;
807   const gchar *name, *s;
808   gint detail1 = 0;
809
810   g_signal_query (signal_hint->signal_id, &signal_query);
811   name = signal_query.signal_name;
812
813   if (n_param_values > 0) // on the case of page-changed
814     if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
815       detail1 = g_value_get_int (&param_values[1]);
816
817   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
818   s = atk_object_get_name (accessible);
819   emit_event (accessible, ITF_EVENT_DOCUMENT, name, "", detail1, 0,
820               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
821
822   return TRUE;
823 }
824
825 /*---------------------------------------------------------------------------*/
826
827 /*
828  * Signal handler for  "Gtk:AtkComponent:bounds-changed". Converts
829  * this to an AT-SPI event - "object:bounds-changed".
830  */
831 static gboolean
832 bounds_event_listener (GSignalInvocationHint * signal_hint,
833                        guint n_param_values,
834                        const GValue * param_values, gpointer data)
835 {
836   AtkObject *accessible;
837   AtkRectangle *atk_rect;
838   GSignalQuery signal_query;
839   const gchar *name;
840
841   g_signal_query (signal_hint->signal_id, &signal_query);
842   name = signal_query.signal_name;
843
844   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
845
846   if (G_VALUE_HOLDS_BOXED (param_values + 1))
847   {
848     atk_rect = g_value_get_boxed (param_values + 1);
849
850     emit_event (accessible, ITF_EVENT_OBJECT, name, "", 0, 0,
851                 "(iiii)", atk_rect, append_rect);
852   }
853   return TRUE;
854 }
855
856 /*---------------------------------------------------------------------------*/
857
858 /* 
859  * Handles the ATK signal 'Gtk:AtkObject:active-descendant-changed' and 
860  * converts it to the AT-SPI signal - 'object:active-descendant-changed'.
861  *
862  */
863 static gboolean
864 active_descendant_event_listener (GSignalInvocationHint * signal_hint,
865                                   guint n_param_values,
866                                   const GValue * param_values, gpointer data)
867 {
868   AtkObject *accessible;
869   AtkObject *child;
870   GSignalQuery signal_query;
871   const gchar *name;
872   gint detail1;
873
874   g_signal_query (signal_hint->signal_id, &signal_query);
875   name = signal_query.signal_name;
876
877   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
878   child = ATK_OBJECT (g_value_get_pointer (&param_values[1]));
879   g_return_val_if_fail (ATK_IS_OBJECT (child), TRUE);
880
881   detail1 = atk_object_get_index_in_parent (child);
882
883   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, 0,
884               "(so)", child, append_object);
885   return TRUE;
886 }
887
888 /*---------------------------------------------------------------------------*/
889
890 /* 
891  * Handles the ATK signal 'Gtk:AtkHypertext:link-selected' and
892  * converts it to the AT-SPI signal - 'object:link-selected'
893  *
894  */
895 static gboolean
896 link_selected_event_listener (GSignalInvocationHint * signal_hint,
897                               guint n_param_values,
898                               const GValue * param_values, gpointer data)
899 {
900   AtkObject *accessible;
901   GSignalQuery signal_query;
902   const gchar *name, *minor;
903   gint detail1 = 0;
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   minor = g_quark_to_string (signal_hint->detail);
910
911   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
912     detail1 = g_value_get_int (&param_values[1]);
913
914   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, 0,
915               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
916   return TRUE;
917 }
918
919 /*---------------------------------------------------------------------------*/
920
921 /* 
922  * Handles the ATK signal 'Gtk:AtkText:text-changed' and
923  * converts it to the AT-SPI signal - 'object:text-changed'
924  * This signal is deprecated by Gtk:AtkText:text-insert
925  * and Gtk:AtkText:text-remove
926  *
927  */
928 static gboolean
929 text_changed_event_listener (GSignalInvocationHint * signal_hint,
930                              guint n_param_values,
931                              const GValue * param_values, gpointer data)
932 {
933   AtkObject *accessible;
934   GSignalQuery signal_query;
935   const gchar *name, *minor;
936   gchar *selected;
937   gint detail1 = 0, detail2 = 0;
938
939   g_signal_query (signal_hint->signal_id, &signal_query);
940   name = signal_query.signal_name;
941
942   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
943   minor = g_quark_to_string (signal_hint->detail);
944
945   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
946     detail1 = g_value_get_int (&param_values[1]);
947
948   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
949     detail2 = g_value_get_int (&param_values[2]);
950
951   selected =
952     atk_text_get_text (ATK_TEXT (accessible), detail1, detail1 + detail2);
953
954   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
955               DBUS_TYPE_STRING_AS_STRING, selected, append_basic);
956   g_free (selected);
957
958   return TRUE;
959 }
960
961 /* 
962  * Handles the ATK signal 'Gtk:AtkText:text-insert' and
963  * converts it to the AT-SPI signal - 'object:text-changed'
964  *
965  */
966 static gboolean
967 text_insert_event_listener (GSignalInvocationHint * signal_hint,
968                             guint n_param_values,
969                             const GValue * param_values, gpointer data)
970 {
971   AtkObject *accessible;
972   guint text_changed_signal_id;
973   GSignalQuery signal_query;
974   const gchar *name;
975   const gchar *minor_raw, *text;
976   gchar *minor;
977   gint detail1 = 0, detail2 = 0;
978
979   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
980   /* Get signal name for 'Gtk:AtkText:text-changed' so
981    * we convert it to the AT-SPI signal - 'object:text-changed'
982    */
983   text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
984   g_signal_query (text_changed_signal_id, &signal_query);
985   name = signal_query.signal_name;
986
987
988   /* Add the insert and keep any detail coming from atk */
989   minor_raw = g_quark_to_string (signal_hint->detail);
990   if (minor_raw)
991     minor = g_strconcat ("insert:", minor_raw, NULL);
992   else
993     minor = g_strdup ("insert");
994
995   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
996     detail1 = g_value_get_int (&param_values[1]);
997
998   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
999     detail2 = g_value_get_int (&param_values[2]);
1000
1001   if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
1002     text = g_value_get_string (&param_values[3]);
1003   else
1004     text = "";
1005
1006   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
1007               DBUS_TYPE_STRING_AS_STRING, text, append_basic);
1008   g_free (minor);
1009   return TRUE;
1010 }
1011
1012 /* 
1013  * Handles the ATK signal 'Gtk:AtkText:text-remove' and
1014  * converts it to the AT-SPI signal - 'object:text-changed'
1015  *
1016  */
1017 static gboolean
1018 text_remove_event_listener (GSignalInvocationHint * signal_hint,
1019                             guint n_param_values,
1020                             const GValue * param_values, gpointer data)
1021 {
1022   AtkObject *accessible;
1023   guint text_changed_signal_id;
1024   GSignalQuery signal_query;
1025   const gchar *name;
1026   const gchar *minor_raw, *text;
1027   gchar *minor;
1028   gint detail1 = 0, detail2 = 0;
1029
1030   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
1031   /* Get signal name for 'Gtk:AtkText:text-changed' so
1032    * we convert it to the AT-SPI signal - 'object:text-changed'
1033    */
1034   text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
1035   g_signal_query (text_changed_signal_id, &signal_query);
1036   name = signal_query.signal_name;
1037
1038   minor_raw = g_quark_to_string (signal_hint->detail);
1039
1040   /* Add the delete and keep any detail coming from atk */
1041   if (minor_raw)
1042     minor = g_strconcat ("delete:", minor_raw, NULL);
1043   else
1044     minor = g_strdup ("delete");
1045
1046   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
1047     detail1 = g_value_get_int (&param_values[1]);
1048
1049   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
1050     detail2 = g_value_get_int (&param_values[2]);
1051
1052   if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
1053     text = g_value_get_string (&param_values[3]);
1054   else
1055     text = "";
1056
1057   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
1058               DBUS_TYPE_STRING_AS_STRING, text, append_basic);
1059   g_free (minor);
1060   return TRUE;
1061 }
1062
1063
1064 /*---------------------------------------------------------------------------*/
1065
1066 /* 
1067  * Handles the ATK signal 'Gtk:AtkText:text-selection-changed' and
1068  * converts it to the AT-SPI signal - 'object:text-selection-changed'
1069  *
1070  */
1071 static gboolean
1072 text_selection_changed_event_listener (GSignalInvocationHint * signal_hint,
1073                                        guint n_param_values,
1074                                        const GValue * param_values,
1075                                        gpointer data)
1076 {
1077   AtkObject *accessible;
1078   GSignalQuery signal_query;
1079   const gchar *name, *minor;
1080
1081   g_signal_query (signal_hint->signal_id, &signal_query);
1082   name = signal_query.signal_name;
1083
1084   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
1085   minor = g_quark_to_string (signal_hint->detail);
1086
1087   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, 0, 0,
1088               DBUS_TYPE_STRING_AS_STRING, "", append_basic);
1089   return TRUE;
1090 }
1091
1092 /*---------------------------------------------------------------------------*/
1093
1094 /*
1095  * Children changed signal converter and forwarder.
1096  *
1097  * Klass (Interface) org.a11y.atspi.Event.Object
1098  * Major is the signal name.
1099  * Minor is 'add' or 'remove'
1100  * detail1 is the index.
1101  * detail2 is 0.
1102  * any_data is the child reference.
1103  */
1104 static gboolean
1105 children_changed_event_listener (GSignalInvocationHint * signal_hint,
1106                                  guint n_param_values,
1107                                  const GValue * param_values, gpointer data)
1108 {
1109   GSignalQuery signal_query;
1110   const gchar *name, *minor;
1111   gint detail1 = 0, detail2 = 0;
1112
1113   AtkObject *accessible, *ao=NULL;
1114   gpointer child;
1115   AtkStateSet *set;
1116   gboolean ret;
1117
1118   g_signal_query (signal_hint->signal_id, &signal_query);
1119   name = signal_query.signal_name;
1120
1121   /* If the accessible is on STATE_MANAGES_DESCENDANTS state,
1122      children-changed signal are not forwarded. */
1123   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
1124   set = atk_object_ref_state_set (accessible);
1125   ret = atk_state_set_contains_state (set, ATK_STATE_MANAGES_DESCENDANTS);
1126   g_object_unref (set);
1127
1128   if (ret)
1129     return TRUE;
1130
1131   minor = g_quark_to_string (signal_hint->detail);
1132
1133   detail1 = g_value_get_uint (param_values + 1);
1134   child = g_value_get_pointer (param_values + 2);
1135
1136   if (ATK_IS_OBJECT (child))
1137     {
1138       ao = ATK_OBJECT (child);
1139       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
1140                   "(so)", ao, append_object);
1141     }
1142   else if ((minor != NULL) && (strcmp (minor, "add") == 0))
1143     {
1144       ao = atk_object_ref_accessible_child (accessible, 
1145                                             detail1);
1146       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
1147                   "(so)", ao, append_object);
1148       g_object_unref (ao);
1149     }
1150   else
1151     {
1152       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
1153                   "(so)", ao, append_object);
1154     }
1155  
1156   return TRUE;
1157 }
1158
1159 /*---------------------------------------------------------------------------*/
1160
1161 /*
1162  * Generic signal converter and forwarder.
1163  *
1164  * Klass (Interface) org.a11y.atspi.Event.Object
1165  * Major is the signal name.
1166  * Minor is NULL.
1167  * detail1 is 0.
1168  * detail2 is 0.
1169  * any_data is NULL.
1170  */
1171 static gboolean
1172 generic_event_listener (GSignalInvocationHint * signal_hint,
1173                         guint n_param_values,
1174                         const GValue * param_values, gpointer data)
1175 {
1176   AtkObject *accessible;
1177   GSignalQuery signal_query;
1178   const gchar *name;
1179   int detail1 = 0, detail2 = 0;
1180
1181   g_signal_query (signal_hint->signal_id, &signal_query);
1182   name = signal_query.signal_name;
1183
1184   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
1185
1186   if (n_param_values > 1 && G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
1187     detail1 = g_value_get_int (&param_values[1]);
1188
1189   if (n_param_values > 2 && G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
1190     detail2 = g_value_get_int (&param_values[2]);
1191
1192   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, detail2,
1193               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
1194   return TRUE;
1195 }
1196
1197 /*---------------------------------------------------------------------------*/
1198
1199 /*
1200  * Registers the provided function as a handler for the given signal name
1201  * and stores the signal id returned so that the function may be
1202  * de-registered later.
1203  */
1204 static guint
1205 add_signal_listener (GSignalEmissionHook listener, const char *signal_name)
1206 {
1207   guint id;
1208
1209   id = atk_add_global_event_listener (listener, signal_name);
1210
1211   if (id > 0) /* id == 0 is a failure */
1212     g_array_append_val (listener_ids, id);
1213
1214   return id;
1215 }
1216
1217 /*
1218  * Initialization for the signal handlers.
1219  *
1220  * Registers all required signal handlers.
1221  */
1222 void
1223 spi_atk_register_event_listeners (void)
1224 {
1225   /*
1226    * Kludge to make sure the Atk interface types are registered, otherwise
1227    * the AtkText signal handlers below won't get registered
1228    */
1229   GObject *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
1230   AtkObject *bo = atk_no_op_object_new (ao);
1231   guint id = 0;
1232
1233   g_object_unref (G_OBJECT (bo));
1234   g_object_unref (ao);
1235
1236   if (listener_ids)
1237   {
1238     g_warning ("atk_bridge: spi_atk-register_event_listeners called multiple times");
1239     return;
1240   }
1241
1242   /* Register for focus event notifications, and register app with central registry  */
1243   listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
1244
1245   atk_bridge_focus_tracker_id = atk_add_focus_tracker (focus_tracker);
1246
1247   add_signal_listener (property_event_listener,
1248                        "Gtk:AtkObject:property-change");
1249
1250   /* window events: we tentative try to register using the old format */
1251   id = add_signal_listener (window_event_listener, "window:create");
1252
1253   if (id != 0)
1254     {
1255       /* If we are able to register using the old format, we assume
1256        * that the ATK implementor is managing window events without
1257        * AtkWindow. We can't use the opposite test because after
1258        * including AtkWindow on ATK you would be able to register to
1259        * that event, although the ATK implementor could or not use it.
1260        */
1261
1262       add_signal_listener (window_event_listener, "window:destroy");
1263       add_signal_listener (window_event_listener, "window:minimize");
1264       add_signal_listener (window_event_listener, "window:maximize");
1265       add_signal_listener (window_event_listener, "window:restore");
1266       add_signal_listener (window_event_listener, "window:activate");
1267       add_signal_listener (window_event_listener, "window:deactivate");
1268     }
1269   else
1270     {
1271       add_signal_listener (window_event_listener, "Atk:AtkWindow:create");
1272       add_signal_listener (window_event_listener, "Atk:AtkWindow:destroy");
1273       add_signal_listener (window_event_listener, "Atk:AtkWindow:minimize");
1274       add_signal_listener (window_event_listener, "Atk:AtkWindow:maximize");
1275       add_signal_listener (window_event_listener, "Atk:AtkWindow:restore");
1276       add_signal_listener (window_event_listener, "Atk:AtkWindow:activate");
1277       add_signal_listener (window_event_listener, "Atk:AtkWindow:deactivate");
1278     }
1279
1280   add_signal_listener (document_event_listener,
1281                        "Gtk:AtkDocument:load-complete");
1282   add_signal_listener (document_event_listener, "Gtk:AtkDocument:reload");
1283   add_signal_listener (document_event_listener,
1284                        "Gtk:AtkDocument:load-stopped");
1285   add_signal_listener (document_event_listener,
1286                        "Gtk:AtkDocument:page-changed");
1287   /* TODO Fake this event on the client side */
1288   add_signal_listener (state_event_listener, "Gtk:AtkObject:state-change");
1289   /* TODO */
1290   add_signal_listener (active_descendant_event_listener,
1291                        "Gtk:AtkObject:active-descendant-changed");
1292   add_signal_listener (bounds_event_listener,
1293                        "Gtk:AtkComponent:bounds-changed");
1294   add_signal_listener (text_selection_changed_event_listener,
1295                        "Gtk:AtkText:text-selection-changed");
1296   add_signal_listener (text_changed_event_listener,
1297                        "Gtk:AtkText:text-changed");
1298   add_signal_listener (text_insert_event_listener,
1299                        "Gtk:AtkText:text-insert");
1300   add_signal_listener (text_remove_event_listener,
1301                        "Gtk:AtkText:text-remove");
1302   add_signal_listener (link_selected_event_listener,
1303                        "Gtk:AtkHypertext:link-selected");
1304   add_signal_listener (generic_event_listener,
1305                        "Gtk:AtkObject:visible-data-changed");
1306   add_signal_listener (generic_event_listener,
1307                        "Gtk:AtkSelection:selection-changed");
1308   add_signal_listener (generic_event_listener,
1309                        "Gtk:AtkText:text-attributes-changed");
1310   add_signal_listener (generic_event_listener,
1311                        "Gtk:AtkText:text-caret-moved");
1312   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-inserted");
1313   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-reordered");
1314   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-deleted");
1315   add_signal_listener (generic_event_listener,
1316                        "Gtk:AtkTable:column-inserted");
1317   add_signal_listener (generic_event_listener,
1318                        "Gtk:AtkTable:column-reordered");
1319   add_signal_listener (generic_event_listener, "Gtk:AtkTable:column-deleted");
1320   add_signal_listener (generic_event_listener, "Gtk:AtkTable:model-changed");
1321   add_signal_listener (children_changed_event_listener, "Gtk:AtkObject:children-changed");
1322
1323 #if 0
1324   g_signal_connect (G_OBJECT (spi_global_app_data->root),
1325                     "children-changed::add",
1326                     (GCallback) toplevel_added_event_listener, NULL);
1327
1328   g_signal_connect (G_OBJECT (spi_global_app_data->root),
1329                     "children-changed::remove",
1330                     (GCallback) toplevel_removed_event_listener, NULL);
1331 #endif
1332
1333   /*
1334    * May add the following listeners to implement preemptive key listening for GTK+
1335    *
1336    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
1337    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
1338    */
1339   atk_bridge_key_event_listener_id =
1340     atk_add_key_event_listener (spi_atk_bridge_key_listener, NULL);
1341 }
1342
1343 /*---------------------------------------------------------------------------*/
1344
1345 /* 
1346  * De-registers all ATK signal handlers.
1347  */
1348 void
1349 spi_atk_deregister_event_listeners (void)
1350 {
1351   gint i;
1352   GArray *ids = listener_ids;
1353   listener_ids = NULL;
1354
1355   if (atk_bridge_focus_tracker_id)
1356   {
1357     atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
1358     atk_bridge_focus_tracker_id = 0;
1359   }
1360
1361   if (ids)
1362     {
1363       for (i = 0; i < ids->len; i++)
1364         {
1365           atk_remove_global_event_listener (g_array_index (ids, guint, i));
1366         }
1367       g_array_free (ids, TRUE);
1368     }
1369
1370   if (atk_bridge_key_event_listener_id)
1371   {
1372     atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
1373     atk_bridge_key_event_listener_id = 0;
1374   }
1375 }
1376
1377 /*---------------------------------------------------------------------------*/
1378
1379 /*
1380  * TODO This function seems out of place here.
1381  *
1382  * Emits fake deactivate signals on all top-level windows.
1383  * Used when shutting down AT-SPI, ensuring that all
1384  * windows have been removed on the client side.
1385  */
1386 void
1387 spi_atk_tidy_windows (void)
1388 {
1389   AtkObject *root;
1390   gint n_children;
1391   gint i;
1392
1393   root = atk_get_root ();
1394   n_children = atk_object_get_n_accessible_children (root);
1395   for (i = 0; i < n_children; i++)
1396     {
1397       AtkObject *child;
1398       AtkStateSet *stateset;
1399       const gchar *name;
1400
1401       child = atk_object_ref_accessible_child (root, i);
1402       stateset = atk_object_ref_state_set (child);
1403
1404       name = atk_object_get_name (child);
1405       if (atk_state_set_contains_state (stateset, ATK_STATE_ACTIVE))
1406         {
1407           emit_event (child, ITF_EVENT_WINDOW, "deactivate", NULL, 0, 0,
1408                       DBUS_TYPE_STRING_AS_STRING, name, append_basic);
1409         }
1410       g_object_unref (stateset);
1411
1412       emit_event (child, ITF_EVENT_WINDOW, "destroy", NULL, 0, 0,
1413                   DBUS_TYPE_STRING_AS_STRING, name, append_basic);
1414       g_object_unref (child);
1415     }
1416 }
1417
1418 gboolean
1419 spi_event_is_subtype (gchar **needle, gchar **haystack)
1420 {
1421   while (*haystack && **haystack)
1422     {
1423       if (g_strcmp0 (*needle, *haystack))
1424         return FALSE;
1425       needle++;
1426       haystack++;
1427     }
1428   return TRUE;
1429 }
1430
1431 /*END------------------------------------------------------------------------*/