2.34.0
[platform/upstream/at-spi2-core.git] / atspi / atspi-misc.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2001, 2002 Sun Microsystems Inc.,
6  * Copyright 2001, 2002 Ximian, Inc.
7  * Copyright 2010, 2011 Novell, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 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  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24
25 /*
26  *
27  * Basic SPI initialization and event loop function prototypes
28  *
29  */
30
31 #include "atspi-private.h"
32 #ifdef HAVE_X11
33 #include "X11/Xlib.h"
34 #endif
35 #include "atspi-gmain.h"
36 #include <stdio.h>
37 #include <string.h>
38
39 static void handle_get_items (DBusPendingCall *pending, void *user_data);
40
41 static DBusConnection *bus = NULL;
42 static GHashTable *live_refs = NULL;
43 static gint method_call_timeout = 800;
44 static gint app_startup_time = 15000;
45 static gboolean allow_sync = TRUE;
46
47 GMainLoop *atspi_main_loop;
48 GMainContext *atspi_main_context;
49 gboolean atspi_no_cache;
50
51 const char *atspi_path_dec = ATSPI_DBUS_PATH_DEC;
52 const char *atspi_path_registry = ATSPI_DBUS_PATH_REGISTRY;
53 const char *atspi_path_root = ATSPI_DBUS_PATH_ROOT;
54 const char *atspi_bus_registry = ATSPI_DBUS_NAME_REGISTRY;
55 const char *atspi_interface_accessible = ATSPI_DBUS_INTERFACE_ACCESSIBLE;
56 const char *atspi_interface_action = ATSPI_DBUS_INTERFACE_ACTION;
57 const char *atspi_interface_application = ATSPI_DBUS_INTERFACE_APPLICATION;
58 const char *atspi_interface_collection = ATSPI_DBUS_INTERFACE_COLLECTION;
59 const char *atspi_interface_component = ATSPI_DBUS_INTERFACE_COMPONENT;
60 const char *atspi_interface_dec = ATSPI_DBUS_INTERFACE_DEC;
61 const char *atspi_interface_device_event_listener = ATSPI_DBUS_INTERFACE_DEVICE_EVENT_LISTENER;
62 const char *atspi_interface_document = ATSPI_DBUS_INTERFACE_DOCUMENT;
63 const char *atspi_interface_editable_text = ATSPI_DBUS_INTERFACE_EDITABLE_TEXT;
64 const char *atspi_interface_event_object = ATSPI_DBUS_INTERFACE_EVENT_OBJECT;
65 const char *atspi_interface_hyperlink = ATSPI_DBUS_INTERFACE_HYPERLINK;
66 const char *atspi_interface_hypertext = ATSPI_DBUS_INTERFACE_HYPERTEXT;
67 const char *atspi_interface_image = ATSPI_DBUS_INTERFACE_IMAGE;
68 const char *atspi_interface_registry = ATSPI_DBUS_INTERFACE_REGISTRY;
69 const char *atspi_interface_selection = ATSPI_DBUS_INTERFACE_SELECTION;
70 const char *atspi_interface_table = ATSPI_DBUS_INTERFACE_TABLE;
71 const char *atspi_interface_table_cell = ATSPI_DBUS_INTERFACE_TABLE_CELL;
72 const char *atspi_interface_text = ATSPI_DBUS_INTERFACE_TEXT;
73 const char *atspi_interface_cache = ATSPI_DBUS_INTERFACE_CACHE;
74 const char *atspi_interface_value = ATSPI_DBUS_INTERFACE_VALUE;
75
76 static const char *interfaces[] =
77 {
78   ATSPI_DBUS_INTERFACE_ACCESSIBLE,
79   ATSPI_DBUS_INTERFACE_ACTION,
80   ATSPI_DBUS_INTERFACE_APPLICATION,
81   ATSPI_DBUS_INTERFACE_COLLECTION,
82   ATSPI_DBUS_INTERFACE_COMPONENT,
83   ATSPI_DBUS_INTERFACE_DOCUMENT,
84   ATSPI_DBUS_INTERFACE_EDITABLE_TEXT,
85   ATSPI_DBUS_INTERFACE_HYPERLINK,
86   ATSPI_DBUS_INTERFACE_HYPERTEXT,
87   ATSPI_DBUS_INTERFACE_IMAGE,
88   "org.a11y.atspi.LoginHelper",
89   ATSPI_DBUS_INTERFACE_SELECTION,
90   ATSPI_DBUS_INTERFACE_TABLE,
91   ATSPI_DBUS_INTERFACE_TABLE_CELL,
92   ATSPI_DBUS_INTERFACE_TEXT,
93   ATSPI_DBUS_INTERFACE_VALUE,
94   NULL
95 };
96
97 gint
98 _atspi_get_iface_num (const char *iface)
99 {
100   /* TODO: Use a binary search or hash to improve performance */
101   int i;
102
103   for (i = 0; interfaces[i]; i++)
104   {
105     if (!strcmp(iface, interfaces[i])) return i;
106   }
107   return -1;
108 }
109
110 GHashTable *
111 _atspi_get_live_refs (void)
112 {
113   if (!live_refs) 
114     {
115       live_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
116     }
117   return live_refs;
118 }
119
120 /* TODO: Add an application parameter */
121 DBusConnection *
122 _atspi_bus ()
123 {
124   if (!bus)
125     atspi_init ();
126   if (!bus)
127     g_error ("AT-SPI: Couldn't connect to accessibility bus. Is at-spi-bus-launcher running?");
128   return bus;
129 }
130
131 #define APP_IS_REGISTRY(app) (!strcmp (app->bus_name, atspi_bus_registry))
132
133 static AtspiAccessible *desktop;
134
135 static void
136 cleanup ()
137 {
138   GHashTable *refs;
139   gint i;
140
141   refs = live_refs;
142   live_refs = NULL;
143   if (refs)
144     {
145       g_hash_table_destroy (refs);
146     }
147
148   if (bus)
149     {
150       dbus_connection_close (bus);
151       dbus_connection_unref (bus);
152       bus = NULL;
153     }
154
155   if (!desktop)
156     return;
157
158   /* TODO: Do we need this code, or should we just dispose the desktop? */
159   for (i = desktop->children->len - 1; i >= 0; i--)
160   {
161     AtspiAccessible *child = g_ptr_array_index (desktop->children, i);
162     if (child->parent.app)
163       g_object_run_dispose (G_OBJECT (child->parent.app));
164     g_object_run_dispose (G_OBJECT (child));
165   }
166
167   g_object_run_dispose (G_OBJECT (desktop->parent.app));
168   g_object_unref (desktop);
169   desktop = NULL;
170 }
171
172 static gboolean atspi_inited = FALSE;
173
174 static GHashTable *app_hash = NULL;
175
176 static void
177 handle_get_bus_address (DBusPendingCall *pending, void *user_data)
178 {
179   AtspiApplication *app = user_data;
180   DBusMessage *reply = dbus_pending_call_steal_reply (pending);
181   DBusMessage *message;
182   const char *address;
183   DBusPendingCall *new_pending;
184
185   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
186   {
187     if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &address,
188                                DBUS_TYPE_INVALID) && address [0])
189     {
190       DBusError error;
191       DBusConnection *bus;
192
193       dbus_error_init (&error);
194       bus = dbus_connection_open_private (address, &error);
195       if (bus)
196       {
197         if (app->bus)
198           {
199             dbus_connection_unref (app->bus);
200           }
201         app->bus = bus;
202         atspi_dbus_connection_setup_with_g_main(bus, g_main_context_default());
203       }
204       else
205       {
206         if (!strcmp (error.name, DBUS_ERROR_FILE_NOT_FOUND))
207           g_warning ("AT-SPI: Unable to open bus connection: %s", error.message);
208         dbus_error_free (&error);
209       }
210     }
211   }
212   dbus_message_unref (reply);
213   dbus_pending_call_unref (pending);
214
215   if (!app->bus)
216     return; /* application has gone away / been disposed */
217
218   message = dbus_message_new_method_call (app->bus_name,
219                                           "/org/a11y/atspi/cache",
220                                           atspi_interface_cache, "GetItems");
221
222   dbus_connection_send_with_reply (app->bus, message, &new_pending, 2000);
223   dbus_message_unref (message);
224   if (!new_pending)
225     return;
226   dbus_pending_call_set_notify (new_pending, handle_get_items, app, NULL);
227 }
228
229 static AtspiApplication *
230 get_application (const char *bus_name)
231 {
232   AtspiApplication *app = NULL;
233   char *bus_name_dup;
234   DBusMessage *message;
235   DBusPendingCall *pending = NULL;
236
237   if (!app_hash)
238   {
239     app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_object_unref);
240     if (!app_hash) return NULL;
241   }
242   app = g_hash_table_lookup (app_hash, bus_name);
243   if (app) return app;
244   bus_name_dup = g_strdup (bus_name);
245   if (!bus_name_dup) return NULL;
246   // TODO: change below to something that will send state-change:defunct notification if necessary */
247   app = _atspi_application_new (bus_name);
248   app->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
249   app->bus = dbus_connection_ref (_atspi_bus ());
250   gettimeofday (&app->time_added, NULL);
251   app->cache = ATSPI_CACHE_UNDEFINED;
252   g_hash_table_insert (app_hash, bus_name_dup, app);
253   message = dbus_message_new_method_call (bus_name, atspi_path_root,
254                                           atspi_interface_application, "GetApplicationBusAddress");
255
256   dbus_connection_send_with_reply (app->bus, message, &pending, 2000);
257   dbus_message_unref (message);
258   if (!pending)
259   {
260     g_hash_table_remove (app_hash, bus_name_dup);
261     return NULL;
262   }
263   dbus_pending_call_set_notify (pending, handle_get_bus_address, app, NULL);
264   return app;
265 }
266
267 static AtspiAccessible *
268 ref_accessible (const char *app_name, const char *path)
269 {
270   AtspiApplication *app;
271   AtspiAccessible *a;
272
273   if (!strcmp (path, ATSPI_DBUS_PATH_NULL))
274     return NULL;
275
276   app = get_application (app_name);
277
278   if (!strcmp (path, "/org/a11y/atspi/accessible/root"))
279   {
280     if (!app->root)
281     {
282       app->root = _atspi_accessible_new (app, atspi_path_root);
283       app->root->accessible_parent = atspi_get_desktop (0);
284       g_ptr_array_add (app->root->accessible_parent->children, g_object_ref (app->root));
285     }
286     return g_object_ref (app->root);
287   }
288
289   a = g_hash_table_lookup (app->hash, path);
290   if (a)
291   {
292     return g_object_ref (a);
293   }
294   a = _atspi_accessible_new (app, path);
295   if (!a)
296     return NULL;
297   g_hash_table_insert (app->hash, g_strdup (a->parent.path), g_object_ref (a));
298   return a;
299 }
300
301 static AtspiHyperlink *
302 ref_hyperlink (const char *app_name, const char *path)
303 {
304   AtspiApplication *app = get_application (app_name);
305   AtspiHyperlink *hyperlink;
306
307   if (!strcmp (path, ATSPI_DBUS_PATH_NULL))
308     return NULL;
309
310   hyperlink = g_hash_table_lookup (app->hash, path);
311   if (hyperlink)
312   {
313     return g_object_ref (hyperlink);
314   }
315   hyperlink = _atspi_hyperlink_new (app, path);
316   g_hash_table_insert (app->hash, g_strdup (hyperlink->parent.path), hyperlink);
317   /* TODO: This should be a weak ref */
318   g_object_ref (hyperlink);     /* for the hash */
319   return hyperlink;
320 }
321
322 typedef struct
323 {
324   char *path;
325   char *parent;
326   GArray *children;
327   GArray *interfaces;
328   char *name;
329   dbus_uint32_t role;
330   char *description;
331   GArray *state_bitflags;
332 } CACHE_ADDITION;
333
334 static DBusHandlerResult
335 handle_remove_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
336 {
337   const char *sender = dbus_message_get_sender (message);
338   AtspiApplication *app;
339   const char *path;
340   DBusMessageIter iter, iter_struct;
341   const char *signature = dbus_message_get_signature (message);
342   AtspiAccessible *a;
343
344   if (strcmp (signature, "(so)") != 0)
345   {
346     g_warning ("AT-SPI: Unknown signature %s for RemoveAccessible", signature);
347     return DBUS_HANDLER_RESULT_HANDLED;
348   }
349
350   dbus_message_iter_init (message, &iter);
351   dbus_message_iter_recurse (&iter, &iter_struct);
352   dbus_message_iter_get_basic (&iter_struct, &sender);
353   dbus_message_iter_next (&iter_struct);
354   dbus_message_iter_get_basic (&iter_struct, &path);
355   app = get_application (sender);
356   a = ref_accessible (sender, path);
357   if (!a)
358     return DBUS_HANDLER_RESULT_HANDLED;
359   g_object_run_dispose (G_OBJECT (a));
360   g_hash_table_remove (app->hash, a->parent.path);
361   g_object_unref (a);   /* unref our own ref */
362   return DBUS_HANDLER_RESULT_HANDLED;
363 }
364
365 static DBusHandlerResult
366 handle_name_owner_changed (DBusConnection *bus, DBusMessage *message, void *user_data)
367 {
368   const char *name, *new, *old;
369   static gboolean registry_lost = FALSE;
370
371   if (!dbus_message_get_args (message, NULL,
372                               DBUS_TYPE_STRING, &name,
373                               DBUS_TYPE_STRING, &old,
374                               DBUS_TYPE_STRING, &new,
375                               DBUS_TYPE_INVALID))
376   {
377     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
378   }
379
380   if (!strcmp (name, "org.a11y.atspi.Registry"))
381   {
382     if (registry_lost && !old[0])
383     {
384       _atspi_reregister_event_listeners ();
385       _atspi_reregister_device_listeners ();
386       registry_lost = FALSE;
387     }
388     else if (!new[0])
389       registry_lost = TRUE;
390   }
391   else if (app_hash)
392   {
393     AtspiApplication *app = g_hash_table_lookup (app_hash, old);
394     if (app && !strcmp (app->bus_name, old))
395       g_object_run_dispose (G_OBJECT (app));
396   }
397   return DBUS_HANDLER_RESULT_HANDLED;
398 }
399
400 static gboolean
401 add_app_to_desktop (AtspiAccessible *a, const char *bus_name)
402 {
403   AtspiAccessible *obj = ref_accessible (bus_name, atspi_path_root);
404   /* The app will be added to the desktop as a side-effect of calling
405    * ref_accessible */
406   g_object_unref (obj);
407   return (obj != NULL);
408 }
409
410 void
411 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
412 {
413   DBusMessageIter iter_struct;
414
415   dbus_message_iter_recurse (iter, &iter_struct);
416   dbus_message_iter_get_basic (&iter_struct, app_name);
417   dbus_message_iter_next (&iter_struct);
418   dbus_message_iter_get_basic (&iter_struct, path);
419   dbus_message_iter_next (iter);
420 }
421
422 static void
423 add_accessible_from_iter (DBusMessageIter *iter)
424 {
425   DBusMessageIter iter_struct, iter_array;
426   const char *app_name, *path;
427   AtspiAccessible *accessible;
428   const char *name, *description;
429   dbus_uint32_t role;
430   gboolean children_cached = FALSE;
431   dbus_int32_t count, index;
432
433   dbus_message_iter_recurse (iter, &iter_struct);
434
435   /* get accessible */
436   get_reference_from_iter (&iter_struct, &app_name, &path);
437   accessible = ref_accessible (app_name, path);
438   if (!accessible)
439     return;
440
441   /* Get application: TODO */
442   dbus_message_iter_next (&iter_struct);
443
444   /* get parent */
445   get_reference_from_iter (&iter_struct, &app_name, &path);
446   if (accessible->accessible_parent)
447     g_object_unref (accessible->accessible_parent);
448   accessible->accessible_parent = ref_accessible (app_name, path);
449
450   if (dbus_message_iter_get_arg_type (&iter_struct) == 'i')
451   {
452     /* Get index in parent */
453     dbus_message_iter_get_basic (&iter_struct, &index);
454     if (index >= 0 && accessible->accessible_parent)
455     {
456       if (index >= accessible->accessible_parent->children->len)
457         g_ptr_array_set_size (accessible->accessible_parent->children, index + 1);
458       g_ptr_array_index (accessible->accessible_parent->children, index) = g_object_ref (accessible);
459     }
460
461     /* get child count */
462     dbus_message_iter_next (&iter_struct);
463     dbus_message_iter_get_basic (&iter_struct, &count);
464     if (count >= 0)
465     {
466       g_ptr_array_set_size (accessible->children, count);
467       children_cached = TRUE;
468     }
469   }
470   else if (dbus_message_iter_get_arg_type (&iter_struct) == 'a')
471   {
472     /* It's the old API with a list of children */
473     /* TODO: Perhaps remove this code eventually */
474     dbus_message_iter_recurse (&iter_struct, &iter_array);
475     while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
476     {
477       AtspiAccessible *child;
478       get_reference_from_iter (&iter_array, &app_name, &path);
479       child = ref_accessible (app_name, path);
480       g_ptr_array_remove (accessible->children, child);
481       g_ptr_array_add (accessible->children, child);
482     }
483     children_cached = TRUE;
484   }
485
486   /* interfaces */
487   dbus_message_iter_next (&iter_struct);
488   _atspi_dbus_set_interfaces (accessible, &iter_struct);
489   dbus_message_iter_next (&iter_struct);
490
491   /* name */
492   if (accessible->name)
493     g_free (accessible->name);
494   dbus_message_iter_get_basic (&iter_struct, &name);
495   accessible->name = g_strdup (name);
496   dbus_message_iter_next (&iter_struct);
497
498   /* role */
499   dbus_message_iter_get_basic (&iter_struct, &role);
500   accessible->role = role;
501   dbus_message_iter_next (&iter_struct);
502
503   /* description */
504   if (accessible->description)
505     g_free (accessible->description);
506   dbus_message_iter_get_basic (&iter_struct, &description);
507   accessible->description = g_strdup (description);
508   dbus_message_iter_next (&iter_struct);
509
510   _atspi_dbus_set_state (accessible, &iter_struct);
511   dbus_message_iter_next (&iter_struct);
512
513   _atspi_accessible_add_cache (accessible, ATSPI_CACHE_NAME | ATSPI_CACHE_ROLE |
514                                ATSPI_CACHE_PARENT | ATSPI_CACHE_DESCRIPTION);
515   if (!atspi_state_set_contains (accessible->states,
516                                        ATSPI_STATE_MANAGES_DESCENDANTS) &&
517                                        children_cached)
518     _atspi_accessible_add_cache (accessible, ATSPI_CACHE_CHILDREN);
519
520   /* This is a bit of a hack since the cache holds a ref, so we don't need
521    * the one provided for us anymore */
522   g_object_unref (accessible);
523 }
524
525 static void
526 handle_get_items (DBusPendingCall *pending, void *user_data)
527 {
528   DBusMessage *reply = dbus_pending_call_steal_reply (pending);
529   DBusMessageIter iter, iter_array;
530
531   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
532   {
533     const char *sender = dbus_message_get_sender (reply);
534     const char *error = NULL;
535     const char *error_name = dbus_message_get_error_name (reply);
536     if (!strcmp (error_name, DBUS_ERROR_SERVICE_UNKNOWN)
537      || !strcmp (error_name, DBUS_ERROR_NO_REPLY))
538     {
539     }
540     else
541     {
542       dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &error,
543                              DBUS_TYPE_INVALID);
544       g_warning ("AT-SPI: Error in GetItems, sender=%s, error=%s", sender, error);
545     }
546     dbus_message_unref (reply);
547     dbus_pending_call_unref (pending);
548     return;
549   }
550
551   dbus_message_iter_init (reply, &iter);
552   dbus_message_iter_recurse (&iter, &iter_array);
553   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
554   {
555     add_accessible_from_iter (&iter_array);
556     dbus_message_iter_next (&iter_array);
557   }
558   dbus_message_unref (reply);
559   dbus_pending_call_unref (pending);
560 }
561
562 /* TODO: Do we stil need this function? */
563 static AtspiAccessible *
564 ref_accessible_desktop (AtspiApplication *app)
565 {
566   GError *error;
567   DBusMessage *message, *reply;
568   DBusMessageIter iter, iter_array;
569   gchar *bus_name_dup;
570
571   if (desktop)
572   {
573     g_object_ref (desktop);
574     return desktop;
575   }
576   desktop = _atspi_accessible_new (app, atspi_path_root);
577   if (!desktop)
578   {
579     return NULL;
580   }
581   g_hash_table_insert (app->hash, g_strdup (desktop->parent.path),
582                        g_object_ref (desktop));
583   app->root = g_object_ref (desktop);
584   desktop->name = g_strdup ("main");
585   message = dbus_message_new_method_call (atspi_bus_registry,
586         atspi_path_root,
587         atspi_interface_accessible,
588         "GetChildren");
589   if (!message)
590     return NULL;
591   error = NULL;
592   reply = _atspi_dbus_send_with_reply_and_block (message, &error);
593   if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
594   {
595     if (error != NULL)
596     {
597       g_warning ("Couldn't get application list: %s", error->message);
598       g_clear_error (&error);
599     }
600     if (reply)
601       dbus_message_unref (reply);
602     return NULL;
603   }
604   dbus_message_iter_init (reply, &iter);
605   dbus_message_iter_recurse (&iter, &iter_array);
606   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
607   {
608     const char *app_name, *path;
609     get_reference_from_iter (&iter_array, &app_name, &path);
610     add_app_to_desktop (desktop, app_name);
611   }
612
613   /* Record the alternate name as an alias for org.a11y.atspi.Registry */
614   bus_name_dup = g_strdup (dbus_message_get_sender (reply));
615   if (bus_name_dup)
616     g_hash_table_insert (app_hash, bus_name_dup, app);
617
618   dbus_message_unref (reply);
619
620   return g_object_ref (desktop);
621 }
622
623 AtspiAccessible *
624 _atspi_ref_accessible (const char *app, const char *path)
625 {
626   AtspiApplication *a = get_application (app);
627   if (!a)
628     return NULL;
629   if ( APP_IS_REGISTRY(a))
630   {
631     if (!a->root)
632       g_object_unref (ref_accessible_desktop (a));      /* sets a->root */
633     return g_object_ref (a->root);
634   }
635   return ref_accessible (app, path);
636 }
637
638 AtspiAccessible *
639 _atspi_dbus_return_accessible_from_message (DBusMessage *message)
640 {
641   DBusMessageIter iter;
642   AtspiAccessible *retval = NULL;
643   const char *signature;
644
645   if (!message)
646     return NULL;
647
648   signature = dbus_message_get_signature (message);
649   if (!strcmp (signature, "(so)"))
650   {
651     dbus_message_iter_init (message, &iter);
652     retval =  _atspi_dbus_return_accessible_from_iter (&iter);
653   }
654   else
655   {
656     g_warning ("AT-SPI: Called _atspi_dbus_return_accessible_from_message with strange signature %s", signature);
657   }
658   dbus_message_unref (message);
659   return retval;
660 }
661
662 AtspiAccessible *
663 _atspi_dbus_return_accessible_from_iter (DBusMessageIter *iter)
664 {
665   const char *app_name, *path;
666
667   get_reference_from_iter (iter, &app_name, &path);
668   return ref_accessible (app_name, path);
669 }
670
671 AtspiHyperlink *
672 _atspi_dbus_return_hyperlink_from_message (DBusMessage *message)
673 {
674   DBusMessageIter iter;
675   AtspiHyperlink *retval = NULL;
676   const char *signature;
677    
678   if (!message)
679     return NULL;
680
681   signature = dbus_message_get_signature (message);
682   if (!strcmp (signature, "(so)"))
683   {
684     dbus_message_iter_init (message, &iter);
685     retval =  _atspi_dbus_return_hyperlink_from_iter (&iter);
686   }
687   else
688   {
689     g_warning ("AT-SPI: Called _atspi_dbus_return_hyperlink_from_message with strange signature %s", signature);
690   }
691   dbus_message_unref (message);
692   return retval;
693 }
694
695 AtspiHyperlink *
696 _atspi_dbus_return_hyperlink_from_iter (DBusMessageIter *iter)
697 {
698   const char *app_name, *path;
699
700   get_reference_from_iter (iter, &app_name, &path);
701   return ref_hyperlink (app_name, path);
702 }
703
704 const char *cache_signal_type = "((so)(so)(so)iiassusau)";
705 const char *old_cache_signal_type = "((so)(so)(so)a(so)assusau)";
706
707 static DBusHandlerResult
708 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
709 {
710   DBusMessageIter iter;
711   const char *signature = dbus_message_get_signature (message);
712
713   if (strcmp (signature, cache_signal_type) != 0 &&
714       strcmp (signature, old_cache_signal_type) != 0)
715   {
716     g_warning ("AT-SPI: AddAccessible with unknown signature %s\n", signature);
717     return DBUS_HANDLER_RESULT_HANDLED;
718   }
719
720   dbus_message_iter_init (message, &iter);
721   add_accessible_from_iter (&iter);
722   return DBUS_HANDLER_RESULT_HANDLED;
723 }
724
725 typedef struct
726 {
727   DBusConnection *bus;
728   DBusMessage *message;
729   void *data;
730 } BusDataClosure;
731
732 static GSource *process_deferred_messages_source = NULL;
733
734 static void
735 process_deferred_message (BusDataClosure *closure)
736 {
737   int type = dbus_message_get_type (closure->message);
738   const char *interface = dbus_message_get_interface (closure->message);
739
740   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
741       !strncmp (interface, "org.a11y.atspi.Event.", 21))
742   {
743     _atspi_dbus_handle_event (closure->bus, closure->message, closure->data);
744   }
745   if (dbus_message_is_method_call (closure->message, atspi_interface_device_event_listener, "NotifyEvent"))
746   {
747     _atspi_dbus_handle_DeviceEvent (closure->bus,
748                                    closure->message, closure->data);
749   }
750   if (dbus_message_is_signal (closure->message, atspi_interface_cache, "AddAccessible"))
751   {
752     handle_add_accessible (closure->bus, closure->message, closure->data);
753   }
754   if (dbus_message_is_signal (closure->message, atspi_interface_cache, "RemoveAccessible"))
755   {
756     handle_remove_accessible (closure->bus, closure->message, closure->data);
757   }
758   if (dbus_message_is_signal (closure->message, "org.freedesktop.DBus", "NameOwnerChanged"))
759   {
760     handle_name_owner_changed (closure->bus, closure->message, closure->data);
761   }
762 }
763
764 static GQueue *deferred_messages = NULL;
765
766 static gboolean
767 process_deferred_messages (void)
768 {
769   static int in_process_deferred_messages = 0;
770   BusDataClosure *closure;
771
772   if (in_process_deferred_messages)
773     return TRUE;
774   in_process_deferred_messages = 1;
775   while ((closure = g_queue_pop_head (deferred_messages)))
776   {
777     process_deferred_message (closure);
778     dbus_message_unref (closure->message);
779     dbus_connection_unref (closure->bus);
780     g_free (closure);
781   }
782   in_process_deferred_messages = 0;
783   return FALSE;
784 }
785
786 static gboolean
787 process_deferred_messages_callback (gpointer data)
788 {
789   if (process_deferred_messages ())
790     return G_SOURCE_CONTINUE;
791
792   process_deferred_messages_source = NULL;
793   return G_SOURCE_REMOVE;
794 }
795
796 static DBusHandlerResult
797 defer_message (DBusConnection *connection, DBusMessage *message, void *user_data)
798 {
799   BusDataClosure *closure = g_new (BusDataClosure, 1);
800
801   closure->bus = dbus_connection_ref (bus);
802   closure->message = dbus_message_ref (message);
803   closure->data = user_data;
804
805   g_queue_push_tail (deferred_messages, closure);
806
807   if (process_deferred_messages_source == NULL)
808   {
809     process_deferred_messages_source = g_idle_source_new ();
810     g_source_set_callback (process_deferred_messages_source,
811                            process_deferred_messages_callback, NULL, NULL);
812     g_source_attach (process_deferred_messages_source, atspi_main_context);
813     g_source_unref (process_deferred_messages_source);
814   }
815
816   return DBUS_HANDLER_RESULT_HANDLED;
817 }
818
819 static DBusHandlerResult
820 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
821 {
822   int type = dbus_message_get_type (message);
823   const char *interface = dbus_message_get_interface (message);
824
825   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
826       !strncmp (interface, "org.a11y.atspi.Event.", 21))
827   {
828     return defer_message (bus, message, data);
829   }
830   if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "NotifyEvent"))
831   {
832     return defer_message (bus, message, data);
833   }
834   if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
835   {
836     return defer_message (bus, message, data);
837   }
838   if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
839   {
840     return defer_message (bus, message, data);
841   }
842   if (dbus_message_is_signal (message, "org.freedesktop.DBus", "NameOwnerChanged"))
843   {
844     defer_message (bus, message, data);
845     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
846   }
847   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
848 }
849
850 /*
851  * Returns a 'canonicalized' value for DISPLAY,
852  * with the screen number stripped off if present.
853  *
854  * TODO: Avoid having duplicate functions for this here and in at-spi2-atk
855  */
856 static gchar *
857 spi_display_name (void)
858 {
859   char *canonical_display_name = NULL;
860   const gchar *display_env = g_getenv ("AT_SPI_DISPLAY");
861
862   if (!display_env)
863     {
864       display_env = g_getenv ("DISPLAY");
865       if (!display_env || !display_env[0])
866         return NULL;
867       else
868         {
869           gchar *display_p, *screen_p;
870           canonical_display_name = g_strdup (display_env);
871           display_p = g_utf8_strrchr (canonical_display_name, -1, ':');
872           screen_p = g_utf8_strrchr (canonical_display_name, -1, '.');
873           if (screen_p && display_p && (screen_p > display_p))
874             {
875               *screen_p = '\0';
876             }
877         }
878     }
879   else
880     {
881       canonical_display_name = g_strdup (display_env);
882     }
883
884   return canonical_display_name;
885 }
886
887 /**
888  * atspi_init:
889  *
890  * Connects to the accessibility registry and initializes the SPI.
891  *
892  * Returns: 0 on success, 1 if already initialized, or an integer error code.  
893  **/
894 int
895 atspi_init (void)
896 {
897   char *match;
898   const gchar *no_cache;
899
900   if (atspi_inited)
901     {
902       return 1;
903     }
904
905   atspi_inited = TRUE;
906
907   _atspi_get_live_refs();
908
909   bus = atspi_get_a11y_bus ();
910   if (!bus)
911     return 2;
912   dbus_bus_register (bus, NULL);
913   atspi_dbus_connection_setup_with_g_main(bus, g_main_context_default());
914   dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
915   match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
916   dbus_bus_add_match (bus, match, NULL);
917   g_free (match);
918   match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
919   dbus_bus_add_match (bus, match, NULL);
920   g_free (match);
921   match = g_strdup_printf ("type='signal',interface='%s',member='ChildrenChanged'", atspi_interface_event_object);
922   dbus_bus_add_match (bus, match, NULL);
923   g_free (match);
924   match = g_strdup_printf ("type='signal',interface='%s',member='PropertyChange'", atspi_interface_event_object);
925   dbus_bus_add_match (bus, match, NULL);
926   g_free (match);
927   match = g_strdup_printf ("type='signal',interface='%s',member='StateChanged'", atspi_interface_event_object);
928   dbus_bus_add_match (bus, match, NULL);
929   g_free (match);
930
931   dbus_bus_add_match (bus,
932                       "type='signal', interface='org.freedesktop.DBus', member='NameOwnerChanged'",
933                       NULL);
934
935   no_cache = g_getenv ("ATSPI_NO_CACHE");
936   if (no_cache && g_strcmp0 (no_cache, "0") != 0)
937     atspi_no_cache = TRUE;
938
939   deferred_messages = g_queue_new ();
940
941   return 0;
942 }
943
944 /**
945  * atspi_is_initialized:
946  *
947  * Indicates whether AT-SPI has been initialized.
948  *
949  * Returns: %True if initialized; %False otherwise.
950  */
951 gboolean
952 atspi_is_initialized ()
953 {
954   return atspi_inited;
955 }
956
957 /**
958  * atspi_event_main:
959  *
960  * Starts/enters the main event loop for the AT-SPI services.
961  *
962  * NOTE: This method does not return control; it is exited via a call to
963  * #atspi_event_quit from within an event handler.
964  *
965  **/
966 void
967 atspi_event_main (void)
968 {
969   atspi_main_loop = g_main_loop_new (NULL, FALSE);
970   g_main_loop_run (atspi_main_loop);
971   atspi_main_loop = NULL;
972 }
973
974 /**
975  * atspi_event_quit:
976  *
977  * Quits the last main event loop for the AT-SPI services,
978  * See: #atspi_event_main
979  **/
980 void
981 atspi_event_quit (void)
982 {
983   g_main_loop_quit (atspi_main_loop);
984 }
985
986 /**
987  * atspi_exit:
988  *
989  * Disconnects from #AtspiRegistry instances and releases 
990  * any floating resources. Call only once at exit.
991  *
992  * Returns: 0 if there were no leaks, otherwise other integer values.
993  **/
994 int
995 atspi_exit (void)
996 {
997   int leaked;
998
999   if (!atspi_inited)
1000     {
1001       return 0;
1002     }
1003
1004   atspi_inited = FALSE;
1005
1006   if (live_refs)
1007     {
1008       leaked = g_hash_table_size (live_refs);
1009     }
1010   else
1011     {
1012       leaked = 0;
1013     }
1014
1015   cleanup ();
1016
1017   return leaked;
1018 }
1019
1020 static GSList *hung_processes;
1021
1022 static void
1023 remove_hung_process (DBusPendingCall *pending, void *data)
1024 {
1025   hung_processes = g_slist_remove (hung_processes, data);
1026   g_free (data);
1027   dbus_pending_call_unref (pending);
1028 }
1029
1030 static void
1031 check_for_hang (DBusMessage *message, DBusError *error, DBusConnection *bus, const char *bus_name)
1032 {
1033   if (!message && error->name &&
1034       !strcmp (error->name, "org.freedesktop.DBus.Error.NoReply"))
1035   {
1036     GSList *l;
1037     DBusMessage *message;
1038     gchar *bus_name_dup;
1039     DBusPendingCall *pending = NULL;
1040     for (l = hung_processes; l; l = l->next)
1041       if (!strcmp (l->data, bus_name))
1042         return;
1043     message = dbus_message_new_method_call (bus_name, "/",
1044                                             "org.freedesktop.DBus.Peer",
1045                                             "Ping");
1046     if (!message)
1047       return;
1048     dbus_connection_send_with_reply (bus, message, &pending, -1);
1049     dbus_message_unref (message);
1050     if (!pending)
1051       return;
1052     bus_name_dup = g_strdup (bus_name);
1053     hung_processes = g_slist_append (hung_processes, bus_name_dup);
1054     dbus_pending_call_set_notify (pending, remove_hung_process, bus_name_dup, NULL);
1055   }
1056 }
1057
1058 static gboolean
1059 connection_is_hung (const char *bus_name)
1060 {
1061   GSList *l;
1062
1063   for (l = hung_processes; l; l = l->next)
1064     if (!strcmp (l->data, bus_name))
1065       return TRUE;
1066   return FALSE;
1067 }
1068
1069 static gboolean
1070 check_app (AtspiApplication *app, GError **error)
1071 {
1072   if (!app || !app->bus)
1073   {
1074     g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_APPLICATION_GONE,
1075                           _("The application no longer exists"));
1076     return FALSE;
1077   }
1078
1079   if (atspi_main_loop && connection_is_hung (app->bus_name))
1080   {
1081       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC,
1082                            "The process appears to be hung.");
1083     return FALSE;
1084   }
1085
1086   return TRUE;
1087 }
1088
1089 static void
1090 set_timeout (AtspiApplication *app)
1091 {
1092   struct timeval tv;
1093   int diff;
1094
1095   if (app && app_startup_time > 0)
1096   {
1097     gettimeofday (&tv, NULL);
1098     diff = (tv.tv_sec - app->time_added.tv_sec) * 1000 + (tv.tv_usec - app->time_added.tv_usec) / 1000;
1099     dbind_set_timeout (MAX(method_call_timeout, app_startup_time - diff));
1100   }
1101   else
1102     dbind_set_timeout (method_call_timeout);
1103 }
1104
1105 dbus_bool_t
1106 _atspi_dbus_call (gpointer obj, const char *interface, const char *method, GError **error, const char *type, ...)
1107 {
1108   va_list args;
1109   dbus_bool_t retval;
1110   DBusError err;
1111   AtspiObject *aobj = ATSPI_OBJECT (obj);
1112
1113   if (!check_app (aobj->app, error))
1114     return FALSE;
1115
1116   if (!allow_sync)
1117   {
1118     _atspi_set_error_no_sync (error);
1119     return FALSE;
1120   }
1121
1122   va_start (args, type);
1123   dbus_error_init (&err);
1124   set_timeout (aobj->app);
1125   retval = dbind_method_call_reentrant_va (aobj->app->bus, aobj->app->bus_name,
1126                                            aobj->path, interface, method, &err,
1127                                            type, args);
1128   va_end (args);
1129   check_for_hang (NULL, &err, aobj->app->bus, aobj->app->bus_name);
1130   process_deferred_messages ();
1131   if (dbus_error_is_set (&err))
1132   {
1133     g_set_error(error, ATSPI_ERROR, ATSPI_ERROR_IPC, "%s", err.message);
1134     dbus_error_free (&err);
1135   }
1136   return retval;
1137 }
1138
1139 DBusMessage *
1140 _atspi_dbus_call_partial (gpointer obj,
1141                           const char *interface,
1142                           const char *method,
1143                           GError **error,
1144                           const char *type, ...)
1145 {
1146   va_list args;
1147
1148   va_start (args, type);
1149   return _atspi_dbus_call_partial_va (obj, interface, method, error, type, args);
1150 }
1151
1152
1153 DBusMessage *
1154 _atspi_dbus_call_partial_va (gpointer obj,
1155                           const char *interface,
1156                           const char *method,
1157                           GError **error,
1158                           const char *type,
1159                           va_list args)
1160 {
1161   AtspiObject *aobj = ATSPI_OBJECT (obj);
1162   DBusError err;
1163     DBusMessage *msg = NULL, *reply = NULL;
1164     DBusMessageIter iter;
1165     const char *p;
1166
1167   dbus_error_init (&err);
1168
1169   if (!check_app (aobj->app, error))
1170     goto out;
1171
1172   msg = dbus_message_new_method_call (aobj->app->bus_name, aobj->path, interface, method);
1173   if (!msg)
1174     goto out;
1175
1176   p = type;
1177   dbus_message_iter_init_append (msg, &iter);
1178   dbind_any_marshal_va (&iter, &p, args);
1179
1180   set_timeout (aobj->app);
1181   reply = dbind_send_and_allow_reentry (aobj->app->bus, msg, &err);
1182   check_for_hang (reply, &err, aobj->app->bus, aobj->app->bus_name);
1183 out:
1184   va_end (args);
1185   if (msg)
1186     dbus_message_unref (msg);
1187   process_deferred_messages ();
1188   if (dbus_error_is_set (&err))
1189   {
1190     /* TODO: Set gerror */
1191     dbus_error_free (&err);
1192   }
1193
1194   if (reply && dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
1195   {
1196     const char *err_str = NULL;
1197     dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &err_str, DBUS_TYPE_INVALID);
1198     if (err_str)
1199       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC, err_str);
1200     dbus_message_unref (reply);
1201     return NULL;
1202   }
1203
1204   return reply;
1205 }
1206
1207 dbus_bool_t
1208 _atspi_dbus_get_property (gpointer obj, const char *interface, const char *name, GError **error, const char *type, void *data)
1209 {
1210   DBusMessage *message, *reply;
1211   DBusMessageIter iter, iter_variant;
1212   DBusError err;
1213   dbus_bool_t retval = FALSE;
1214   AtspiObject *aobj = ATSPI_OBJECT (obj);
1215   char expected_type = (type [0] == '(' ? 'r' : type [0]);
1216
1217   if (!aobj)
1218     return FALSE;
1219
1220   if (!check_app (aobj->app, error))
1221     return FALSE;
1222
1223   if (!allow_sync)
1224   {
1225     _atspi_set_error_no_sync (error);
1226     return FALSE;
1227   }
1228
1229   message = dbus_message_new_method_call (aobj->app->bus_name,
1230                                           aobj->path,
1231                                           "org.freedesktop.DBus.Properties",
1232                                           "Get");
1233   if (!message)
1234   {
1235     // TODO: throw exception
1236     return FALSE;
1237   }
1238   dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
1239   dbus_error_init (&err);
1240   set_timeout (aobj->app);
1241   reply = dbind_send_and_allow_reentry (aobj->app->bus, message, &err);
1242   check_for_hang (reply, &err, aobj->app->bus, aobj->app->bus_name);
1243   dbus_message_unref (message);
1244   process_deferred_messages ();
1245   if (!reply)
1246   {
1247     // TODO: throw exception
1248     goto done;
1249   }
1250
1251   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
1252   {
1253     const char *err_str = NULL;
1254     dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &err_str, DBUS_TYPE_INVALID);
1255     if (err_str)
1256       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC, err_str);
1257     goto done;
1258   }
1259
1260   dbus_message_iter_init (reply, &iter);
1261   if (dbus_message_iter_get_arg_type (&iter) != 'v')
1262   {
1263     g_warning ("atspi_dbus_get_property: expected a variant when fetching %s from interface %s; got %s\n", name, interface, dbus_message_get_signature (reply));
1264     goto done;
1265   }
1266   dbus_message_iter_recurse (&iter, &iter_variant);
1267   if (dbus_message_iter_get_arg_type (&iter_variant) != expected_type)
1268   {
1269     g_warning ("atspi_dbus_get_property: Wrong type: expected %s, got %c\n", type, dbus_message_iter_get_arg_type (&iter_variant));
1270     goto done;
1271   }
1272   if (!strcmp (type, "(so)"))
1273   {
1274     *((AtspiAccessible **)data) = _atspi_dbus_return_accessible_from_iter (&iter_variant);
1275   }
1276   else
1277   {
1278     dbus_message_iter_get_basic (&iter_variant, data);
1279     if (type [0] == 's')
1280       *(char **)data = g_strdup (*(char **)data);
1281   }
1282   retval = TRUE;
1283 done:
1284   dbus_error_free (&err);
1285   if (reply)
1286     dbus_message_unref (reply);
1287   return retval;
1288 }
1289
1290 DBusMessage *
1291 _atspi_dbus_send_with_reply_and_block (DBusMessage *message, GError **error)
1292 {
1293   DBusMessage *reply;
1294   DBusError err;
1295   AtspiApplication *app;
1296   DBusConnection *bus;
1297
1298   app = get_application (dbus_message_get_destination (message));
1299
1300   if (app && !app->bus)
1301     return NULL;        /* will fail anyway; app has been disposed */
1302
1303   bus = (app ? app->bus : _atspi_bus());
1304   dbus_error_init (&err);
1305   set_timeout (app);
1306   reply = dbind_send_and_allow_reentry (bus, message, &err);
1307   process_deferred_messages ();
1308   dbus_message_unref (message);
1309   if (dbus_error_is_set (&err))
1310   {
1311     if (error)
1312       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC, err.message);
1313     dbus_error_free (&err);
1314   }
1315   return reply;
1316 }
1317
1318 GHashTable *
1319 _atspi_dbus_return_hash_from_message (DBusMessage *message)
1320 {
1321   DBusMessageIter iter;
1322   GHashTable *ret;
1323
1324   if (!message)
1325     return NULL;
1326
1327   _ATSPI_DBUS_CHECK_SIG (message, "a{ss}", NULL, NULL);
1328
1329   dbus_message_iter_init (message, &iter);
1330   ret = _atspi_dbus_hash_from_iter (&iter);
1331   dbus_message_unref (message);
1332   return ret;
1333 }
1334
1335 GHashTable *
1336 _atspi_dbus_hash_from_iter (DBusMessageIter *iter)
1337 {
1338   GHashTable *hash = g_hash_table_new_full (g_str_hash, g_str_equal,
1339                                             (GDestroyNotify) g_free,
1340                                             (GDestroyNotify) g_free);
1341   DBusMessageIter iter_array, iter_dict;
1342
1343   dbus_message_iter_recurse (iter, &iter_array);
1344   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1345   {
1346     const char *name, *value;
1347     dbus_message_iter_recurse (&iter_array, &iter_dict);
1348     dbus_message_iter_get_basic (&iter_dict, &name);
1349     dbus_message_iter_next (&iter_dict);
1350     dbus_message_iter_get_basic (&iter_dict, &value);
1351     g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
1352     dbus_message_iter_next (&iter_array);
1353   }
1354   return hash;
1355 }
1356
1357 GArray *
1358 _atspi_dbus_return_attribute_array_from_message (DBusMessage *message)
1359 {
1360   DBusMessageIter iter;
1361   GArray *ret;
1362
1363   if (!message)
1364     return NULL;
1365
1366   _ATSPI_DBUS_CHECK_SIG (message, "a{ss}", NULL, NULL);
1367
1368   dbus_message_iter_init (message, &iter);
1369
1370   ret = _atspi_dbus_attribute_array_from_iter (&iter);
1371   dbus_message_unref (message);
1372   return ret;
1373 }
1374
1375 GArray *
1376 _atspi_dbus_attribute_array_from_iter (DBusMessageIter *iter)
1377 {
1378   DBusMessageIter iter_array, iter_dict;
1379   GArray *array = g_array_new (TRUE, TRUE, sizeof (gchar *));
1380
1381   dbus_message_iter_recurse (iter, &iter_array);
1382   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1383   {
1384     const char *name, *value;
1385     gchar *str;
1386     dbus_message_iter_recurse (&iter_array, &iter_dict);
1387     dbus_message_iter_get_basic (&iter_dict, &name);
1388     dbus_message_iter_next (&iter_dict);
1389     dbus_message_iter_get_basic (&iter_dict, &value);
1390     str = g_strdup_printf ("%s:%s", name, value);
1391     array = g_array_append_val (array, str);
1392     dbus_message_iter_next (&iter_array);;
1393   }
1394   return array;
1395 }
1396
1397 void
1398 _atspi_dbus_set_interfaces (AtspiAccessible *accessible, DBusMessageIter *iter)
1399 {
1400   DBusMessageIter iter_array;
1401   char *iter_sig = dbus_message_iter_get_signature (iter);
1402
1403   accessible->interfaces = 0;
1404   if (strcmp (iter_sig, "as") != 0)
1405   {
1406     g_warning ("_atspi_dbus_set_interfaces: Passed iterator with invalid signature %s", dbus_message_iter_get_signature (iter));
1407     dbus_free (iter_sig);
1408     return;
1409   }
1410   dbus_free (iter_sig);
1411   dbus_message_iter_recurse (iter, &iter_array);
1412   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1413   {
1414     const char *iface;
1415     gint n;
1416     dbus_message_iter_get_basic (&iter_array, &iface);
1417     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
1418     n = _atspi_get_iface_num (iface);
1419     if (n == -1)
1420     {
1421       g_warning ("AT-SPI: Unknown interface %s", iface);
1422     }
1423     else
1424       accessible->interfaces |= (1 << n);
1425     dbus_message_iter_next (&iter_array);
1426   }
1427   _atspi_accessible_add_cache (accessible, ATSPI_CACHE_INTERFACES);
1428 }
1429
1430 void
1431 _atspi_dbus_set_state (AtspiAccessible *accessible, DBusMessageIter *iter)
1432 {
1433   DBusMessageIter iter_array;
1434   gint count;
1435   dbus_uint32_t *states;
1436
1437   dbus_message_iter_recurse (iter, &iter_array);
1438   dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
1439   if (count != 2)
1440   {
1441     g_warning ("AT-SPI: expected 2 values in states array; got %d\n", count);
1442     if (!accessible->states)
1443       accessible->states = _atspi_state_set_new_internal (accessible, 0);
1444   }
1445   else
1446   {
1447     guint64 val = ((guint64)states [1]) << 32;
1448     val += states [0];
1449     if (!accessible->states)
1450       accessible->states = _atspi_state_set_new_internal (accessible, val);
1451     else
1452       accessible->states->states = val;
1453   }
1454   _atspi_accessible_add_cache (accessible, ATSPI_CACHE_STATES);
1455 }
1456
1457 GQuark
1458 _atspi_error_quark (void)
1459 {
1460   return g_quark_from_static_string ("atspi_error");
1461 }
1462
1463 /*
1464  * Gets the IOR from the XDisplay.
1465  */
1466 #ifdef HAVE_X11
1467 static char *
1468 get_accessibility_bus_address_x11 (void)
1469 {
1470   Atom AT_SPI_BUS;
1471   Atom actual_type;
1472   Display *bridge_display = NULL;
1473   int actual_format;
1474   char *data;
1475   unsigned char *data_x11 = NULL;
1476   unsigned long nitems;
1477   unsigned long leftover;
1478   char *display_name;
1479
1480   display_name = spi_display_name ();
1481   if (!display_name)
1482     return NULL;
1483
1484   bridge_display = XOpenDisplay (display_name);
1485   g_free (display_name);
1486
1487   if (!bridge_display)
1488     {
1489       g_warning ("Could not open X display");
1490       return NULL;
1491     }
1492       
1493   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
1494   XGetWindowProperty (bridge_display,
1495                       XDefaultRootWindow (bridge_display),
1496                       AT_SPI_BUS, 0L,
1497                       (long) BUFSIZ, False,
1498                       (Atom) 31, &actual_type, &actual_format,
1499                       &nitems, &leftover, &data_x11);
1500   XCloseDisplay (bridge_display);
1501
1502   data = g_strdup ((gchar *)data_x11);
1503   XFree (data_x11);
1504   return data;
1505 }
1506 #endif
1507
1508 static char *
1509 get_accessibility_bus_address_dbus (void)
1510 {
1511   DBusConnection *session_bus = NULL;
1512   DBusMessage *message;
1513   DBusMessage *reply;
1514   DBusError error;
1515   char *address = NULL;
1516
1517   session_bus = dbus_bus_get (DBUS_BUS_SESSION, NULL);
1518   if (!session_bus)
1519     return NULL;
1520
1521   message = dbus_message_new_method_call ("org.a11y.Bus",
1522                                           "/org/a11y/bus",
1523                                           "org.a11y.Bus",
1524                                           "GetAddress");
1525
1526   dbus_error_init (&error);
1527   reply = dbus_connection_send_with_reply_and_block (session_bus,
1528                                                      message,
1529                                                      -1,
1530                                                      &error);
1531   dbus_message_unref (message);
1532
1533   if (!reply)
1534   {
1535     g_warning ("AT-SPI: Error retrieving accessibility bus address: %s: %s",
1536                error.name, error.message);
1537     dbus_error_free (&error);
1538     goto out;
1539   }
1540   
1541   {
1542     const char *tmp_address;
1543     if (!dbus_message_get_args (reply,
1544                                 NULL,
1545                                 DBUS_TYPE_STRING,
1546                                 &tmp_address,
1547                                 DBUS_TYPE_INVALID))
1548       {
1549         dbus_message_unref (reply);
1550         goto out;
1551       }
1552     address = g_strdup (tmp_address);
1553     dbus_message_unref (reply);
1554   }
1555   
1556 out:
1557   dbus_connection_unref (session_bus);
1558   return address;
1559 }
1560
1561 static DBusConnection *a11y_bus;
1562 static dbus_int32_t a11y_dbus_slot = -1;
1563
1564 static void
1565 a11y_bus_free (void *data)
1566 {
1567   if (data == a11y_bus)
1568     {
1569       a11y_bus = NULL;
1570       dbus_connection_free_data_slot (&a11y_dbus_slot);
1571     }
1572 }
1573
1574 /**
1575  * atspi_get_a11y_bus: (skip)
1576  */
1577 DBusConnection *
1578 atspi_get_a11y_bus (void)
1579 {
1580   DBusError error;
1581   char *address = NULL;
1582   const char *address_env = NULL;
1583
1584   if (a11y_bus && dbus_connection_get_is_connected (a11y_bus))
1585     return a11y_bus;
1586
1587   if (a11y_dbus_slot == -1)
1588     if (!dbus_connection_allocate_data_slot (&a11y_dbus_slot))
1589       g_warning ("AT-SPI: Unable to allocate D-Bus slot");
1590
1591   address_env = g_getenv ("AT_SPI_BUS_ADDRESS");
1592   if (address_env != NULL && *address_env != 0)
1593     address = g_strdup (address_env);
1594 #ifdef HAVE_X11
1595   if (!address && g_getenv ("DISPLAY") != NULL &&
1596       g_getenv ("WAYLAND_DISPLAY") == NULL)
1597     address = get_accessibility_bus_address_x11 ();
1598 #endif
1599   if (!address)
1600     address = get_accessibility_bus_address_dbus ();
1601   if (!address)
1602     return NULL;
1603
1604   dbus_error_init (&error);
1605   a11y_bus = dbus_connection_open_private (address, &error);
1606   g_free (address);
1607
1608   if (!a11y_bus)
1609     {
1610       if (!g_getenv("SSH_CONNECTION"))
1611         g_warning ("Couldn't connect to accessibility bus: %s", error.message);
1612       dbus_error_free (&error);
1613       return NULL;
1614     }
1615   else
1616     {
1617       if (!dbus_bus_register (a11y_bus, &error))
1618         {
1619           g_warning ("Couldn't register with accessibility bus: %s", error.message);
1620           dbus_error_free (&error);
1621           dbus_connection_close (a11y_bus);
1622           dbus_connection_unref (a11y_bus);
1623           a11y_bus = NULL;
1624           return NULL;
1625         }
1626     }
1627   
1628   /* Simulate a weak ref on the bus */
1629   dbus_connection_set_data (a11y_bus, a11y_dbus_slot, a11y_bus, a11y_bus_free);
1630
1631   return a11y_bus;
1632 }
1633
1634 /**
1635  * atspi_set_timeout:
1636  * @val: The timeout value, in milliseconds, or -1 to disable the timeout.
1637  * @startup_time: The amount of time, in milliseconds, to allow to pass
1638  * before enforcing timeouts on an application. Can be used to prevent
1639  * timeout exceptions if an application is likely to block for an extended
1640  * period of time on initialization. -1 can be passed to disable this
1641  * behavior.
1642  *
1643  * Set the timeout used for method calls. If this is not set explicitly,
1644  * a default of 0.8 ms is used.
1645  * Note that at-spi2-registryd currently uses a timeout of 3 seconds when
1646  * sending a keyboard event notification. This means that, if an AT makes
1647  * a call in response to the keyboard notification and the application
1648  * being called does not respond before the timeout is reached,
1649  * at-spi2-registryd will time out on the keyboard event notification and
1650  * pass the key onto the application (ie, reply to indicate that the key
1651  * was not consumed), so this may make it undesirable to set a timeout
1652  * larger than 3 seconds.
1653  *
1654  * By default, the normal timeout is set to 800 ms, and the application startup
1655  * timeout is set to 15 seconds.
1656  */
1657 void
1658 atspi_set_timeout (gint val, gint startup_time)
1659 {
1660   method_call_timeout = val;
1661   app_startup_time = startup_time;
1662 }
1663
1664 /**
1665  * atspi_set_main_context:
1666  * @cnx: The #GMainContext to use.
1667  *
1668  * Sets the main loop context that AT-SPI should assume is in use when
1669  * setting an idle callback.
1670  * This function should be called by application-side implementors (ie,
1671  * at-spi2-atk) when it is desirable to re-enter the main loop.
1672  */
1673 void
1674 atspi_set_main_context (GMainContext *cnx)
1675 {
1676   if (atspi_main_context == cnx)
1677     return;
1678   if (process_deferred_messages_source != NULL)
1679   {
1680     g_source_destroy (process_deferred_messages_source);
1681     process_deferred_messages_source = g_idle_source_new ();
1682     g_source_set_callback (process_deferred_messages_source,
1683                            process_deferred_messages_callback, NULL, NULL);
1684     g_source_attach (process_deferred_messages_source, cnx);
1685     g_source_unref (process_deferred_messages_source);
1686   }
1687   atspi_main_context = cnx;
1688   atspi_dbus_connection_setup_with_g_main (atspi_get_a11y_bus (), cnx);
1689
1690   if (desktop)
1691   {
1692     gint i;
1693     for (i = desktop->children->len - 1; i >= 0; i--)
1694     {
1695       AtspiAccessible *child = g_ptr_array_index (desktop->children, i);
1696       if (child->parent.app && child->parent.app->bus)
1697         atspi_dbus_connection_setup_with_g_main (child->parent.app->bus, cnx);
1698     }
1699   }
1700 }
1701
1702 #ifdef DEBUG_REF_COUNTS
1703 static void
1704 print_disposed (gpointer key, gpointer value, gpointer data)
1705 {
1706   AtspiAccessible *accessible = key;
1707   if (accessible->parent.app)
1708     return;
1709   g_print ("disposed: %s %d\n", accessible->name, accessible->role);
1710 }
1711
1712 void
1713 debug_disposed ()
1714 {
1715   g_hash_table_foreach (live_refs, print_disposed, NULL);
1716 }
1717 #endif
1718
1719 gchar *
1720 _atspi_name_compat (gchar *name)
1721 {
1722   gchar *p = name;
1723
1724   while (*p)
1725   {
1726     if (*p == '-')
1727       *p = ' ';
1728     p++;
1729   }
1730   return name;
1731 }
1732
1733 /**
1734  * atspi_role_get_name:
1735  * @role: an #AtspiRole object to query.
1736  *
1737  * Gets a localizable string that indicates the name of an #AtspiRole.
1738  * <em>DEPRECATED.</em>
1739  *
1740  * Returns: a localizable string name for an #AtspiRole enumerated type.
1741  **/
1742 gchar *
1743 atspi_role_get_name (AtspiRole role)
1744 {
1745   gchar *retval = NULL;
1746   GTypeClass *type_class;
1747   GEnumValue *value;
1748
1749   type_class = g_type_class_ref (ATSPI_TYPE_ROLE);
1750   g_return_val_if_fail (G_IS_ENUM_CLASS (type_class), NULL);
1751
1752   value = g_enum_get_value (G_ENUM_CLASS (type_class), role);
1753
1754   if (value)
1755     {
1756       retval = g_strdup (value->value_nick);
1757     }
1758
1759   g_type_class_unref (type_class);
1760
1761   if (retval)
1762     return _atspi_name_compat (retval);
1763
1764   return NULL;
1765 }
1766
1767 GHashTable *
1768 _atspi_dbus_update_cache_from_dict (AtspiAccessible *accessible, DBusMessageIter *iter)
1769 {
1770   GHashTable *cache = _atspi_accessible_ref_cache (accessible);
1771   DBusMessageIter iter_dict, iter_dict_entry, iter_struct, iter_variant;
1772
1773   dbus_message_iter_recurse (iter, &iter_dict);
1774   while (dbus_message_iter_get_arg_type (&iter_dict) != DBUS_TYPE_INVALID)
1775   {
1776     const char *key;
1777     GValue *val = NULL;
1778     dbus_message_iter_recurse (&iter_dict, &iter_dict_entry);
1779     dbus_message_iter_get_basic (&iter_dict_entry, &key);
1780     dbus_message_iter_next (&iter_dict_entry);
1781     dbus_message_iter_recurse (&iter_dict_entry, &iter_variant);
1782     if (!strcmp (key, "interfaces"))
1783     {
1784       _atspi_dbus_set_interfaces (accessible, &iter_variant);
1785     }
1786     else if (!strcmp (key, "Attributes"))
1787     {
1788       char *iter_sig = dbus_message_iter_get_signature (&iter_variant);
1789       val = g_new0 (GValue, 1);;
1790       g_value_init (val, G_TYPE_HASH_TABLE);
1791       if (strcmp (iter_sig, "a{ss}") != 0)
1792       {
1793         dbus_free (iter_sig);
1794         break;
1795       }
1796       dbus_free (iter_sig);
1797       g_value_take_boxed (val, _atspi_dbus_hash_from_iter (&iter_variant));
1798     }
1799     else if (!strcmp (key, "Component.ScreenExtents"))
1800     {
1801       dbus_int32_t d_int;
1802       AtspiRect extents;
1803       char *iter_sig = dbus_message_iter_get_signature (&iter_variant);
1804       val = g_new0 (GValue, 1);;
1805       g_value_init (val, ATSPI_TYPE_RECT);
1806       if (strcmp (iter_sig, "(iiii)") != 0)
1807       {
1808         dbus_free (iter_sig);
1809         break;
1810       }
1811       dbus_free (iter_sig);
1812       dbus_message_iter_recurse (&iter_variant, &iter_struct);
1813       dbus_message_iter_get_basic (&iter_struct, &d_int);
1814       extents.x = d_int;
1815       dbus_message_iter_next (&iter_struct);
1816       dbus_message_iter_get_basic (&iter_struct, &d_int);
1817       extents.y = d_int;
1818       dbus_message_iter_next (&iter_struct);
1819       dbus_message_iter_get_basic (&iter_struct, &d_int);
1820       extents.width = d_int;
1821       dbus_message_iter_next (&iter_struct);
1822       dbus_message_iter_get_basic (&iter_struct, &d_int);
1823       extents.height = d_int;
1824       g_value_set_boxed (val, &extents);
1825     }
1826     if (val)
1827       g_hash_table_insert (cache, g_strdup (key), val); 
1828     dbus_message_iter_next (&iter_dict);
1829   }
1830
1831   return cache;
1832 }
1833
1834 gboolean
1835 _atspi_get_allow_sync ()
1836 {
1837   return allow_sync;
1838 }
1839
1840 gboolean
1841 _atspi_set_allow_sync (gboolean val)
1842 {
1843   gboolean ret = allow_sync;
1844
1845   allow_sync = val;
1846   return ret;
1847 }
1848
1849 void
1850 _atspi_set_error_no_sync (GError **error)
1851 {
1852   g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_SYNC_NOT_ALLOWED,
1853                         _("Attempted synchronous call where prohibited"));
1854 }
1855
1856 static const char *sr_introspection = "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
1857 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
1858 "<node name=\"/org/a11y/atspi/screenreader\">\n"
1859 "  <interface name=\"org.a11y.Atspi.ScreenReader\">\n"
1860 "    <signal name=\"ReadingPosition\">\n"
1861 "      <arg type=\"i\"/>\n"
1862 "      <arg type=\"i\"/>\n"
1863 "    </signal>\n"
1864 "  </interface>\n"
1865 "</node>";
1866
1867 static DBusHandlerResult
1868 screen_reader_filter (DBusConnection *bus, DBusMessage *message, void *user_data)
1869 {
1870   if (dbus_message_is_method_call (message, DBUS_INTERFACE_INTROSPECTABLE,
1871       "Introspect"))
1872   {
1873     DBusMessage *reply = dbus_message_new_method_return (message);
1874     dbus_message_append_args (reply, DBUS_TYPE_STRING, &sr_introspection,
1875                               DBUS_TYPE_INVALID);
1876     dbus_connection_send (bus, reply, NULL);
1877     dbus_message_unref (reply);
1878     return DBUS_HANDLER_RESULT_HANDLED;
1879   }
1880   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
1881 }
1882
1883 gboolean
1884 _atspi_prepare_screen_reader_interface ()
1885 {
1886   static gint initialized = 0;
1887   DBusConnection *a11y_bus = _atspi_bus ();
1888
1889   if (initialized)
1890     return (initialized > 0);
1891
1892   if (dbus_bus_request_name (a11y_bus, "org.a11y.Atspi.ScreenReader", 0, NULL) < 0)
1893   {
1894     initialized = -1;
1895     return FALSE;
1896   }
1897
1898   initialized = 1;
1899   dbus_connection_add_filter (a11y_bus, screen_reader_filter, NULL, NULL);
1900   return TRUE;
1901 }