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