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