3ba1ba881c30204a3535cf37b13ac96f71bc3aa1
[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 static gchar *
262 signal_name_to_dbus (const gchar *s)
263 {
264   gchar *ret = g_strdup (s);
265   gchar *t;
266
267   if (!ret)
268     return NULL;
269   ret [0] = toupper (ret [0]);
270   while ((t = strchr (ret, '-')) != NULL)
271   {
272     memmove (t, t + 1, strlen (t));
273     *t = toupper (*t);
274   }
275   return ret;
276 }
277
278 /*
279  * Emits an AT-SPI event.
280  * AT-SPI events names are split into three parts:
281  * class:major:minor
282  * This is mapped onto D-Bus events as:
283  * D-Bus Interface:Signal Name:Detail argument
284  *
285  * Marshals a basic type into the 'any_data' attribute of
286  * the AT-SPI event.
287  */
288 static void 
289 emit_event (AtkObject  *obj,
290             const char *klass,
291             const char *major,
292             const char *minor,
293             dbus_int32_t detail1,
294             dbus_int32_t detail2,
295             const char *type,
296             const void *val,
297             void (*append_variant) (DBusMessageIter *, const char *, const void *))
298 {
299   DBusConnection *bus = spi_global_app_data->bus;
300   const char *path =  spi_register_object_to_path (spi_global_register,
301                                                    G_OBJECT (obj));
302
303   gchar *cname, *t;
304   DBusMessage *sig;
305   DBusMessageIter iter, iter_struct;
306   
307   if (!klass) klass = "";
308   if (!major) major = "";
309   if (!minor) minor = "";
310   if (!type) type = "u";
311
312   /*
313    * This is very annoying, but as '-' isn't a legal signal
314    * name in D-Bus (Why not??!?) The names need converting
315    * on this side, and again on the client side.
316    */
317   cname = signal_name_to_dbus (major);
318   sig = dbus_message_new_signal(path, klass, cname);
319   g_free(cname);
320
321   dbus_message_iter_init_append(sig, &iter);
322
323   spi_object_append_reference (&iter, spi_global_app_data->root);
324   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &minor);
325   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail1);
326   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail2);
327
328   append_variant (&iter, type, val);
329
330   dbus_connection_send(bus, sig, NULL);
331   dbus_message_unref(sig);
332 }
333
334 /*---------------------------------------------------------------------------*/
335
336 /*
337  * The focus listener handles the ATK 'focus' signal and forwards it
338  * as the AT-SPI event, 'focus:'
339  */
340 static void
341 focus_tracker (AtkObject * accessible)
342 {
343   emit_event (accessible, ITF_EVENT_FOCUS, "focus", "", 0, 0,
344               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
345 }
346
347 /*---------------------------------------------------------------------------*/
348
349 #define PCHANGE "property_change"
350
351 /* 
352  * This handler handles the following ATK signals and
353  * converts them to AT-SPI events:
354  *  
355  * Gtk:AtkObject:property-change -> object:property-change:(property-name)
356  *
357  * The property-name is part of the ATK property-change signal.
358  */
359 static gboolean
360 property_event_listener (GSignalInvocationHint * signal_hint,
361                          guint n_param_values,
362                          const GValue * param_values, gpointer data)
363 {
364   AtkObject *accessible;
365   AtkPropertyValues *values;
366
367   const gchar *pname = NULL;
368
369   AtkObject *otemp;
370   const gchar *s1, s2;
371   gint i;
372
373   accessible = g_value_get_object (&param_values[0]);
374   values = (AtkPropertyValues *) g_value_get_pointer (&param_values[1]);
375
376   pname = values[0].property_name;
377
378   /* TODO Could improve this control statement by matching
379    * on only the end of the signal names,
380    */
381   if (strcmp (pname, "accessible-name") == 0)
382     {
383       s1 = atk_object_get_name (accessible);
384       if (s1 != NULL)
385         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
386                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
387     }
388   if (strcmp (pname, "accessible-description") == 0)
389     {
390       s1 = atk_object_get_description (accessible);
391       if (s1 != NULL)
392         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
393                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
394     }
395   if (strcmp (pname, "accessible-parent") == 0)
396     {
397       otemp = atk_object_get_parent (accessible);
398       if (otemp != NULL)
399         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
400                     "(so)", otemp, append_object);
401     }
402   if (strcmp (pname, "accessible-table-summary") == 0)
403     {
404       otemp = atk_table_get_summary (ATK_TABLE (accessible));
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-column-header") == 0)
410     {
411       i = g_value_get_int (&(values->new_value));
412       otemp = atk_table_get_column_header (ATK_TABLE (accessible), i);
413       if (otemp != NULL)
414         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
415                     "(so)", otemp, append_object);
416     }
417   else if (strcmp (pname, "accessible-table-row-header") == 0)
418     {
419       i = g_value_get_int (&(values->new_value));
420       otemp = atk_table_get_row_header (ATK_TABLE (accessible), i);
421       if (otemp != NULL)
422         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
423                     "(so)", otemp, append_object);
424     }
425   else if (strcmp (pname, "accessible-table-row-description") == 0)
426     {
427       i = g_value_get_int (&(values->new_value));
428       s1 = atk_table_get_row_description (ATK_TABLE (accessible), i);
429       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
430                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
431     }
432   else if (strcmp (pname, "accessible-table-column-description") == 0)
433     {
434       i = g_value_get_int (&(values->new_value));
435       s1 = atk_table_get_column_description (ATK_TABLE (accessible), i);
436       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
437                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
438     }
439   else if (strcmp (pname, "accessible-table-caption-object") == 0)
440     {
441       otemp = atk_table_get_caption (ATK_TABLE (accessible));
442       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
443                   "(so)", otemp, append_object);
444     }
445   else
446     {
447       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
448             DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
449     }
450   return TRUE;
451 }
452
453 /*---------------------------------------------------------------------------*/
454
455 #define STATE_CHANGED "state-changed"
456
457 /*
458  * The state event listener handles 'Gtk:AtkObject:state-change' ATK signals
459  * and forwards them as object:state-changed:(param-name) AT-SPI events. Where
460  * the param-name is part of the ATK state-change signal.
461  */
462 static gboolean
463 state_event_listener (GSignalInvocationHint * signal_hint,
464                       guint n_param_values,
465                       const GValue * param_values, gpointer data)
466 {
467   AtkObject *accessible;
468   gchar *pname;
469   guint detail1;
470
471   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
472   pname = g_strdup (g_value_get_string (&param_values[1]));
473
474   /* TODO - Possibly ignore a change to the 'defunct' state.
475    * This is because without reference counting defunct objects should be removed.
476    */
477   detail1 = (g_value_get_boolean (&param_values[2])) ? 1 : 0;
478   emit_event (accessible, ITF_EVENT_OBJECT, STATE_CHANGED, pname, detail1, 0,
479               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
480   g_free (pname);
481   return TRUE;
482 }
483
484 /*---------------------------------------------------------------------------*/
485
486 /*
487  * The window event listener handles the following ATK signals and forwards
488  * them as AT-SPI events:
489  *
490  * window:create     -> window:create
491  * window:destroy    -> window:destroy
492  * window:minimize   -> window:minimize
493  * window:maximize   -> window:maximize
494  * window:activate   -> window:activate
495  * window:deactivate -> window:deactivate
496  */
497 static gboolean
498 window_event_listener (GSignalInvocationHint * signal_hint,
499                        guint n_param_values,
500                        const GValue * param_values, gpointer data)
501 {
502   AtkObject *accessible;
503   GSignalQuery signal_query;
504   const gchar *name, *s;
505
506   g_signal_query (signal_hint->signal_id, &signal_query);
507   name = signal_query.signal_name;
508
509   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
510   s = atk_object_get_name (accessible);
511   emit_event (accessible, ITF_EVENT_WINDOW, name, "", 0, 0,
512               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
513
514   return TRUE;
515 }
516
517 /*---------------------------------------------------------------------------*/
518
519 /* 
520  * The document event listener handles the following ATK signals
521  * and converts them to AT-SPI events:
522  *
523  * Gtk:AtkDocument:load-complete ->  document:load-complete
524  * Gtk:AtkDocument:load-stopped  ->  document:load-stopped
525  * Gtk:AtkDocument:reload        ->  document:reload
526  */
527 static gboolean
528 document_event_listener (GSignalInvocationHint * signal_hint,
529                          guint n_param_values,
530                          const GValue * param_values, gpointer data)
531 {
532   AtkObject *accessible;
533   GSignalQuery signal_query;
534   const gchar *name, *s;
535
536   g_signal_query (signal_hint->signal_id, &signal_query);
537   name = signal_query.signal_name;
538
539   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
540   s = atk_object_get_name (accessible);
541   emit_event (accessible, ITF_EVENT_DOCUMENT, name, "", 0, 0,
542               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
543
544   return TRUE;
545 }
546
547 /*---------------------------------------------------------------------------*/
548
549 /*
550  * Signal handler for  "Gtk:AtkComponent:bounds-changed". Converts
551  * this to an AT-SPI event - "object:bounds-changed".
552  */
553 static gboolean
554 bounds_event_listener (GSignalInvocationHint * signal_hint,
555                        guint n_param_values,
556                        const GValue * param_values, gpointer data)
557 {
558   AtkObject *accessible;
559   AtkRectangle *atk_rect;
560   GSignalQuery signal_query;
561   const gchar *name, *s;
562
563   g_signal_query (signal_hint->signal_id, &signal_query);
564   name = signal_query.signal_name;
565
566   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
567
568   if (G_VALUE_HOLDS_BOXED (param_values + 1))
569   {
570     atk_rect = g_value_get_boxed (param_values + 1);
571
572     emit_event (accessible, ITF_EVENT_OBJECT, name, "", 0, 0,
573                 "(iiii)", atk_rect, append_rect);
574   }
575   return TRUE;
576 }
577
578 /*---------------------------------------------------------------------------*/
579
580 /* 
581  * Handles the ATK signal 'Gtk:AtkObject:active-descendant-changed' and 
582  * converts it to the AT-SPI signal - 'object:active-descendant-changed'.
583  *
584  */
585 static gboolean
586 active_descendant_event_listener (GSignalInvocationHint * signal_hint,
587                                   guint n_param_values,
588                                   const GValue * param_values, gpointer data)
589 {
590   AtkObject *accessible;
591   AtkObject *child;
592   GSignalQuery signal_query;
593   const gchar *name, *minor;
594   gchar *s;
595   gint detail1;
596
597   g_signal_query (signal_hint->signal_id, &signal_query);
598   name = signal_query.signal_name;
599
600   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
601   child = ATK_OBJECT (g_value_get_pointer (&param_values[1]));
602   g_return_val_if_fail (ATK_IS_OBJECT (child), TRUE);
603   minor = g_quark_to_string (signal_hint->detail);
604
605   detail1 = atk_object_get_index_in_parent (child);
606
607   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, 0,
608               "(so)", child, append_object);
609   g_free (s);
610   return TRUE;
611 }
612
613 /*---------------------------------------------------------------------------*/
614
615 /* 
616  * Handles the ATK signal 'Gtk:AtkHypertext:link-selected' and
617  * converts it to the AT-SPI signal - 'object:link-selected'
618  *
619  */
620 static gboolean
621 link_selected_event_listener (GSignalInvocationHint * signal_hint,
622                               guint n_param_values,
623                               const GValue * param_values, gpointer data)
624 {
625   AtkObject *accessible;
626   GSignalQuery signal_query;
627   const gchar *name, *minor;
628   gint detail1;
629
630   g_signal_query (signal_hint->signal_id, &signal_query);
631   name = signal_query.signal_name;
632
633   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
634   minor = g_quark_to_string (signal_hint->detail);
635
636   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
637     detail1 = g_value_get_int (&param_values[1]);
638
639   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, 0,
640               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
641   return TRUE;
642 }
643
644 /*---------------------------------------------------------------------------*/
645
646 /* 
647  * Handles the ATK signal 'Gtk:AtkText:text-changed' and
648  * converts it to the AT-SPI signal - 'object:text-changed'
649  *
650  */
651 static gboolean
652 text_changed_event_listener (GSignalInvocationHint * signal_hint,
653                              guint n_param_values,
654                              const GValue * param_values, gpointer data)
655 {
656   AtkObject *accessible;
657   GSignalQuery signal_query;
658   const gchar *name, *minor;
659   gchar *selected;
660   gint detail1, detail2;
661
662   g_signal_query (signal_hint->signal_id, &signal_query);
663   name = signal_query.signal_name;
664
665   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
666   minor = g_quark_to_string (signal_hint->detail);
667
668   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
669     detail1 = g_value_get_int (&param_values[1]);
670
671   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
672     detail2 = g_value_get_int (&param_values[2]);
673
674   selected =
675     atk_text_get_text (ATK_TEXT (accessible), detail1, detail1 + detail2);
676
677   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
678               DBUS_TYPE_STRING_AS_STRING, selected, append_basic);
679   return TRUE;
680 }
681
682 /*---------------------------------------------------------------------------*/
683
684 /* 
685  * Handles the ATK signal 'Gtk:AtkText:text-selection-changed' and
686  * converts it to the AT-SPI signal - 'object:text-selection-changed'
687  *
688  */
689 static gboolean
690 text_selection_changed_event_listener (GSignalInvocationHint * signal_hint,
691                                        guint n_param_values,
692                                        const GValue * param_values,
693                                        gpointer data)
694 {
695   AtkObject *accessible;
696   GSignalQuery signal_query;
697   const gchar *name, *minor;
698   gint detail1, detail2;
699
700   g_signal_query (signal_hint->signal_id, &signal_query);
701   name = signal_query.signal_name;
702
703   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
704   minor = g_quark_to_string (signal_hint->detail);
705
706   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
707     detail1 = g_value_get_int (&param_values[1]);
708
709   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
710     detail2 = g_value_get_int (&param_values[2]);
711
712   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
713               DBUS_TYPE_STRING_AS_STRING, "", append_basic);
714   return TRUE;
715 }
716
717 /*---------------------------------------------------------------------------*/
718
719 /*
720  * Children changed signal converter and forwarder.
721  *
722  * Klass (Interface) org.freedesktop.atspi.Event.Object
723  * Major is the signal name.
724  * Minor is 'add' or 'remove'
725  * detail1 is the index.
726  * detail2 is 0.
727  * any_data is the child reference.
728  */
729 static gboolean
730 children_changed_event_listener (GSignalInvocationHint * signal_hint,
731                                  guint n_param_values,
732                                  const GValue * param_values, gpointer data)
733 {
734   GSignalQuery signal_query;
735   const gchar *name, *minor;
736   gint detail1, detail2 = 0;
737
738   AtkObject *accessible, *ao;
739   gpointer child;
740
741   g_signal_query (signal_hint->signal_id, &signal_query);
742   name = signal_query.signal_name;
743
744   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
745   minor = g_quark_to_string (signal_hint->detail);
746
747   detail1 = g_value_get_uint (param_values + 1);
748   child = g_value_get_pointer (param_values + 2);
749
750   if (ATK_IS_OBJECT (child))
751     {
752       ao = ATK_OBJECT (child);
753       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
754                   "(so)", ao, append_object);
755     }
756   else if ((minor != NULL) && (strcmp (minor, "add") == 0))
757     {
758       ao = atk_object_ref_accessible_child (accessible, 
759                                             detail1);
760       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
761                   "(so)", ao, append_object);
762     }
763   else
764     {
765       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
766                   "(so)", ao, append_object);
767     }
768  
769   return TRUE;
770 }
771
772 /*---------------------------------------------------------------------------*/
773
774 static void
775 toplevel_added_event_listener (AtkObject * accessible,
776                                guint index, AtkObject * child)
777 {
778   emit_event (accessible, ITF_EVENT_OBJECT, "children-changed", "add", index, 0,
779               "(so)", child, append_object);
780 }
781
782 static void
783 toplevel_removed_event_listener (AtkObject * accessible,
784                                  guint index, AtkObject * child)
785 {
786   emit_event (accessible, ITF_EVENT_OBJECT, "children-changed", "remove", index, 0,
787               "(so)", child, append_object);
788 }
789
790 /*---------------------------------------------------------------------------*/
791
792 /*
793  * Generic signal converter and forwarder.
794  *
795  * Klass (Interface) org.freedesktop.atspi.Event.Object
796  * Major is the signal name.
797  * Minor is NULL.
798  * detail1 is 0.
799  * detail2 is 0.
800  * any_data is NULL.
801  */
802 static gboolean
803 generic_event_listener (GSignalInvocationHint * signal_hint,
804                         guint n_param_values,
805                         const GValue * param_values, gpointer data)
806 {
807   AtkObject *accessible;
808   GSignalQuery signal_query;
809   const gchar *name;
810   int detail1 = 0, detail2 = 0;
811
812   g_signal_query (signal_hint->signal_id, &signal_query);
813   name = signal_query.signal_name;
814
815   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
816
817   if (n_param_values > 1 && G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
818     detail1 = g_value_get_int (&param_values[1]);
819
820   if (n_param_values > 2 && G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
821     detail2 = g_value_get_int (&param_values[2]);
822
823   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, detail2,
824               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
825   return TRUE;
826 }
827
828 /*---------------------------------------------------------------------------*/
829
830 /*
831  * Registers the provided function as a handler for the given signal name
832  * and stores the signal id returned so that the function may be
833  * de-registered later.
834  */
835 static void
836 add_signal_listener (GSignalEmissionHook listener, const char *signal_name)
837 {
838   guint id;
839
840   id = atk_add_global_event_listener (listener, signal_name);
841   g_array_append_val (listener_ids, id);
842 }
843
844 /*
845  * Initialization for the signal handlers.
846  *
847  * Registers all required signal handlers.
848  */
849 void
850 spi_atk_register_event_listeners (void)
851 {
852   /*
853    * Kludge to make sure the Atk interface types are registered, otherwise
854    * the AtkText signal handlers below won't get registered
855    */
856   GObject *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
857   AtkObject *bo = atk_no_op_object_new (ao);
858
859   g_object_unref (G_OBJECT (bo));
860   g_object_unref (ao);
861
862   /* Register for focus event notifications, and register app with central registry  */
863   listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
864
865   atk_bridge_focus_tracker_id = atk_add_focus_tracker (focus_tracker);
866
867   add_signal_listener (property_event_listener,
868                        "Gtk:AtkObject:property-change");
869   add_signal_listener (window_event_listener, "window:create");
870   add_signal_listener (window_event_listener, "window:destroy");
871   add_signal_listener (window_event_listener, "window:minimize");
872   add_signal_listener (window_event_listener, "window:maximize");
873   add_signal_listener (window_event_listener, "window:restore");
874   add_signal_listener (window_event_listener, "window:activate");
875   add_signal_listener (window_event_listener, "window:deactivate");
876   add_signal_listener (document_event_listener,
877                        "Gtk:AtkDocument:load-complete");
878   add_signal_listener (document_event_listener, "Gtk:AtkDocument:reload");
879   add_signal_listener (document_event_listener,
880                        "Gtk:AtkDocument:load-stopped");
881   /* TODO Fake this event on the client side */
882   add_signal_listener (state_event_listener, "Gtk:AtkObject:state-change");
883   /* TODO */
884   add_signal_listener (active_descendant_event_listener,
885                        "Gtk:AtkObject:active-descendant-changed");
886   add_signal_listener (bounds_event_listener,
887                        "Gtk:AtkComponent:bounds-changed");
888   add_signal_listener (text_selection_changed_event_listener,
889                        "Gtk:AtkText:text-selection-changed");
890   add_signal_listener (text_changed_event_listener,
891                        "Gtk:AtkText:text-changed");
892   add_signal_listener (link_selected_event_listener,
893                        "Gtk:AtkHypertext:link-selected");
894   add_signal_listener (generic_event_listener,
895                        "Gtk:AtkObject:visible-data-changed");
896   add_signal_listener (generic_event_listener,
897                        "Gtk:AtkSelection:selection-changed");
898   add_signal_listener (generic_event_listener,
899                        "Gtk:AtkText:text-attributes-changed");
900   add_signal_listener (generic_event_listener,
901                        "Gtk:AtkText:text-caret-moved");
902   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-inserted");
903   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-reordered");
904   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-deleted");
905   add_signal_listener (generic_event_listener,
906                        "Gtk:AtkTable:column-inserted");
907   add_signal_listener (generic_event_listener,
908                        "Gtk:AtkTable:column-reordered");
909   add_signal_listener (generic_event_listener, "Gtk:AtkTable:column-deleted");
910   add_signal_listener (generic_event_listener, "Gtk:AtkTable:model-changed");
911
912   /* Children signal listeners */
913   atk_add_global_event_listener (children_changed_event_listener,
914                                  "Gtk:AtkObject:children-changed");
915
916 #if 0
917   g_signal_connect (G_OBJECT (spi_global_app_data->root),
918                     "children-changed::add",
919                     (GCallback) toplevel_added_event_listener, NULL);
920
921   g_signal_connect (G_OBJECT (spi_global_app_data->root),
922                     "children-changed::remove",
923                     (GCallback) toplevel_removed_event_listener, NULL);
924 #endif
925
926   /*
927    * May add the following listeners to implement preemptive key listening for GTK+
928    *
929    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
930    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
931    */
932   atk_bridge_key_event_listener_id =
933     atk_add_key_event_listener (spi_atk_bridge_key_listener, NULL);
934 }
935
936 /*---------------------------------------------------------------------------*/
937
938 /* 
939  * De-registers all ATK signal handlers.
940  */
941 void
942 spi_atk_deregister_event_listeners (void)
943 {
944   gint i;
945   GArray *ids = listener_ids;
946   listener_ids = NULL;
947
948   if (atk_bridge_focus_tracker_id)
949     atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
950
951   for (i = 0; ids && i < ids->len; i++)
952     {
953       atk_remove_global_event_listener (g_array_index (ids, guint, i));
954     }
955
956   if (atk_bridge_key_event_listener_id)
957     atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
958 }
959
960 /*---------------------------------------------------------------------------*/
961
962 /*
963  * TODO This function seems out of place here.
964  *
965  * Emits fake deactivate signals on all top-level windows.
966  * Used when shutting down AT-SPI, ensuring that all
967  * windows have been removed on the client side.
968  */
969 void
970 spi_atk_tidy_windows (void)
971 {
972   AtkObject *root;
973   gint n_children;
974   gint i;
975
976   root = atk_get_root ();
977   n_children = atk_object_get_n_accessible_children (root);
978   for (i = 0; i < n_children; i++)
979     {
980       AtkObject *child;
981       AtkStateSet *stateset;
982       const gchar *name;
983
984       child = atk_object_ref_accessible_child (root, i);
985       stateset = atk_object_ref_state_set (child);
986
987       name = atk_object_get_name (child);
988       if (atk_state_set_contains_state (stateset, ATK_STATE_ACTIVE))
989         {
990           emit_event (child, ITF_EVENT_WINDOW, "deactivate", NULL, 0, 0,
991                       DBUS_TYPE_STRING_AS_STRING, name, append_basic);
992         }
993       g_object_unref (stateset);
994
995       emit_event (child, ITF_EVENT_WINDOW, "destroy", NULL, 0, 0,
996                   DBUS_TYPE_STRING_AS_STRING, name, append_basic);
997       g_object_unref (child);
998     }
999 }
1000
1001 /*END------------------------------------------------------------------------*/