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