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