Translate child changed ATK signals to AT-SPI.
[platform/core/uifw/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 2008, 2009, Codethink Ltd.
6  * Copyright 2001, 2002, 2003 Sun Microsystems Inc.,
7  * Copyright 2001, 2002, 2003 Ximian, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include <string.h>
26
27 #include <atk/atk.h>
28 #include <droute/droute.h>
29
30 #include "bridge.h"
31 #include "accessible-register.h"
32
33 #include "common/spi-dbus.h"
34
35 static GArray *listener_ids = NULL;
36
37 static gint atk_bridge_key_event_listener_id;
38 static gint atk_bridge_focus_tracker_id;
39
40 /*---------------------------------------------------------------------------*/
41
42 #define ITF_EVENT_OBJECT   "org.freedesktop.atspi.Event.Object"
43 #define ITF_EVENT_WINDOW   "org.freedesktop.atspi.Event.Window"
44 #define ITF_EVENT_DOCUMENT "org.freedesktop.atspi.Event.Document"
45 #define ITF_EVENT_FOCUS    "org.freedesktop.atspi.Event.Focus"
46
47 /*---------------------------------------------------------------------------*/
48
49 static void
50 set_reply (DBusPendingCall * pending, void *user_data)
51 {
52   void **replyptr = (void **) user_data;
53
54   *replyptr = dbus_pending_call_steal_reply (pending);
55 }
56
57 static DBusMessage *
58 send_and_allow_reentry (DBusConnection * bus, DBusMessage * message)
59 {
60   DBusPendingCall *pending;
61   DBusMessage *reply = NULL;
62
63   if (!dbus_connection_send_with_reply (bus, message, &pending, -1))
64     {
65       return NULL;
66     }
67   dbus_pending_call_set_notify (pending, set_reply, (void *) &reply, NULL);
68   while (!reply)
69     {
70       if (!dbus_connection_read_write_dispatch (bus, -1))
71         return NULL;
72     }
73   return reply;
74 }
75
76 /*---------------------------------------------------------------------------*/
77
78 /*
79  * Functionality related to sending device events from the application.
80  *
81  * This is used for forwarding key events on to the registry daemon.
82  */
83
84 static gboolean
85 Accessibility_DeviceEventController_NotifyListenersSync (const
86                                                          Accessibility_DeviceEvent
87                                                          * key_event)
88 {
89   DBusMessage *message;
90   DBusError error;
91   dbus_bool_t consumed = FALSE;
92
93   message =
94     dbus_message_new_method_call (SPI_DBUS_NAME_REGISTRY,
95                                   SPI_DBUS_PATH_DEC,
96                                   SPI_DBUS_INTERFACE_DEC,
97                                   "NotifyListenersSync");
98
99   dbus_error_init (&error);
100   if (spi_dbus_marshal_deviceEvent (message, key_event))
101     {
102       DBusMessage *reply =
103         send_and_allow_reentry (spi_global_app_data->bus, message);
104       if (reply)
105         {
106           DBusError error;
107           dbus_error_init (&error);
108           dbus_message_get_args (reply, &error, DBUS_TYPE_BOOLEAN, &consumed,
109                                  DBUS_TYPE_INVALID);
110           dbus_message_unref (reply);
111         }
112     }
113   dbus_message_unref (message);
114   return consumed;
115 }
116
117 static void
118 spi_init_keystroke_from_atk_key_event (Accessibility_DeviceEvent * keystroke,
119                                        AtkKeyEventStruct * event)
120 {
121   keystroke->id = (dbus_int32_t) event->keyval;
122   keystroke->hw_code = (dbus_int16_t) event->keycode;
123   keystroke->timestamp = (dbus_uint32_t) event->timestamp;
124   keystroke->modifiers = (dbus_uint16_t) (event->state & 0xFFFF);
125   if (event->string)
126     {
127       gunichar c;
128
129       keystroke->event_string = g_strdup (event->string);
130       c = g_utf8_get_char_validated (event->string, -1);
131       if (c > 0 && g_unichar_isprint (c))
132         keystroke->is_text = TRUE;
133       else
134         keystroke->is_text = FALSE;
135     }
136   else
137     {
138       keystroke->event_string = g_strdup ("");
139       keystroke->is_text = FALSE;
140     }
141   switch (event->type)
142     {
143     case (ATK_KEY_EVENT_PRESS):
144       keystroke->type = Accessibility_KEY_PRESSED_EVENT;
145       break;
146     case (ATK_KEY_EVENT_RELEASE):
147       keystroke->type = Accessibility_KEY_RELEASED_EVENT;
148       break;
149     default:
150       keystroke->type = 0;
151       break;
152     }
153 #if 0
154   g_print
155     ("key_event type %d; val=%d code=%d modifiers=%x name=%s is_text=%d, time=%lx\n",
156      (int) keystroke->type, (int) keystroke->id, (int) keystroke->hw_code,
157      (int) keystroke->modifiers, keystroke->event_string,
158      (int) keystroke->is_text, (unsigned long) keystroke->timestamp);
159 #endif
160 }
161
162
163 static gint
164 spi_atk_bridge_key_listener (AtkKeyEventStruct * event, gpointer data)
165 {
166   gboolean result;
167   Accessibility_DeviceEvent key_event;
168
169   spi_init_keystroke_from_atk_key_event (&key_event, event);
170
171   result =
172     Accessibility_DeviceEventController_NotifyListenersSync (&key_event);
173
174   if (key_event.event_string)
175     g_free (key_event.event_string);
176
177   return result;
178 }
179
180 /*---------------------------------------------------------------------------*/
181
182 static gchar *
183 convert_signal_name (const gchar * s)
184 {
185   gchar *ret = g_strdup (s);
186   gchar *t;
187
188   if (!ret)
189     return NULL;
190   ret[0] = toupper (ret[0]);
191   while ((t = strchr (ret, '-')) != NULL)
192     {
193       memmove (t, t + 1, strlen (t));
194       *t = toupper (*t);
195     }
196   return ret;
197 }
198
199 static const void *
200 replace_null (const gint type,
201               const void *val)
202 {
203   switch (type)
204     {
205       case DBUS_TYPE_STRING:
206       case DBUS_TYPE_OBJECT_PATH:
207            if (!val)
208               return "";
209            else
210               return val;
211       default:
212            return val;
213     }
214 }
215
216 static void
217 append_basic (DBusMessageIter *iter,
218               const char *type,
219               const void *val)
220 {
221   DBusMessageIter sub;
222
223   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &sub);
224
225     val = replace_null ((int) *type, val);
226     dbus_message_iter_append_basic(&sub, (int) *type, &val);
227
228   dbus_message_iter_close_container(iter, &sub);
229 }
230
231 static void
232 append_rect (DBusMessageIter *iter,
233              const char *type,
234              const void *val)
235 {
236   DBusMessageIter variant, sub;
237   const AtkRectangle *rect = (const AtkRectangle *) val;
238
239   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &variant);
240
241     dbus_message_iter_open_container (&variant, DBUS_TYPE_STRUCT, NULL, &sub);
242
243       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->x));
244       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->y));
245       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->width));
246       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->height));
247
248     dbus_message_iter_close_container (&variant, &sub);
249
250   dbus_message_iter_close_container(iter, &variant);
251 }
252
253 static void
254 append_object (DBusMessageIter *iter,
255                const char *type,
256                const void *val)
257 {
258   spi_object_append_v_reference (iter, ATK_OBJECT (val));
259 }
260
261 /*
262  * Emits an AT-SPI event.
263  * AT-SPI events names are split into three parts:
264  * class:major:minor
265  * This is mapped onto D-Bus events as:
266  * D-Bus Interface:Signal Name:Detail argument
267  *
268  * Marshals a basic type into the 'any_data' attribute of
269  * the AT-SPI event.
270  */
271 static void 
272 emit_event (AtkObject  *obj,
273             const char *klass,
274             const char *major,
275             const char *minor,
276             dbus_int32_t detail1,
277             dbus_int32_t detail2,
278             const char *type,
279             const void *val,
280             void (*append_variant) (DBusMessageIter *, const char *, const void *))
281 {
282   DBusConnection *bus = spi_global_app_data->bus;
283   const char *path =  spi_register_object_to_path (spi_global_register,
284                                                    G_OBJECT (obj));
285
286   gchar *cname, *t;
287   DBusMessage *sig;
288   DBusMessageIter iter, iter_struct;
289   
290   if (!klass) klass = "";
291   if (!major) major = "";
292   if (!minor) minor = "";
293   if (!type) type = "u";
294
295   /*
296    * This is very annoying, but as '-' isn't a legal signal
297    * name in D-Bus (Why not??!?) The names need converting
298    * on this side, and again on the client side.
299    */
300   cname = g_strdup(major);
301   while ((t = strchr(cname, '-')) != NULL) *t = '_';
302   sig = dbus_message_new_signal(path, klass, cname);
303   g_free(cname);
304
305   dbus_message_iter_init_append(sig, &iter);
306
307   spi_object_append_reference (&iter, spi_global_app_data->root);
308   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &minor);
309   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail1);
310   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail2);
311
312   append_variant (&iter, type, val);
313
314   dbus_connection_send(bus, sig, NULL);
315   dbus_message_unref(sig);
316 }
317
318 /*---------------------------------------------------------------------------*/
319
320 /*
321  * The focus listener handles the ATK 'focus' signal and forwards it
322  * as the AT-SPI event, 'focus:'
323  */
324 static void
325 focus_tracker (AtkObject * accessible)
326 {
327   emit_event (accessible, ITF_EVENT_FOCUS, "focus", "", 0, 0,
328               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
329 }
330
331 /*---------------------------------------------------------------------------*/
332
333 #define PCHANGE "property_change"
334
335 /* 
336  * This handler handles the following ATK signals and
337  * converts them to AT-SPI events:
338  *  
339  * Gtk:AtkObject:property-change -> object:property-change:(property-name)
340  *
341  * The property-name is part of the ATK property-change signal.
342  */
343 static gboolean
344 property_event_listener (GSignalInvocationHint * signal_hint,
345                          guint n_param_values,
346                          const GValue * param_values, gpointer data)
347 {
348   AtkObject *accessible;
349   AtkPropertyValues *values;
350
351   const gchar *pname = NULL;
352
353   AtkObject *otemp;
354   const gchar *s1, s2;
355   gint i;
356
357   accessible = g_value_get_object (&param_values[0]);
358   values = (AtkPropertyValues *) g_value_get_pointer (&param_values[1]);
359
360   pname = values[0].property_name;
361
362   /* TODO Could improve this control statement by matching
363    * on only the end of the signal names,
364    */
365   if (strcmp (pname, "accessible-name") == 0)
366     {
367       s1 = atk_object_get_name (accessible);
368       if (s1 != NULL)
369         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
370                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
371     }
372   if (strcmp (pname, "accessible-description") == 0)
373     {
374       s1 = atk_object_get_description (accessible);
375       if (s1 != NULL)
376         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
377                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
378     }
379   if (strcmp (pname, "accessible-parent") == 0)
380     {
381       otemp = atk_object_get_parent (accessible);
382       if (otemp != NULL)
383         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
384                     "(so)", otemp, append_object);
385     }
386   if (strcmp (pname, "accessible-table-summary") == 0)
387     {
388       otemp = atk_table_get_summary (ATK_TABLE (accessible));
389       if (otemp != NULL)
390         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
391                     "(so)", otemp, append_object);
392     }
393   else if (strcmp (pname, "accessible-table-column-header") == 0)
394     {
395       i = g_value_get_int (&(values->new_value));
396       otemp = atk_table_get_column_header (ATK_TABLE (accessible), i);
397       if (otemp != NULL)
398         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
399                     "(so)", otemp, append_object);
400     }
401   else if (strcmp (pname, "accessible-table-row-header") == 0)
402     {
403       i = g_value_get_int (&(values->new_value));
404       otemp = atk_table_get_row_header (ATK_TABLE (accessible), i);
405       if (otemp != NULL)
406         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
407                     "(so)", otemp, append_object);
408     }
409   else if (strcmp (pname, "accessible-table-row-description") == 0)
410     {
411       i = g_value_get_int (&(values->new_value));
412       s1 = atk_table_get_row_description (ATK_TABLE (accessible), i);
413       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
414                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
415     }
416   else if (strcmp (pname, "accessible-table-column-description") == 0)
417     {
418       i = g_value_get_int (&(values->new_value));
419       s1 = atk_table_get_column_description (ATK_TABLE (accessible), i);
420       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
421                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
422     }
423   else if (strcmp (pname, "accessible-table-caption-object") == 0)
424     {
425       otemp = atk_table_get_caption (ATK_TABLE (accessible));
426       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
427                   "(so)", otemp, append_object);
428     }
429   else
430     {
431       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
432             DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
433     }
434   return TRUE;
435 }
436
437 /*---------------------------------------------------------------------------*/
438
439 #define STATE_CHANGED "state-changed"
440
441 /*
442  * The state event listener handles 'Gtk:AtkObject:state-change' ATK signals
443  * and forwards them as object:state-changed:(param-name) AT-SPI events. Where
444  * the param-name is part of the ATK state-change signal.
445  */
446 static gboolean
447 state_event_listener (GSignalInvocationHint * signal_hint,
448                       guint n_param_values,
449                       const GValue * param_values, gpointer data)
450 {
451   AtkObject *accessible;
452   gchar *pname;
453   guint detail1;
454
455   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
456   pname = g_strdup (g_value_get_string (&param_values[1]));
457
458   /* TODO - Possibly ignore a change to the 'defunct' state.
459    * This is because without reference counting defunct objects should be removed.
460    */
461   detail1 = (g_value_get_boolean (&param_values[2])) ? 1 : 0;
462   emit_event (accessible, ITF_EVENT_OBJECT, STATE_CHANGED, pname, detail1, 0,
463               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
464   g_free (pname);
465   return TRUE;
466 }
467
468 /*---------------------------------------------------------------------------*/
469
470 /*
471  * The window event listener handles the following ATK signals and forwards
472  * them as AT-SPI events:
473  *
474  * window:create     -> window:create
475  * window:destroy    -> window:destroy
476  * window:minimize   -> window:minimize
477  * window:maximize   -> window:maximize
478  * window:activate   -> window:activate
479  * window:deactivate -> window:deactivate
480  */
481 static gboolean
482 window_event_listener (GSignalInvocationHint * signal_hint,
483                        guint n_param_values,
484                        const GValue * param_values, gpointer data)
485 {
486   AtkObject *accessible;
487   GSignalQuery signal_query;
488   const gchar *name, *s;
489
490   g_signal_query (signal_hint->signal_id, &signal_query);
491   name = signal_query.signal_name;
492
493   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
494   s = atk_object_get_name (accessible);
495   emit_event (accessible, ITF_EVENT_WINDOW, name, "", 0, 0,
496               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
497
498   return TRUE;
499 }
500
501 /*---------------------------------------------------------------------------*/
502
503 /* 
504  * The document event listener handles the following ATK signals
505  * and converts them to AT-SPI events:
506  *
507  * Gtk:AtkDocument:load-complete ->  document:load-complete
508  * Gtk:AtkDocument:load-stopped  ->  document:load-stopped
509  * Gtk:AtkDocument:reload        ->  document:reload
510  */
511 static gboolean
512 document_event_listener (GSignalInvocationHint * signal_hint,
513                          guint n_param_values,
514                          const GValue * param_values, gpointer data)
515 {
516   AtkObject *accessible;
517   GSignalQuery signal_query;
518   const gchar *name, *s;
519
520   g_signal_query (signal_hint->signal_id, &signal_query);
521   name = signal_query.signal_name;
522
523   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
524   s = atk_object_get_name (accessible);
525   emit_event (accessible, ITF_EVENT_DOCUMENT, name, "", 0, 0,
526               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
527
528   return TRUE;
529 }
530
531 /*---------------------------------------------------------------------------*/
532
533 /*
534  * Signal handler for  "Gtk:AtkComponent:bounds-changed". Converts
535  * this to an AT-SPI event - "object:bounds-changed".
536  */
537 static gboolean
538 bounds_event_listener (GSignalInvocationHint * signal_hint,
539                        guint n_param_values,
540                        const GValue * param_values, gpointer data)
541 {
542   AtkObject *accessible;
543   AtkRectangle *atk_rect;
544   GSignalQuery signal_query;
545   const gchar *name, *s;
546
547   g_signal_query (signal_hint->signal_id, &signal_query);
548   name = signal_query.signal_name;
549
550   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
551
552   if (G_VALUE_HOLDS_BOXED (param_values + 1))
553   {
554     atk_rect = g_value_get_boxed (param_values + 1);
555
556     emit_event (accessible, ITF_EVENT_OBJECT, name, "", 0, 0,
557                 "(iiii)", atk_rect, append_rect);
558   }
559   return TRUE;
560 }
561
562 /*---------------------------------------------------------------------------*/
563
564 /* 
565  * Handles the ATK signal 'Gtk:AtkObject:active-descendant-changed' and 
566  * converts it to the AT-SPI signal - 'object:active-descendant-changed'.
567  *
568  */
569 static gboolean
570 active_descendant_event_listener (GSignalInvocationHint * signal_hint,
571                                   guint n_param_values,
572                                   const GValue * param_values, gpointer data)
573 {
574   AtkObject *accessible;
575   AtkObject *child;
576   GSignalQuery signal_query;
577   const gchar *name, *minor;
578   gchar *s;
579   gint detail1;
580
581   g_signal_query (signal_hint->signal_id, &signal_query);
582   name = signal_query.signal_name;
583
584   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
585   child = ATK_OBJECT (g_value_get_pointer (&param_values[1]));
586   g_return_val_if_fail (ATK_IS_OBJECT (child), TRUE);
587   minor = g_quark_to_string (signal_hint->detail);
588
589   detail1 = atk_object_get_index_in_parent (child);
590
591   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, 0,
592               "(so)", child, append_object);
593   g_free (s);
594   return TRUE;
595 }
596
597 /*---------------------------------------------------------------------------*/
598
599 /* 
600  * Handles the ATK signal 'Gtk:AtkHypertext:link-selected' and
601  * converts it to the AT-SPI signal - 'object:link-selected'
602  *
603  */
604 static gboolean
605 link_selected_event_listener (GSignalInvocationHint * signal_hint,
606                               guint n_param_values,
607                               const GValue * param_values, gpointer data)
608 {
609   AtkObject *accessible;
610   GSignalQuery signal_query;
611   const gchar *name, *minor;
612   gint detail1;
613
614   g_signal_query (signal_hint->signal_id, &signal_query);
615   name = signal_query.signal_name;
616
617   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
618   minor = g_quark_to_string (signal_hint->detail);
619
620   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
621     detail1 = g_value_get_int (&param_values[1]);
622
623   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, 0,
624               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
625   return TRUE;
626 }
627
628 /*---------------------------------------------------------------------------*/
629
630 /* 
631  * Handles the ATK signal 'Gtk:AtkText:text-changed' and
632  * converts it to the AT-SPI signal - 'object:text-changed'
633  *
634  */
635 static gboolean
636 text_changed_event_listener (GSignalInvocationHint * signal_hint,
637                              guint n_param_values,
638                              const GValue * param_values, gpointer data)
639 {
640   AtkObject *accessible;
641   GSignalQuery signal_query;
642   const gchar *name, *minor;
643   gchar *selected;
644   gint detail1, detail2;
645
646   g_signal_query (signal_hint->signal_id, &signal_query);
647   name = signal_query.signal_name;
648
649   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
650   minor = g_quark_to_string (signal_hint->detail);
651
652   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
653     detail1 = g_value_get_int (&param_values[1]);
654
655   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
656     detail2 = g_value_get_int (&param_values[2]);
657
658   selected =
659     atk_text_get_text (ATK_TEXT (accessible), detail1, detail1 + detail2);
660
661   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
662               DBUS_TYPE_STRING_AS_STRING, selected, append_basic);
663   return TRUE;
664 }
665
666 /*---------------------------------------------------------------------------*/
667
668 /* 
669  * Handles the ATK signal 'Gtk:AtkText:text-selection-changed' and
670  * converts it to the AT-SPI signal - 'object:text-selection-changed'
671  *
672  */
673 static gboolean
674 text_selection_changed_event_listener (GSignalInvocationHint * signal_hint,
675                                        guint n_param_values,
676                                        const GValue * param_values,
677                                        gpointer data)
678 {
679   AtkObject *accessible;
680   GSignalQuery signal_query;
681   const gchar *name, *minor;
682   gint detail1, detail2;
683
684   g_signal_query (signal_hint->signal_id, &signal_query);
685   name = signal_query.signal_name;
686
687   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
688   minor = g_quark_to_string (signal_hint->detail);
689
690   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
691     detail1 = g_value_get_int (&param_values[1]);
692
693   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
694     detail2 = g_value_get_int (&param_values[2]);
695
696   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
697               DBUS_TYPE_STRING_AS_STRING, "", append_basic);
698   return TRUE;
699 }
700
701 /*---------------------------------------------------------------------------*/
702
703 /*
704  * Children changed signal converter and forwarder.
705  *
706  * Klass (Interface) org.freedesktop.atspi.Event.Object
707  * Major is the signal name.
708  * Minor is 'add' or 'remove'
709  * detail1 is the index.
710  * detail2 is 0.
711  * any_data is the child reference.
712  */
713 static gboolean
714 children_changed_event_listener (GSignalInvocationHint * signal_hint,
715                                  guint n_param_values,
716                                  const GValue * param_values, gpointer data)
717 {
718   AtkObject *accessible, *child;
719   GSignalQuery signal_query;
720   const gchar *name, *minor;
721   gint detail1, detail2;
722
723   g_signal_query (signal_hint->signal_id, &signal_query);
724   name = signal_query.signal_name;
725
726   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
727   minor = g_quark_to_string (signal_hint->detail);
728
729   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
730     detail1 = g_value_get_int (&param_values[1]);
731
732   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_OBJECT)
733     {
734       child = ATK_OBJECT(g_value_get_pointer (&param_values[2]));
735       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
736                   "(so)", child, append_object);
737     }
738   else
739     {
740       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
741                   "s", "", append_basic);
742     }
743   return TRUE;
744 }
745
746 /*---------------------------------------------------------------------------*/
747
748
749 /*
750  * Generic signal converter and forwarder.
751  *
752  * Klass (Interface) org.freedesktop.atspi.Event.Object
753  * Major is the signal name.
754  * Minor is NULL.
755  * detail1 is 0.
756  * detail2 is 0.
757  * any_data is NULL.
758  */
759 static gboolean
760 generic_event_listener (GSignalInvocationHint * signal_hint,
761                         guint n_param_values,
762                         const GValue * param_values, gpointer data)
763 {
764   AtkObject *accessible;
765   GSignalQuery signal_query;
766   const gchar *name;
767   int detail1 = 0, detail2 = 0;
768
769   g_signal_query (signal_hint->signal_id, &signal_query);
770   name = signal_query.signal_name;
771
772   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
773
774   if (n_param_values > 1 && G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
775     detail1 = g_value_get_int (&param_values[1]);
776
777   if (n_param_values > 2 && G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
778     detail2 = g_value_get_int (&param_values[2]);
779
780   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, detail2,
781               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
782   return TRUE;
783 }
784
785 /*---------------------------------------------------------------------------*/
786
787 /*
788  * Registers the provided function as a handler for the given signal name
789  * and stores the signal id returned so that the function may be
790  * de-registered later.
791  */
792 static void
793 add_signal_listener (GSignalEmissionHook listener, const char *signal_name)
794 {
795   guint id;
796
797   id = atk_add_global_event_listener (listener, signal_name);
798   g_array_append_val (listener_ids, id);
799 }
800
801 /*
802  * Initialization for the signal handlers.
803  *
804  * Registers all required signal handlers.
805  */
806 void
807 spi_atk_register_event_listeners (void)
808 {
809   /*
810    * Kludge to make sure the Atk interface types are registered, otherwise
811    * the AtkText signal handlers below won't get registered
812    */
813   GObject *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
814   AtkObject *bo = atk_no_op_object_new (ao);
815
816   g_object_unref (G_OBJECT (bo));
817   g_object_unref (ao);
818
819   /* Register for focus event notifications, and register app with central registry  */
820   listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
821
822   atk_bridge_focus_tracker_id = atk_add_focus_tracker (focus_tracker);
823
824   add_signal_listener (property_event_listener,
825                        "Gtk:AtkObject:property-change");
826   add_signal_listener (window_event_listener, "window:create");
827   add_signal_listener (window_event_listener, "window:destroy");
828   add_signal_listener (window_event_listener, "window:minimize");
829   add_signal_listener (window_event_listener, "window:maximize");
830   add_signal_listener (window_event_listener, "window:restore");
831   add_signal_listener (window_event_listener, "window:activate");
832   add_signal_listener (window_event_listener, "window:deactivate");
833   add_signal_listener (document_event_listener,
834                        "Gtk:AtkDocument:load-complete");
835   add_signal_listener (document_event_listener, "Gtk:AtkDocument:reload");
836   add_signal_listener (document_event_listener,
837                        "Gtk:AtkDocument:load-stopped");
838   /* TODO Fake this event on the client side */
839   add_signal_listener (state_event_listener, "Gtk:AtkObject:state-change");
840   /* TODO */
841   add_signal_listener (active_descendant_event_listener,
842                        "Gtk:AtkObject:active-descendant-changed");
843   add_signal_listener (bounds_event_listener,
844                        "Gtk:AtkComponent:bounds-changed");
845   add_signal_listener (text_selection_changed_event_listener,
846                        "Gtk:AtkText:text-selection-changed");
847   add_signal_listener (text_changed_event_listener,
848                        "Gtk:AtkText:text-changed");
849   add_signal_listener (link_selected_event_listener,
850                        "Gtk:AtkHypertext:link-selected");
851   add_signal_listener (generic_event_listener,
852                        "Gtk:AtkObject:visible-data-changed");
853   add_signal_listener (generic_event_listener,
854                        "Gtk:AtkSelection:selection-changed");
855   add_signal_listener (generic_event_listener,
856                        "Gtk:AtkText:text-attributes-changed");
857   add_signal_listener (generic_event_listener,
858                        "Gtk:AtkText:text-caret-moved");
859   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-inserted");
860   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-reordered");
861   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-deleted");
862   add_signal_listener (generic_event_listener,
863                        "Gtk:AtkTable:column-inserted");
864   add_signal_listener (generic_event_listener,
865                        "Gtk:AtkTable:column-reordered");
866   add_signal_listener (generic_event_listener, "Gtk:AtkTable:column-deleted");
867   add_signal_listener (generic_event_listener, "Gtk:AtkTable:model-changed");
868
869   /* Children signal listeners */
870   atk_add_global_event_listener (children_changed_event_listener,
871                                  "Gtk:AtkObject:children-changed");
872
873   /*
874    * May add the following listeners to implement preemptive key listening for GTK+
875    *
876    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
877    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
878    */
879   atk_bridge_key_event_listener_id =
880     atk_add_key_event_listener (spi_atk_bridge_key_listener, NULL);
881 }
882
883 /*---------------------------------------------------------------------------*/
884
885 /* 
886  * De-registers all ATK signal handlers.
887  */
888 void
889 spi_atk_deregister_event_listeners (void)
890 {
891   gint i;
892   GArray *ids = listener_ids;
893   listener_ids = NULL;
894
895   if (atk_bridge_focus_tracker_id)
896     atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
897
898   for (i = 0; ids && i < ids->len; i++)
899     {
900       atk_remove_global_event_listener (g_array_index (ids, guint, i));
901     }
902
903   if (atk_bridge_key_event_listener_id)
904     atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
905 }
906
907 /*---------------------------------------------------------------------------*/
908
909 /*
910  * TODO This function seems out of place here.
911  *
912  * Emits fake deactivate signals on all top-level windows.
913  * Used when shutting down AT-SPI, ensuring that all
914  * windows have been removed on the client side.
915  */
916 void
917 spi_atk_tidy_windows (void)
918 {
919   AtkObject *root;
920   gint n_children;
921   gint i;
922
923   root = atk_get_root ();
924   n_children = atk_object_get_n_accessible_children (root);
925   for (i = 0; i < n_children; i++)
926     {
927       AtkObject *child;
928       AtkStateSet *stateset;
929       const gchar *name;
930
931       child = atk_object_ref_accessible_child (root, i);
932       stateset = atk_object_ref_state_set (child);
933
934       name = atk_object_get_name (child);
935       if (atk_state_set_contains_state (stateset, ATK_STATE_ACTIVE))
936         {
937           emit_event (child, ITF_EVENT_WINDOW, "deactivate", NULL, 0, 0,
938                       DBUS_TYPE_STRING_AS_STRING, name, append_basic);
939         }
940       g_object_unref (stateset);
941
942       emit_event (child, ITF_EVENT_WINDOW, "destroy", NULL, 0, 0,
943                   DBUS_TYPE_STRING_AS_STRING, name, append_basic);
944       g_object_unref (child);
945     }
946 }
947
948 /*END------------------------------------------------------------------------*/