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