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