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