BGO#638377: Handle text-insert, text-remove, and text-update signals from ATK
[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 #include <ctype.h>
27
28 #include <atk/atk.h>
29 #include <droute/droute.h>
30 #include <atspi/atspi.h>
31
32 #include "bridge.h"
33 #include "accessible-register.h"
34
35 #include "common/spi-dbus.h"
36 #include "event.h"
37 #include "object.h"
38 #include "dbus/dbus-glib-lowlevel.h"
39
40 static GArray *listener_ids = NULL;
41
42 static gint atk_bridge_key_event_listener_id;
43 static gint atk_bridge_focus_tracker_id;
44
45 /*---------------------------------------------------------------------------*/
46
47 #define ITF_EVENT_OBJECT   "org.a11y.atspi.Event.Object"
48 #define ITF_EVENT_WINDOW   "org.a11y.atspi.Event.Window"
49 #define ITF_EVENT_DOCUMENT "org.a11y.atspi.Event.Document"
50 #define ITF_EVENT_FOCUS    "org.a11y.atspi.Event.Focus"
51
52 /*---------------------------------------------------------------------------*/
53
54 typedef struct _SpiReentrantCallClosure 
55 {
56   GMainLoop   *loop;
57   DBusMessage *reply;
58 } SpiReentrantCallClosure;
59
60 static void
61 switch_main_context (GMainContext *cnx)
62 {
63 /* This code won't work on dbus-glib earlier than 0.9.0 because of FDO#30574 */
64   if (spi_global_app_data->app_bus_addr [0] == '\0')
65     return;
66
67   GList *list;
68
69   atspi_dbus_server_setup_with_g_main (spi_global_app_data->server, cnx);
70   atspi_dbus_connection_setup_with_g_main (spi_global_app_data->bus, cnx);
71   for (list = spi_global_app_data->direct_connections; list; list = list->next)
72     atspi_dbus_connection_setup_with_g_main (list->data, cnx);
73 }
74
75 static void
76 set_reply (DBusPendingCall * pending, void *user_data)
77 {
78   SpiReentrantCallClosure* closure = (SpiReentrantCallClosure *) user_data; 
79
80   closure->reply = dbus_pending_call_steal_reply (pending);
81   dbus_pending_call_unref (pending);
82   switch_main_context (NULL);
83   g_main_loop_quit (closure->loop);
84 }
85
86 static DBusMessage *
87 send_and_allow_reentry (DBusConnection * bus, DBusMessage * message)
88 {
89   DBusPendingCall *pending;
90   SpiReentrantCallClosure closure;
91   GMainContext *main_context;
92
93   main_context = (g_getenv ("AT_SPI_CLIENT") ? NULL :
94                   spi_global_app_data->main_context);
95   closure.loop = g_main_loop_new (main_context, FALSE);
96   switch_main_context (main_context);
97
98   if (!dbus_connection_send_with_reply (bus, message, &pending, -1) || !pending)
99     {
100       switch_main_context (NULL);
101       return NULL;
102     }
103   dbus_pending_call_set_notify (pending, set_reply, (void *) &closure, NULL);
104   g_main_loop_run  (closure.loop);
105   
106   g_main_loop_unref (closure.loop);
107   return closure.reply;
108 }
109
110 /*---------------------------------------------------------------------------*/
111
112 /*
113  * Functionality related to sending device events from the application.
114  *
115  * This is used for forwarding key events on to the registry daemon.
116  */
117
118 static gboolean
119 Accessibility_DeviceEventController_NotifyListenersSync (const
120                                                          Accessibility_DeviceEvent
121                                                          * key_event)
122 {
123   DBusMessage *message;
124   DBusError error;
125   dbus_bool_t consumed = FALSE;
126
127   message =
128     dbus_message_new_method_call (SPI_DBUS_NAME_REGISTRY,
129                                   SPI_DBUS_PATH_DEC,
130                                   SPI_DBUS_INTERFACE_DEC,
131                                   "NotifyListenersSync");
132
133   dbus_error_init (&error);
134   if (spi_dbus_marshal_deviceEvent (message, key_event))
135     {
136       DBusMessage *reply =
137         send_and_allow_reentry (spi_global_app_data->bus, message);
138       if (reply)
139         {
140           DBusError error;
141           dbus_error_init (&error);
142           dbus_message_get_args (reply, &error, DBUS_TYPE_BOOLEAN, &consumed,
143                                  DBUS_TYPE_INVALID);
144           dbus_message_unref (reply);
145         }
146     }
147   dbus_message_unref (message);
148   return consumed;
149 }
150
151 static void
152 spi_init_keystroke_from_atk_key_event (Accessibility_DeviceEvent * keystroke,
153                                        AtkKeyEventStruct * event)
154 {
155   keystroke->id = (dbus_int32_t) event->keyval;
156   keystroke->hw_code = (dbus_int16_t) event->keycode;
157   keystroke->timestamp = (dbus_uint32_t) event->timestamp;
158   keystroke->modifiers = (dbus_uint16_t) (event->state & 0xFFFF);
159   if (event->string)
160     {
161       gunichar c;
162
163       keystroke->event_string = g_strdup (event->string);
164       c = g_utf8_get_char_validated (event->string, -1);
165       if (c > 0 && g_unichar_isprint (c))
166         keystroke->is_text = TRUE;
167       else
168         keystroke->is_text = FALSE;
169     }
170   else
171     {
172       keystroke->event_string = g_strdup ("");
173       keystroke->is_text = FALSE;
174     }
175   switch (event->type)
176     {
177     case (ATK_KEY_EVENT_PRESS):
178       keystroke->type = Accessibility_KEY_PRESSED_EVENT;
179       break;
180     case (ATK_KEY_EVENT_RELEASE):
181       keystroke->type = Accessibility_KEY_RELEASED_EVENT;
182       break;
183     default:
184       keystroke->type = 0;
185       break;
186     }
187 #if 0
188   g_print
189     ("key_event type %d; val=%d code=%d modifiers=%x name=%s is_text=%d, time=%lx\n",
190      (int) keystroke->type, (int) keystroke->id, (int) keystroke->hw_code,
191      (int) keystroke->modifiers, keystroke->event_string,
192      (int) keystroke->is_text, (unsigned long) keystroke->timestamp);
193 #endif
194 }
195
196
197 static gint
198 spi_atk_bridge_key_listener (AtkKeyEventStruct * event, gpointer data)
199 {
200   gboolean result;
201   Accessibility_DeviceEvent key_event;
202
203   spi_init_keystroke_from_atk_key_event (&key_event, event);
204
205   result =
206     Accessibility_DeviceEventController_NotifyListenersSync (&key_event);
207
208   if (key_event.event_string)
209     g_free (key_event.event_string);
210
211   return result;
212 }
213
214 /*---------------------------------------------------------------------------*/
215
216 static gchar *
217 convert_signal_name (const gchar * s)
218 {
219   gchar *ret = g_strdup (s);
220   gchar *t;
221
222   if (!ret)
223     return NULL;
224   ret[0] = toupper (ret[0]);
225   while ((t = strchr (ret, '-')) != NULL)
226     {
227       memmove (t, t + 1, strlen (t));
228       *t = toupper (*t);
229     }
230   return ret;
231 }
232
233 static const void *
234 replace_null (const gint type,
235               const void *val)
236 {
237   switch (type)
238     {
239       case DBUS_TYPE_STRING:
240       case DBUS_TYPE_OBJECT_PATH:
241            if (!val)
242               return "";
243            else
244               return val;
245       default:
246            return val;
247     }
248 }
249
250 static void
251 append_basic (DBusMessageIter *iter,
252               const char *type,
253               const void *val)
254 {
255   DBusMessageIter sub;
256
257   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &sub);
258
259     val = replace_null ((int) *type, val);
260     dbus_message_iter_append_basic(&sub, (int) *type, &val);
261
262   dbus_message_iter_close_container(iter, &sub);
263 }
264
265 static void
266 append_rect (DBusMessageIter *iter,
267              const char *type,
268              const void *val)
269 {
270   DBusMessageIter variant, sub;
271   const AtkRectangle *rect = (const AtkRectangle *) val;
272
273   dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, type, &variant);
274
275     dbus_message_iter_open_container (&variant, DBUS_TYPE_STRUCT, NULL, &sub);
276
277       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->x));
278       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->y));
279       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->width));
280       dbus_message_iter_append_basic (&sub, DBUS_TYPE_INT32, &(rect->height));
281
282     dbus_message_iter_close_container (&variant, &sub);
283
284   dbus_message_iter_close_container(iter, &variant);
285 }
286
287 static void
288 append_object (DBusMessageIter *iter,
289                const char *type,
290                const void *val)
291 {
292   spi_object_append_v_reference (iter, ATK_OBJECT (val));
293 }
294
295 static gchar *
296 signal_name_to_dbus (const gchar *s)
297 {
298   gchar *ret = g_strdup (s);
299   gchar *t;
300
301   if (!ret)
302     return NULL;
303   ret [0] = toupper (ret [0]);
304   while ((t = strchr (ret, '-')) != NULL)
305   {
306     memmove (t, t + 1, strlen (t));
307     *t = toupper (*t);
308   }
309   return ret;
310 }
311
312 /*
313  * Converts names of the form "active-descendant-changed" to
314  * "ActiveDescendantChanged"
315  */
316 static gchar *
317 ensure_proper_format (const char *name)
318 {
319   gchar *ret = (gchar *) g_malloc (strlen (name) * 2 + 2);
320   gchar *p = ret;
321   gboolean need_upper = TRUE;
322
323   if (!ret)
324     return NULL;
325   while (*name)
326     {
327       if (need_upper)
328         {
329           *p++ = toupper (*name);
330           need_upper = FALSE;
331         }
332       else if (*name == '-')
333         need_upper = TRUE;
334       else if (*name == ':')
335         {
336           need_upper = TRUE;
337           *p++ = *name;
338         }
339       else
340         *p++ = *name;
341       name++;
342     }
343   *p = '\0';
344   return ret;
345 }
346
347 static gboolean
348 signal_is_needed (const gchar *klass, const gchar *major, const gchar *minor)
349 {
350   gchar *data [4];
351   GList *iter;
352   event_data *evdata;
353   gboolean ret = FALSE;
354   GList *list;
355
356   if (!spi_global_app_data->events_initialized)
357     return TRUE;
358
359   data [0] = ensure_proper_format (klass + 21);
360   data [1] = ensure_proper_format (major);
361   data [2] = ensure_proper_format (minor);
362   data [3] = NULL;
363
364   /* Hack: events such as "object::text-changed::insert:system" as
365      generated by Gecko */
366   data [2][strcspn (data [2], ":")] = '\0';
367   for (list = spi_global_app_data->events; list; list = list->next)
368     {
369       evdata = list->data;
370       if (spi_event_is_subtype (data, evdata->data))
371         {
372           ret = TRUE;
373           break;
374         }
375     }
376
377   g_free (data [2]);
378   g_free (data [1]);
379   g_free (data [0]);
380   return ret;
381 }
382
383 /*
384  * Emits an AT-SPI event.
385  * AT-SPI events names are split into three parts:
386  * class:major:minor
387  * This is mapped onto D-Bus events as:
388  * D-Bus Interface:Signal Name:Detail argument
389  *
390  * Marshals a basic type into the 'any_data' attribute of
391  * the AT-SPI event.
392  */
393 static void 
394 emit_event (AtkObject  *obj,
395             const char *klass,
396             const char *major,
397             const char *minor,
398             dbus_int32_t detail1,
399             dbus_int32_t detail2,
400             const char *type,
401             const void *val,
402             void (*append_variant) (DBusMessageIter *, const char *, const void *))
403 {
404   DBusConnection *bus = spi_global_app_data->bus;
405   const char *path;
406
407   gchar *cname, *t;
408   DBusMessage *sig;
409   DBusMessageIter iter, iter_struct;
410   
411   if (!klass) klass = "";
412   if (!major) major = "";
413   if (!minor) minor = "";
414   if (!type) type = "u";
415
416   if (!signal_is_needed (klass, major, minor))
417     return;
418
419   path =  spi_register_object_to_path (spi_global_register, G_OBJECT (obj));
420
421   /*
422    * This is very annoying, but as '-' isn't a legal signal
423    * name in D-Bus (Why not??!?) The names need converting
424    * on this side, and again on the client side.
425    */
426   cname = signal_name_to_dbus (major);
427   sig = dbus_message_new_signal(path, klass, cname);
428
429   dbus_message_iter_init_append(sig, &iter);
430
431   dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &minor);
432   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail1);
433   dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &detail2);
434   append_variant (&iter, type, val);
435   spi_object_append_reference (&iter, spi_global_app_data->root);
436
437   dbus_connection_send(bus, sig, NULL);
438   dbus_message_unref(sig);
439
440   if (g_strcmp0 (cname, "ChildrenChanged") != 0)
441     spi_object_lease_if_needed (G_OBJECT (obj));
442
443   g_free(cname);
444   g_free (path);
445 }
446
447 /*---------------------------------------------------------------------------*/
448
449 /*
450  * The focus listener handles the ATK 'focus' signal and forwards it
451  * as the AT-SPI event, 'focus:'
452  */
453 static void
454 focus_tracker (AtkObject * accessible)
455 {
456   emit_event (accessible, ITF_EVENT_FOCUS, "focus", "", 0, 0,
457               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
458 }
459
460 /*---------------------------------------------------------------------------*/
461
462 #define PCHANGE "PropertyChange"
463
464 /* 
465  * This handler handles the following ATK signals and
466  * converts them to AT-SPI events:
467  *  
468  * Gtk:AtkObject:property-change -> object:property-change:(property-name)
469  *
470  * The property-name is part of the ATK property-change signal.
471  */
472 static gboolean
473 property_event_listener (GSignalInvocationHint * signal_hint,
474                          guint n_param_values,
475                          const GValue * param_values, gpointer data)
476 {
477   AtkObject *accessible;
478   AtkPropertyValues *values;
479
480   const gchar *pname = NULL;
481
482   AtkObject *otemp;
483   const gchar *s1, s2;
484   gint i;
485
486   accessible = g_value_get_object (&param_values[0]);
487   values = (AtkPropertyValues *) g_value_get_pointer (&param_values[1]);
488
489   pname = values[0].property_name;
490
491   /* TODO Could improve this control statement by matching
492    * on only the end of the signal names,
493    */
494   if (strcmp (pname, "accessible-name") == 0)
495     {
496       s1 = atk_object_get_name (accessible);
497       if (s1 != NULL)
498         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
499                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
500     }
501   else if (strcmp (pname, "accessible-description") == 0)
502     {
503       s1 = atk_object_get_description (accessible);
504       if (s1 != NULL)
505         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
506                     DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
507     }
508   else if (strcmp (pname, "accessible-parent") == 0)
509     {
510       otemp = atk_object_get_parent (accessible);
511       if (otemp != NULL)
512         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
513                     "(so)", otemp, append_object);
514     }
515   else if (strcmp (pname, "accessible-role") == 0)
516     {
517       i = atk_object_get_role (accessible);
518       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
519                     DBUS_TYPE_UINT32_AS_STRING, GINT_TO_POINTER(i), append_basic);
520     }
521   else if (strcmp (pname, "accessible-table-summary") == 0)
522     {
523       otemp = atk_table_get_summary (ATK_TABLE (accessible));
524       if (otemp != NULL)
525         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
526                     "(so)", otemp, append_object);
527     }
528   else if (strcmp (pname, "accessible-table-column-header") == 0)
529     {
530       i = g_value_get_int (&(values->new_value));
531       otemp = atk_table_get_column_header (ATK_TABLE (accessible), i);
532       if (otemp != NULL)
533         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
534                     "(so)", otemp, append_object);
535     }
536   else if (strcmp (pname, "accessible-table-row-header") == 0)
537     {
538       i = g_value_get_int (&(values->new_value));
539       otemp = atk_table_get_row_header (ATK_TABLE (accessible), i);
540       if (otemp != NULL)
541         emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
542                     "(so)", otemp, append_object);
543     }
544   else if (strcmp (pname, "accessible-table-row-description") == 0)
545     {
546       i = g_value_get_int (&(values->new_value));
547       s1 = atk_table_get_row_description (ATK_TABLE (accessible), i);
548       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
549                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
550     }
551   else if (strcmp (pname, "accessible-table-column-description") == 0)
552     {
553       i = g_value_get_int (&(values->new_value));
554       s1 = atk_table_get_column_description (ATK_TABLE (accessible), i);
555       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
556                   DBUS_TYPE_STRING_AS_STRING, s1, append_basic);
557     }
558   else if (strcmp (pname, "accessible-table-caption-object") == 0)
559     {
560       otemp = atk_table_get_caption (ATK_TABLE (accessible));
561       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
562                   "(so)", otemp, append_object);
563     }
564   else
565     {
566       emit_event (accessible, ITF_EVENT_OBJECT, PCHANGE, pname, 0, 0,
567             DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
568     }
569   return TRUE;
570 }
571
572 /*---------------------------------------------------------------------------*/
573
574 #define STATE_CHANGED "state-changed"
575
576 /*
577  * The state event listener handles 'Gtk:AtkObject:state-change' ATK signals
578  * and forwards them as object:state-changed:(param-name) AT-SPI events. Where
579  * the param-name is part of the ATK state-change signal.
580  */
581 static gboolean
582 state_event_listener (GSignalInvocationHint * signal_hint,
583                       guint n_param_values,
584                       const GValue * param_values, gpointer data)
585 {
586   AtkObject *accessible;
587   gchar *pname;
588   guint detail1;
589
590   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
591   pname = g_strdup (g_value_get_string (&param_values[1]));
592
593   /* TODO - Possibly ignore a change to the 'defunct' state.
594    * This is because without reference counting defunct objects should be removed.
595    */
596   detail1 = (g_value_get_boolean (&param_values[2])) ? 1 : 0;
597   emit_event (accessible, ITF_EVENT_OBJECT, STATE_CHANGED, pname, detail1, 0,
598               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
599   g_free (pname);
600   return TRUE;
601 }
602
603 /*---------------------------------------------------------------------------*/
604
605 /*
606  * The window event listener handles the following ATK signals and forwards
607  * them as AT-SPI events:
608  *
609  * window:create     -> window:create
610  * window:destroy    -> window:destroy
611  * window:minimize   -> window:minimize
612  * window:maximize   -> window:maximize
613  * window:activate   -> window:activate
614  * window:deactivate -> window:deactivate
615  */
616 static gboolean
617 window_event_listener (GSignalInvocationHint * signal_hint,
618                        guint n_param_values,
619                        const GValue * param_values, gpointer data)
620 {
621   AtkObject *accessible;
622   GSignalQuery signal_query;
623   const gchar *name, *s;
624
625   g_signal_query (signal_hint->signal_id, &signal_query);
626   name = signal_query.signal_name;
627
628   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
629   s = atk_object_get_name (accessible);
630   emit_event (accessible, ITF_EVENT_WINDOW, name, "", 0, 0,
631               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
632
633   return TRUE;
634 }
635
636 /*---------------------------------------------------------------------------*/
637
638 /* 
639  * The document event listener handles the following ATK signals
640  * and converts them to AT-SPI events:
641  *
642  * Gtk:AtkDocument:load-complete ->  document:load-complete
643  * Gtk:AtkDocument:load-stopped  ->  document:load-stopped
644  * Gtk:AtkDocument:reload        ->  document:reload
645  */
646 static gboolean
647 document_event_listener (GSignalInvocationHint * signal_hint,
648                          guint n_param_values,
649                          const GValue * param_values, gpointer data)
650 {
651   AtkObject *accessible;
652   GSignalQuery signal_query;
653   const gchar *name, *s;
654
655   g_signal_query (signal_hint->signal_id, &signal_query);
656   name = signal_query.signal_name;
657
658   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
659   s = atk_object_get_name (accessible);
660   emit_event (accessible, ITF_EVENT_DOCUMENT, name, "", 0, 0,
661               DBUS_TYPE_STRING_AS_STRING, s, append_basic);
662
663   return TRUE;
664 }
665
666 /*---------------------------------------------------------------------------*/
667
668 /*
669  * Signal handler for  "Gtk:AtkComponent:bounds-changed". Converts
670  * this to an AT-SPI event - "object:bounds-changed".
671  */
672 static gboolean
673 bounds_event_listener (GSignalInvocationHint * signal_hint,
674                        guint n_param_values,
675                        const GValue * param_values, gpointer data)
676 {
677   AtkObject *accessible;
678   AtkRectangle *atk_rect;
679   GSignalQuery signal_query;
680   const gchar *name, *s;
681
682   g_signal_query (signal_hint->signal_id, &signal_query);
683   name = signal_query.signal_name;
684
685   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
686
687   if (G_VALUE_HOLDS_BOXED (param_values + 1))
688   {
689     atk_rect = g_value_get_boxed (param_values + 1);
690
691     emit_event (accessible, ITF_EVENT_OBJECT, name, "", 0, 0,
692                 "(iiii)", atk_rect, append_rect);
693   }
694   return TRUE;
695 }
696
697 /*---------------------------------------------------------------------------*/
698
699 /* 
700  * Handles the ATK signal 'Gtk:AtkObject:active-descendant-changed' and 
701  * converts it to the AT-SPI signal - 'object:active-descendant-changed'.
702  *
703  */
704 static gboolean
705 active_descendant_event_listener (GSignalInvocationHint * signal_hint,
706                                   guint n_param_values,
707                                   const GValue * param_values, gpointer data)
708 {
709   AtkObject *accessible;
710   AtkObject *child;
711   GSignalQuery signal_query;
712   const gchar *name, *minor;
713   gint detail1;
714
715   g_signal_query (signal_hint->signal_id, &signal_query);
716   name = signal_query.signal_name;
717
718   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
719   child = ATK_OBJECT (g_value_get_pointer (&param_values[1]));
720   g_return_val_if_fail (ATK_IS_OBJECT (child), TRUE);
721   minor = g_quark_to_string (signal_hint->detail);
722
723   detail1 = atk_object_get_index_in_parent (child);
724
725   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, 0,
726               "(so)", child, append_object);
727   return TRUE;
728 }
729
730 /*---------------------------------------------------------------------------*/
731
732 /* 
733  * Handles the ATK signal 'Gtk:AtkHypertext:link-selected' and
734  * converts it to the AT-SPI signal - 'object:link-selected'
735  *
736  */
737 static gboolean
738 link_selected_event_listener (GSignalInvocationHint * signal_hint,
739                               guint n_param_values,
740                               const GValue * param_values, gpointer data)
741 {
742   AtkObject *accessible;
743   GSignalQuery signal_query;
744   const gchar *name, *minor;
745   gint detail1;
746
747   g_signal_query (signal_hint->signal_id, &signal_query);
748   name = signal_query.signal_name;
749
750   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
751   minor = g_quark_to_string (signal_hint->detail);
752
753   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
754     detail1 = g_value_get_int (&param_values[1]);
755
756   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, 0,
757               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
758   return TRUE;
759 }
760
761 /*---------------------------------------------------------------------------*/
762
763 /* 
764  * Handles the ATK signal 'Gtk:AtkText:text-changed' and
765  * converts it to the AT-SPI signal - 'object:text-changed'
766  * This signal is deprecated by Gtk:AtkText:text-insert
767  * and Gtk:AtkText:text-remove
768  *
769  */
770 static gboolean
771 text_changed_event_listener (GSignalInvocationHint * signal_hint,
772                              guint n_param_values,
773                              const GValue * param_values, gpointer data)
774 {
775   AtkObject *accessible;
776   GSignalQuery signal_query;
777   const gchar *name, *minor;
778   gchar *selected;
779   gint detail1, detail2;
780
781   g_signal_query (signal_hint->signal_id, &signal_query);
782   name = signal_query.signal_name;
783
784   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
785   minor = g_quark_to_string (signal_hint->detail);
786
787   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
788     detail1 = g_value_get_int (&param_values[1]);
789
790   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
791     detail2 = g_value_get_int (&param_values[2]);
792
793   selected =
794     atk_text_get_text (ATK_TEXT (accessible), detail1, detail1 + detail2);
795
796   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
797               DBUS_TYPE_STRING_AS_STRING, selected, append_basic);
798   return TRUE;
799 }
800
801 /* 
802  * Handles the ATK signal 'Gtk:AtkText:text-insert' and
803  * converts it to the AT-SPI signal - 'object:text-changed'
804  *
805  */
806 static gboolean
807 text_insert_event_listener (GSignalInvocationHint * signal_hint,
808                             guint n_param_values,
809                             const GValue * param_values, gpointer data)
810 {
811   AtkObject *accessible;
812   guint text_changed_signal_id;
813   GSignalQuery signal_query;
814   const gchar *name;
815   gchar *minor, *text;
816   gint detail1, detail2;
817
818   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
819   /* Get signal name for 'Gtk:AtkText:text-changed' so
820    * we convert it to the AT-SPI signal - 'object:text-changed'
821    */
822   text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
823   g_signal_query (text_changed_signal_id, &signal_query);
824   name = signal_query.signal_name;
825
826
827   /* Add the insert and keep any detail coming from atk */
828   minor = g_quark_to_string (signal_hint->detail);
829   if (minor)
830     minor = g_strconcat ("insert:", minor, NULL);
831   else
832     minor = g_strdup ("insert");
833
834   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
835     detail1 = g_value_get_int (&param_values[1]);
836
837   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
838     detail2 = g_value_get_int (&param_values[2]);
839
840   if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
841     text = g_value_get_string (&param_values[3]);
842
843   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
844               DBUS_TYPE_STRING_AS_STRING, text, append_basic);
845   g_free (minor);
846   return TRUE;
847 }
848
849 /* 
850  * Handles the ATK signal 'Gtk:AtkText:text-remove' and
851  * converts it to the AT-SPI signal - 'object:text-changed'
852  *
853  */
854 static gboolean
855 text_remove_event_listener (GSignalInvocationHint * signal_hint,
856                             guint n_param_values,
857                             const GValue * param_values, gpointer data)
858 {
859   AtkObject *accessible;
860   guint text_changed_signal_id;
861   GSignalQuery signal_query;
862   const gchar *name;
863   gchar *minor, *text;
864   gint detail1, detail2;
865
866   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
867   /* Get signal name for 'Gtk:AtkText:text-changed' so
868    * we convert it to the AT-SPI signal - 'object:text-changed'
869    */
870   text_changed_signal_id = g_signal_lookup ("text-changed", G_OBJECT_TYPE (accessible));
871   g_signal_query (text_changed_signal_id, &signal_query);
872   name = signal_query.signal_name;
873
874   minor = g_quark_to_string (signal_hint->detail);
875
876   /* Add the delete and keep any detail coming from atk */
877   minor = g_quark_to_string (signal_hint->detail);
878   if (minor)
879     minor = g_strconcat ("delete:", minor, NULL);
880   else
881     minor = g_strdup ("delete");
882
883   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
884     detail1 = g_value_get_int (&param_values[1]);
885
886   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
887     detail2 = g_value_get_int (&param_values[2]);
888
889   if (G_VALUE_TYPE (&param_values[3]) == G_TYPE_STRING)
890     text = g_value_get_string (&param_values[3]);
891
892   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
893               DBUS_TYPE_STRING_AS_STRING, text, append_basic);
894   g_free (minor);
895   return TRUE;
896 }
897
898
899 /*---------------------------------------------------------------------------*/
900
901 /* 
902  * Handles the ATK signal 'Gtk:AtkText:text-selection-changed' and
903  * converts it to the AT-SPI signal - 'object:text-selection-changed'
904  *
905  */
906 static gboolean
907 text_selection_changed_event_listener (GSignalInvocationHint * signal_hint,
908                                        guint n_param_values,
909                                        const GValue * param_values,
910                                        gpointer data)
911 {
912   AtkObject *accessible;
913   GSignalQuery signal_query;
914   const gchar *name, *minor;
915   gint detail1, detail2;
916
917   g_signal_query (signal_hint->signal_id, &signal_query);
918   name = signal_query.signal_name;
919
920   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
921   minor = g_quark_to_string (signal_hint->detail);
922
923   if (G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
924     detail1 = g_value_get_int (&param_values[1]);
925
926   if (G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
927     detail2 = g_value_get_int (&param_values[2]);
928
929   emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
930               DBUS_TYPE_STRING_AS_STRING, "", append_basic);
931   return TRUE;
932 }
933
934 /*---------------------------------------------------------------------------*/
935
936 /*
937  * Children changed signal converter and forwarder.
938  *
939  * Klass (Interface) org.a11y.atspi.Event.Object
940  * Major is the signal name.
941  * Minor is 'add' or 'remove'
942  * detail1 is the index.
943  * detail2 is 0.
944  * any_data is the child reference.
945  */
946 static gboolean
947 children_changed_event_listener (GSignalInvocationHint * signal_hint,
948                                  guint n_param_values,
949                                  const GValue * param_values, gpointer data)
950 {
951   GSignalQuery signal_query;
952   const gchar *name, *minor;
953   gint detail1, detail2 = 0;
954
955   AtkObject *accessible, *ao=NULL;
956   gpointer child;
957
958   g_signal_query (signal_hint->signal_id, &signal_query);
959   name = signal_query.signal_name;
960
961   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
962   minor = g_quark_to_string (signal_hint->detail);
963
964   detail1 = g_value_get_uint (param_values + 1);
965   child = g_value_get_pointer (param_values + 2);
966
967   if (ATK_IS_OBJECT (child))
968     {
969       ao = ATK_OBJECT (child);
970       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
971                   "(so)", ao, append_object);
972     }
973   else if ((minor != NULL) && (strcmp (minor, "add") == 0))
974     {
975       ao = atk_object_ref_accessible_child (accessible, 
976                                             detail1);
977       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
978                   "(so)", ao, append_object);
979     }
980   else
981     {
982       emit_event (accessible, ITF_EVENT_OBJECT, name, minor, detail1, detail2,
983                   "(so)", ao, append_object);
984     }
985  
986   return TRUE;
987 }
988
989 /*---------------------------------------------------------------------------*/
990
991 static void
992 toplevel_added_event_listener (AtkObject * accessible,
993                                guint index, AtkObject * child)
994 {
995   emit_event (accessible, ITF_EVENT_OBJECT, "children-changed", "add", index, 0,
996               "(so)", child, append_object);
997 }
998
999 static void
1000 toplevel_removed_event_listener (AtkObject * accessible,
1001                                  guint index, AtkObject * child)
1002 {
1003   emit_event (accessible, ITF_EVENT_OBJECT, "children-changed", "remove", index, 0,
1004               "(so)", child, append_object);
1005 }
1006
1007 /*---------------------------------------------------------------------------*/
1008
1009 /*
1010  * Generic signal converter and forwarder.
1011  *
1012  * Klass (Interface) org.a11y.atspi.Event.Object
1013  * Major is the signal name.
1014  * Minor is NULL.
1015  * detail1 is 0.
1016  * detail2 is 0.
1017  * any_data is NULL.
1018  */
1019 static gboolean
1020 generic_event_listener (GSignalInvocationHint * signal_hint,
1021                         guint n_param_values,
1022                         const GValue * param_values, gpointer data)
1023 {
1024   AtkObject *accessible;
1025   GSignalQuery signal_query;
1026   const gchar *name;
1027   int detail1 = 0, detail2 = 0;
1028
1029   g_signal_query (signal_hint->signal_id, &signal_query);
1030   name = signal_query.signal_name;
1031
1032   accessible = ATK_OBJECT (g_value_get_object (&param_values[0]));
1033
1034   if (n_param_values > 1 && G_VALUE_TYPE (&param_values[1]) == G_TYPE_INT)
1035     detail1 = g_value_get_int (&param_values[1]);
1036
1037   if (n_param_values > 2 && G_VALUE_TYPE (&param_values[2]) == G_TYPE_INT)
1038     detail2 = g_value_get_int (&param_values[2]);
1039
1040   emit_event (accessible, ITF_EVENT_OBJECT, name, "", detail1, detail2,
1041               DBUS_TYPE_INT32_AS_STRING, 0, append_basic);
1042   return TRUE;
1043 }
1044
1045 /*---------------------------------------------------------------------------*/
1046
1047 /*
1048  * Registers the provided function as a handler for the given signal name
1049  * and stores the signal id returned so that the function may be
1050  * de-registered later.
1051  */
1052 static void
1053 add_signal_listener (GSignalEmissionHook listener, const char *signal_name)
1054 {
1055   guint id;
1056
1057   id = atk_add_global_event_listener (listener, signal_name);
1058   g_array_append_val (listener_ids, id);
1059 }
1060
1061 /*
1062  * Initialization for the signal handlers.
1063  *
1064  * Registers all required signal handlers.
1065  */
1066 void
1067 spi_atk_register_event_listeners (void)
1068 {
1069   /*
1070    * Kludge to make sure the Atk interface types are registered, otherwise
1071    * the AtkText signal handlers below won't get registered
1072    */
1073   GObject *ao = g_object_new (ATK_TYPE_OBJECT, NULL);
1074   AtkObject *bo = atk_no_op_object_new (ao);
1075
1076   g_object_unref (G_OBJECT (bo));
1077   g_object_unref (ao);
1078
1079   /* Register for focus event notifications, and register app with central registry  */
1080   listener_ids = g_array_sized_new (FALSE, TRUE, sizeof (guint), 16);
1081
1082   atk_bridge_focus_tracker_id = atk_add_focus_tracker (focus_tracker);
1083
1084   add_signal_listener (property_event_listener,
1085                        "Gtk:AtkObject:property-change");
1086   add_signal_listener (window_event_listener, "window:create");
1087   add_signal_listener (window_event_listener, "window:destroy");
1088   add_signal_listener (window_event_listener, "window:minimize");
1089   add_signal_listener (window_event_listener, "window:maximize");
1090   add_signal_listener (window_event_listener, "window:restore");
1091   add_signal_listener (window_event_listener, "window:activate");
1092   add_signal_listener (window_event_listener, "window:deactivate");
1093   add_signal_listener (document_event_listener,
1094                        "Gtk:AtkDocument:load-complete");
1095   add_signal_listener (document_event_listener, "Gtk:AtkDocument:reload");
1096   add_signal_listener (document_event_listener,
1097                        "Gtk:AtkDocument:load-stopped");
1098   /* TODO Fake this event on the client side */
1099   add_signal_listener (state_event_listener, "Gtk:AtkObject:state-change");
1100   /* TODO */
1101   add_signal_listener (active_descendant_event_listener,
1102                        "Gtk:AtkObject:active-descendant-changed");
1103   add_signal_listener (bounds_event_listener,
1104                        "Gtk:AtkComponent:bounds-changed");
1105   add_signal_listener (text_selection_changed_event_listener,
1106                        "Gtk:AtkText:text-selection-changed");
1107   add_signal_listener (text_changed_event_listener,
1108                        "Gtk:AtkText:text-changed");
1109   add_signal_listener (text_insert_event_listener,
1110                        "Gtk:AtkText:text-insert");
1111   add_signal_listener (text_remove_event_listener,
1112                        "Gtk:AtkText:text-remove");
1113   add_signal_listener (link_selected_event_listener,
1114                        "Gtk:AtkHypertext:link-selected");
1115   add_signal_listener (generic_event_listener,
1116                        "Gtk:AtkObject:visible-data-changed");
1117   add_signal_listener (generic_event_listener,
1118                        "Gtk:AtkSelection:selection-changed");
1119   add_signal_listener (generic_event_listener,
1120                        "Gtk:AtkText:text-attributes-changed");
1121   add_signal_listener (generic_event_listener,
1122                        "Gtk:AtkText:text-caret-moved");
1123   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-inserted");
1124   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-reordered");
1125   add_signal_listener (generic_event_listener, "Gtk:AtkTable:row-deleted");
1126   add_signal_listener (generic_event_listener,
1127                        "Gtk:AtkTable:column-inserted");
1128   add_signal_listener (generic_event_listener,
1129                        "Gtk:AtkTable:column-reordered");
1130   add_signal_listener (generic_event_listener, "Gtk:AtkTable:column-deleted");
1131   add_signal_listener (generic_event_listener, "Gtk:AtkTable:model-changed");
1132
1133   /* Children signal listeners */
1134   atk_add_global_event_listener (children_changed_event_listener,
1135                                  "Gtk:AtkObject:children-changed");
1136
1137 #if 0
1138   g_signal_connect (G_OBJECT (spi_global_app_data->root),
1139                     "children-changed::add",
1140                     (GCallback) toplevel_added_event_listener, NULL);
1141
1142   g_signal_connect (G_OBJECT (spi_global_app_data->root),
1143                     "children-changed::remove",
1144                     (GCallback) toplevel_removed_event_listener, NULL);
1145 #endif
1146
1147   /*
1148    * May add the following listeners to implement preemptive key listening for GTK+
1149    *
1150    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-press-event");
1151    * atk_add_global_event_listener (spi_atk_bridge_widgetkey_listener, "Gtk:GtkWidget:key-release-event");
1152    */
1153   atk_bridge_key_event_listener_id =
1154     atk_add_key_event_listener (spi_atk_bridge_key_listener, NULL);
1155 }
1156
1157 /*---------------------------------------------------------------------------*/
1158
1159 /* 
1160  * De-registers all ATK signal handlers.
1161  */
1162 void
1163 spi_atk_deregister_event_listeners (void)
1164 {
1165   gint i;
1166   GArray *ids = listener_ids;
1167   listener_ids = NULL;
1168
1169   if (atk_bridge_focus_tracker_id)
1170     atk_remove_focus_tracker (atk_bridge_focus_tracker_id);
1171
1172   for (i = 0; ids && i < ids->len; i++)
1173     {
1174       atk_remove_global_event_listener (g_array_index (ids, guint, i));
1175     }
1176
1177   if (atk_bridge_key_event_listener_id)
1178     atk_remove_key_event_listener (atk_bridge_key_event_listener_id);
1179 }
1180
1181 /*---------------------------------------------------------------------------*/
1182
1183 /*
1184  * TODO This function seems out of place here.
1185  *
1186  * Emits fake deactivate signals on all top-level windows.
1187  * Used when shutting down AT-SPI, ensuring that all
1188  * windows have been removed on the client side.
1189  */
1190 void
1191 spi_atk_tidy_windows (void)
1192 {
1193   AtkObject *root;
1194   gint n_children;
1195   gint i;
1196
1197   root = atk_get_root ();
1198   n_children = atk_object_get_n_accessible_children (root);
1199   for (i = 0; i < n_children; i++)
1200     {
1201       AtkObject *child;
1202       AtkStateSet *stateset;
1203       const gchar *name;
1204
1205       child = atk_object_ref_accessible_child (root, i);
1206       stateset = atk_object_ref_state_set (child);
1207
1208       name = atk_object_get_name (child);
1209       if (atk_state_set_contains_state (stateset, ATK_STATE_ACTIVE))
1210         {
1211           emit_event (child, ITF_EVENT_WINDOW, "deactivate", NULL, 0, 0,
1212                       DBUS_TYPE_STRING_AS_STRING, name, append_basic);
1213         }
1214       g_object_unref (stateset);
1215
1216       emit_event (child, ITF_EVENT_WINDOW, "destroy", NULL, 0, 0,
1217                   DBUS_TYPE_STRING_AS_STRING, name, append_basic);
1218       g_object_unref (child);
1219     }
1220 }
1221
1222 gboolean
1223 spi_event_is_subtype (gchar **needle, gchar **haystack)
1224 {
1225   while (*haystack && **haystack)
1226     {
1227       if (g_strcmp0 (*needle, *haystack))
1228         return FALSE;
1229       needle++;
1230       haystack++;
1231     }
1232   return TRUE;
1233 }
1234
1235 /*END------------------------------------------------------------------------*/