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