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