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