Some work on event support; many bug fixes
[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  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 /*
25  *
26  * Basic SPI initialization and event loop function prototypes
27  *
28  */
29
30 #include "atspi-private.h"
31 #include "X11/Xlib.h"
32 #include <stdio.h>
33
34 static DBusConnection *bus = NULL;
35 static GHashTable *apps = NULL;
36 static GHashTable *live_refs = NULL;
37 static GQueue *exception_handlers = NULL;
38 static DBusError exception;
39
40 const char *atspi_path_dec = ATSPI_DBUS_PATH_DEC;
41 const char *atspi_path_registry = ATSPI_DBUS_PATH_REGISTRY;
42 const char *atspi_path_root = ATSPI_DBUS_PATH_ROOT;
43 const char *atspi_bus_registry = ATSPI_DBUS_NAME_REGISTRY;
44 const char *atspi_interface_accessible = ATSPI_DBUS_INTERFACE_ACCESSIBLE;
45 const char *atspi_interface_action = ATSPI_DBUS_INTERFACE_ACTION;
46 const char *atspi_interface_application = ATSPI_DBUS_INTERFACE_APPLICATION;
47 const char *atspi_interface_component = ATSPI_DBUS_INTERFACE_COMPONENT;
48 const char *atspi_interface_dec = ATSPI_DBUS_INTERFACE_DEC;
49 const char *atspi_interface_device_event_listener = ATSPI_DBUS_INTERFACE_DEVICE_EVENT_LISTENER;
50 const char *atspi_interface_document = ATSPI_DBUS_INTERFACE_DOCUMENT;
51 const char *atspi_interface_editable_text = ATSPI_DBUS_INTERFACE_EDITABLE_TEXT;
52 const char *atspi_interface_event_object = ATSPI_DBUS_INTERFACE_EVENT_OBJECT;
53 const char *atspi_interface_hyperlink = ATSPI_DBUS_INTERFACE_HYPERLINK;
54 const char *atspi_interface_hypertext = ATSPI_DBUS_INTERFACE_HYPERTEXT;
55 const char *atspi_interface_image = ATSPI_DBUS_INTERFACE_IMAGE;
56 const char *atspi_interface_registry = ATSPI_DBUS_INTERFACE_REGISTRY;
57 const char *atspi_interface_selection = ATSPI_DBUS_INTERFACE_SELECTION;
58 const char *atspi_interface_table = ATSPI_DBUS_INTERFACE_TABLE;
59 const char *atspi_interface_text = ATSPI_DBUS_INTERFACE_TEXT;
60 const char *atspi_interface_cache = ATSPI_DBUS_INTERFACE_CACHE;
61 const char *atspi_interface_value = ATSPI_DBUS_INTERFACE_VALUE;
62
63 static const char *interfaces[] =
64 {
65   ATSPI_DBUS_INTERFACE_ACCESSIBLE,
66   ATSPI_DBUS_INTERFACE_ACTION,
67   ATSPI_DBUS_INTERFACE_APPLICATION,
68   ATSPI_DBUS_INTERFACE_COLLECTION,
69   ATSPI_DBUS_INTERFACE_COMPONENT,
70   ATSPI_DBUS_INTERFACE_DOCUMENT,
71   ATSPI_DBUS_INTERFACE_EDITABLE_TEXT,
72   ATSPI_DBUS_INTERFACE_HYPERLINK,
73   ATSPI_DBUS_INTERFACE_HYPERTEXT,
74   ATSPI_DBUS_INTERFACE_IMAGE,
75   "org.a11y.atspi.LoginHelper",
76   ATSPI_DBUS_INTERFACE_SELECTION,
77   ATSPI_DBUS_INTERFACE_TABLE,
78   ATSPI_DBUS_INTERFACE_TEXT,
79   ATSPI_DBUS_INTERFACE_VALUE,
80   NULL
81 };
82
83 gint
84 _atspi_get_iface_num (const char *iface)
85 {
86   /* TODO: Use a binary search or hash to improve performance */
87   int i;
88
89   for (i = 0; interfaces[i]; i++)
90   {
91     if (!strcmp(iface, interfaces[i])) return i;
92   }
93   return -1;
94 }
95
96 static GHashTable *
97 get_live_refs (void)
98 {
99   if (!live_refs) 
100     {
101       live_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
102     }
103   return live_refs;
104 }
105
106 /* TODO: Add an application parameter */
107 DBusConnection *
108 _atspi_bus ()
109 {
110   if (!bus)
111     atspi_init ();
112   return bus;
113 }
114
115 #define APP_IS_REGISTRY(app) (!strcmp (app->bus_name, atspi_bus_registry))
116
117 static void
118 cleanup ()
119 {
120   GHashTable *refs;
121
122   refs = live_refs;
123   live_refs = NULL;
124   if (refs)
125     {
126       g_hash_table_destroy (refs);
127     }
128 }
129
130 static gboolean atspi_inited = FALSE;
131
132 static GHashTable *app_hash = NULL;
133
134 static AtspiApplication *
135 get_application (const char *bus_name)
136 {
137   AtspiApplication *app = NULL;
138   char *bus_name_dup;
139
140   if (!app_hash)
141   {
142     app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_unref);
143     if (!app_hash) return NULL;
144   }
145   app = g_hash_table_lookup (app_hash, bus_name);
146   if (app) return app;
147   bus_name_dup = g_strdup (bus_name);
148   if (!bus_name_dup) return NULL;
149   // TODO: change below to something that will send state-change:defunct notification if necessary */
150   app = _atspi_application_new (bus_name);
151   if (!app) return NULL;
152   app->bus_name = bus_name_dup;
153   if (APP_IS_REGISTRY (app))
154   {
155     app->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
156   }
157   else
158   {
159     app->hash = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_object_unref);
160   }
161   g_hash_table_insert (app_hash, bus_name_dup, app);
162   return app;
163 }
164
165 static AtspiAccessible *
166 ref_accessible (const char *app_name, const char *path)
167 {
168   AtspiApplication *app = get_application (app_name);
169   AtspiAccessible *a;
170
171   if (!strcmp (path, "/org/a11y/atspi/accessible/root"))
172   {
173     if (!app->root)
174     {
175       app->root = atspi_accessible_new (app, atspi_path_root);
176       app->root->accessible_parent = atspi_get_desktop (0);
177     }
178     return g_object_ref (app->root);
179   }
180
181   a = g_hash_table_lookup (app->hash, path);
182   if (a)
183   {
184     g_object_ref (a);
185     return a;
186   }
187   a = atspi_accessible_new (app, path);
188   if (!a)
189     return NULL;
190   g_hash_table_insert (app->hash, a->path, a);
191   g_object_ref (a);     /* for the hash */
192   return a;
193 }
194
195 typedef struct
196 {
197   char *path;
198   char *parent;
199   GArray *children;
200   GArray *interfaces;
201   char *name;
202   dbus_uint32_t role;
203   char *description;
204   GArray *state_bitflags;
205 } CACHE_ADDITION;
206
207 static DBusHandlerResult
208 handle_remove_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
209 {
210   const char *sender = dbus_message_get_sender (message);
211   AtspiApplication *app;
212   const char *path;
213   DBusMessageIter iter, iter_struct;
214   const char *signature = dbus_message_get_signature (message);
215   AtspiAccessible *a;
216   int id;
217
218   if (strcmp (signature, "(so)") != 0)
219   {
220     g_warning ("at-spi: Unknown signature %s for RemoveAccessible", signature);
221     return DBUS_HANDLER_RESULT_HANDLED;
222   }
223
224   dbus_message_iter_init (message, &iter);
225   dbus_message_iter_recurse (&iter, &iter_struct);
226   dbus_message_iter_get_basic (&iter_struct, &sender);
227   dbus_message_iter_get_basic (&iter_struct, &path);
228   app = get_application (sender);
229   a = ref_accessible (sender, path);
230   if (a->accessible_parent && g_list_find (a->accessible_parent->children, a))
231   {
232     a->accessible_parent->children = g_list_remove (a->accessible_parent->children, a);
233     g_object_unref (a);
234   }
235   g_hash_table_remove (app->hash, app->bus_name);
236   g_object_unref (a);   /* unref our own ref */
237   return DBUS_HANDLER_RESULT_HANDLED;
238 }
239
240 static gboolean
241 add_app_to_desktop (AtspiAccessible *a, const char *bus_name)
242 {
243   DBusError error;
244   char *root_path;
245
246   dbus_error_init (&error);
247   AtspiAccessible *obj = ref_accessible (bus_name, atspi_path_root);
248   if (obj)
249   {
250     GList *new_list = g_list_append (a->children, obj);
251     if (new_list)
252     {
253       a->children = new_list;
254       return TRUE;
255     }
256   }
257   else
258   {
259     g_warning ("Error calling getRoot for %s: %s", bus_name, error.message);
260   }
261   return FALSE;
262 }
263
264 static void
265 send_children_changed (AtspiAccessible *parent, AtspiAccessible *child, gboolean add)
266 {
267   AtspiEvent e;
268
269   memset (&e, 0, sizeof(e));
270   e.type = (add? "object:children-changed:add": "object:children-changed:remove");
271   e.source = parent;
272   e.detail1 = g_list_index (parent->children, child);
273   _atspi_send_event (&e);
274 }
275
276 static void
277 unref_object_and_descendants (AtspiAccessible *obj)
278 {
279   GList *l;
280
281   for (l = obj->children; l; l = l->next)
282   {
283     unref_object_and_descendants (l->data);
284   }
285   g_object_unref (obj);
286 }
287
288 static gboolean
289 remove_app_from_desktop (AtspiAccessible *a, const char *bus_name)
290 {
291   GList *l;
292   AtspiAccessible *child;
293
294   for (l = a->children; l; l = l->next)
295   {
296     child = l->data;
297     if (!strcmp (bus_name, child->app->bus_name)) break;
298   }
299   if (!l)
300   {
301     g_warning ("Removing unregistered app %s; doing nothing\n", bus_name);
302     return FALSE;
303   }
304   send_children_changed (a, child, FALSE);
305   a->children = g_list_remove (a->children, child);
306   unref_object_and_descendants (child);
307   return TRUE;
308 }
309
310 static AtspiAccessible *desktop;
311
312 void
313 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
314 {
315   DBusMessageIter iter_struct;
316
317   dbus_message_iter_recurse (iter, &iter_struct);
318   dbus_message_iter_get_basic (&iter_struct, app_name);
319   dbus_message_iter_next (&iter_struct);
320   dbus_message_iter_get_basic (&iter_struct, path);
321   dbus_message_iter_next (iter);
322 }
323
324 static void
325 add_accessible_from_iter (DBusMessageIter *iter)
326 {
327   gint i;
328   GList *new_list;
329   DBusMessageIter iter_struct, iter_array;
330   const char *app_name, *path;
331   AtspiApplication *app;
332   AtspiAccessible *accessible;
333   const char *name, *description;
334   dbus_uint32_t role;
335   dbus_uint32_t *states;
336   int count;
337
338   dbus_message_iter_recurse (iter, &iter_struct);
339
340   /* get accessible */
341   get_reference_from_iter (&iter_struct, &app_name, &path);
342   accessible = ref_accessible (app_name, path);
343
344   /* Get application: TODO */
345   dbus_message_iter_next (&iter_struct);
346
347   /* get parent */
348   get_reference_from_iter (&iter_struct, &app_name, &path);
349   accessible->accessible_parent = ref_accessible (app_name, path);
350
351   /* Get children */
352   dbus_message_iter_recurse (&iter_struct, &iter_array);
353   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
354   {
355     AtspiAccessible *child;
356     get_reference_from_iter (&iter_array, &app_name, &path);
357     child = ref_accessible (app_name, path);
358     new_list = g_list_append (accessible->children, child);
359     if (new_list) accessible->children = new_list;
360   }
361
362   /* interfaces */
363   accessible->interfaces = 0;
364   dbus_message_iter_next (&iter_struct);
365   dbus_message_iter_recurse (&iter_struct, &iter_array);
366   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
367   {
368     const char *iface;
369     gint n;
370     dbus_message_iter_get_basic (&iter_array, &iface);
371     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
372     n = _atspi_get_iface_num (iface);
373     if (n == -1)
374     {
375       g_warning ("Unknown interface %s", iface);
376     }
377     else accessible->interfaces |= (1 << n);
378     dbus_message_iter_next (&iter_array);
379   }
380   dbus_message_iter_next (&iter_struct);
381
382   /* name */
383   dbus_message_iter_get_basic (&iter_struct, &name);
384   accessible->name = g_strdup (name);
385   dbus_message_iter_next (&iter_struct);
386
387   /* role */
388   dbus_message_iter_get_basic (&iter_struct, &role);
389   accessible->role = role;
390   dbus_message_iter_next (&iter_struct);
391
392   /* description */
393   dbus_message_iter_get_basic (&iter_struct, &description);
394   accessible->description = g_strdup (description);
395   dbus_message_iter_next (&iter_struct);
396
397   dbus_message_iter_recurse (&iter_struct, &iter_array);
398   dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
399   if (count != 2)
400   {
401     g_warning ("at-spi: expected 2 values in states array; got %d\n", count);
402     accessible->states = atspi_state_set_new (accessible, 0);
403   }
404   else
405   {
406     guint64 val = ((guint64)states [1]) << 32;
407     val += states [0];
408     accessible->states = atspi_state_set_new (accessible, val);
409   }
410   dbus_message_iter_next (&iter_struct);
411
412   /* This is a bit of a hack since the cache holds a ref, so we don't need
413    * the one provided for us anymore */
414   g_object_unref (accessible);
415 }
416
417 static void
418 add_accessibles (const char *app_name)
419 {
420   DBusError error;
421   DBusMessage *message, *reply;
422   DBusMessageIter iter, iter_array;
423
424   AtspiApplication *app = get_application (app_name);
425   /* TODO: Move this functionality into app initializer? */
426   dbus_error_init (&error);
427   message = dbus_message_new_method_call (app_name, "/org/a11y/atspi/cache", atspi_interface_cache, "GetItems");
428   reply = _atspi_dbus_send_with_reply_and_block (message);
429   if (!reply || strcmp (dbus_message_get_signature (reply), "a((so)(so)(so)a(so)assusau)") != 0)
430   {
431     g_warning ("at-spi: Error in GetItems");
432     return;
433     if (reply)
434       dbus_message_unref (reply);
435   }
436   dbus_message_iter_init (reply, &iter);
437   dbus_message_iter_recurse (&iter, &iter_array);
438   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
439   {
440     add_accessible_from_iter (&iter_array);
441     dbus_message_iter_next (&iter_array);
442   }
443   dbus_message_unref (reply);
444 }
445
446 /* TODO: Do we stil need this function? */
447 static AtspiAccessible *
448 ref_accessible_desktop (AtspiApplication *app)
449 {
450   DBusError error;
451   GArray *apps = NULL;
452   GArray *additions;
453   gint i;
454   DBusMessage *message, *reply;
455   DBusMessageIter iter, iter_array;
456
457   if (desktop)
458   {
459     g_object_ref (desktop);
460     return desktop;
461   }
462   desktop = atspi_accessible_new (app, atspi_path_root);
463   if (!desktop)
464   {
465     return NULL;
466   }
467   g_hash_table_insert (app->hash, desktop->path, desktop);
468   g_object_ref (desktop);       /* for the hash */
469   desktop->name = g_strdup ("main");
470   dbus_error_init (&error);
471   message = dbus_message_new_method_call (atspi_bus_registry,
472         atspi_path_root,
473         atspi_interface_accessible,
474         "GetChildren");
475   if (!message)
476     return;
477   reply = _atspi_dbus_send_with_reply_and_block (message);
478   if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
479   {
480     g_error ("Couldn't get application list: %s", error.message);
481     if (reply)
482       dbus_message_unref (reply);
483     return;
484   }
485   dbus_message_iter_init (reply, &iter);
486   dbus_message_iter_recurse (&iter, &iter_array);
487   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
488   {
489     const char *app_name, *path;
490     get_reference_from_iter (&iter_array, &app_name, &path);
491     add_accessibles (app_name);
492     add_app_to_desktop (desktop, app_name);
493   }
494   dbus_message_unref (reply);
495   return desktop;
496 }
497
498 AtspiAccessible *
499 _atspi_ref_accessible (const char *app, const char *path)
500 {
501   AtspiApplication *a = get_application (app);
502   if (!a) return NULL;
503   if ( APP_IS_REGISTRY(a))
504   {
505     return ref_accessible_desktop (a);
506   }
507   return ref_accessible (app, path);
508 }
509
510 AtspiAccessible *
511 _atspi_dbus_return_accessible_from_message (DBusMessage *message)
512 {
513   DBusMessageIter iter;
514   AtspiAccessible *retval = NULL;
515   const char *signature = dbus_message_get_signature (message);
516    
517   if (!strcmp (signature, "(so)"))
518   {
519     dbus_message_iter_init (message, &iter);
520     retval =  _atspi_dbus_return_accessible_from_iter (&iter);
521   }
522   else
523   {
524     g_warning ("Atspi: Called __atspi_dbus_return_accessible_from_message with strange signature %s", signature);
525   }
526   dbus_message_unref (message);
527   return retval;
528 }
529
530 AtspiAccessible *
531 _atspi_dbus_return_accessible_from_iter (DBusMessageIter *iter)
532 {
533   const char *app_name, *path;
534
535   get_reference_from_iter (iter, &app_name, &path);
536   return ref_accessible (app_name, path);
537 }
538
539 /* TODO: Remove this function. We should not need it anymore.
540  * If we do, it's a bug.
541  */
542 AtspiAccessible *
543 _atspi_ref_related_accessible (AtspiAccessible *obj, const AtspiReference *ref)
544 {
545   const char *app = (ref->name && ref->name[0]? ref->name: obj->app->bus_name);
546   return ref_accessible (app, obj->path);
547 }
548
549 const char *cache_signal_type = "((so)(so)(so)a(so)assusau)";
550
551 static DBusHandlerResult
552 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
553 {
554   DBusMessageIter iter;
555   const char *sender = dbus_message_get_sender (message);
556   AtspiApplication *app = get_application (sender);
557   const char *type = cache_signal_type;
558
559   if (strcmp (dbus_message_get_signature (message), cache_signal_type) != 0)
560   {
561     g_warning ("atspi: AddAccessible with unknown signature %s\n", dbus_message_get_signature (message));
562     return;
563   }
564
565   dbus_message_iter_init (message, &iter);
566   add_accessible_from_iter (&iter);
567 }
568
569 static DBusHandlerResult
570 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
571 {
572   int type = dbus_message_get_type (message);
573   const char *interface = dbus_message_get_interface (message);
574   const char *member = dbus_message_get_member (message); 
575   dbus_uint32_t v;
576   char *bus_name;
577
578   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
579       !strncmp (interface, "org.a11y.atspi.Event.", 21))
580   {
581     return atspi_dbus_handle_event (bus, message, data);
582   }
583   if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "notifyEvent"))
584   {
585     g_warning ("atspi: TODO: DeviceEvent");
586     //return handle_device_event (bus, message, data);
587   }
588   if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
589   {
590     return handle_add_accessible (bus, message, data);
591   }
592   if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
593   {
594     return handle_remove_accessible (bus, message, data);
595   }
596   /* TODO: Handle ChildrenChanged, StateChanged, PropertyChanged */
597   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
598 }
599
600 static const char *signal_interfaces[] =
601 {
602   "org.a11y.atspi.Event.Object",
603   "org.a11y.atspi.Event.Window",
604   "org.a11y.atspi.Event.Mouse",
605   "org.a11y.atspi.Event.Terminal",
606   "org.a11y.atspi.Event.Document",
607   "org.a11y.atspi.Event.Focus",
608   NULL
609 };
610
611 /*
612  * Returns a 'canonicalized' value for DISPLAY,
613  * with the screen number stripped off if present.
614  *
615  * TODO: Avoid having duplicate functions for this here and in at-spi2-atk
616  */
617 static const gchar *
618 spi_display_name (void)
619 {
620   static const char *canonical_display_name = NULL;
621   if (!canonical_display_name)
622     {
623       const gchar *display_env = g_getenv ("AT_SPI_DISPLAY");
624       if (!display_env)
625         {
626           display_env = g_getenv ("DISPLAY");
627           if (!display_env || !display_env[0])
628             canonical_display_name = ":0";
629           else
630             {
631               gchar *display_p, *screen_p;
632               canonical_display_name = g_strdup (display_env);
633               display_p = strrchr (canonical_display_name, ':');
634               screen_p = strrchr (canonical_display_name, '.');
635               if (screen_p && display_p && (screen_p > display_p))
636                 {
637                   *screen_p = '\0';
638                 }
639             }
640         }
641       else
642         {
643           canonical_display_name = display_env;
644         }
645     }
646   return canonical_display_name;
647 }
648
649 /* TODO: Avoid having duplicate functions for this here and in at-spi2-atk */
650 static DBusConnection *
651 get_accessibility_bus ()
652 {
653   Atom AT_SPI_BUS;
654   Atom actual_type;
655   Display *bridge_display;
656   int actual_format;
657   unsigned char *data = NULL;
658   unsigned long nitems;
659   unsigned long leftover;
660
661   DBusConnection *bus = NULL;
662   DBusError error;
663
664   bridge_display = XOpenDisplay (spi_display_name ());
665   if (!bridge_display)
666     {
667       g_warning ("AT_SPI: Could not get the display\n");
668       return NULL;
669     }
670
671   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
672   XGetWindowProperty (bridge_display,
673                       XDefaultRootWindow (bridge_display),
674                       AT_SPI_BUS, 0L,
675                       (long) BUFSIZ, False,
676                       (Atom) 31, &actual_type, &actual_format,
677                       &nitems, &leftover, &data);
678
679   dbus_error_init (&error);
680
681   if (data == NULL)
682     {
683       g_warning
684         ("AT-SPI: Accessibility bus not found - Using session bus.\n");
685       bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
686       if (!bus)
687         {
688           g_warning ("AT-SPI: Couldn't connect to bus: %s\n", error.message);
689           return NULL;
690         }
691     }
692   else
693     {
694       bus = dbus_connection_open (data, &error);
695       if (!bus)
696         {
697           g_warning ("AT-SPI: Couldn't connect to bus: %s\n", error.message);
698           return NULL;
699         }
700       else
701         {
702           if (!dbus_bus_register (bus, &error))
703             {
704               g_warning ("AT-SPI: Couldn't register with bus: %s\n", error.message);
705               return NULL;
706             }
707         }
708     }
709
710   return bus;
711 }
712
713 /**
714  * atspi_init:
715  *
716  * Connects to the accessibility registry and initializes the SPI.
717  *
718  * Returns: 0 on success, otherwise an integer error code.  
719  **/
720 int
721 atspi_init (void)
722 {
723   DBusError error;
724   char *match;
725   int i;
726
727   if (atspi_inited)
728     {
729       return 1;
730     }
731
732   atspi_inited = TRUE;
733
734   g_type_init ();
735
736   get_live_refs();
737
738   dbus_error_init (&error);
739   bus = get_accessibility_bus ();
740   if (!bus)
741   {
742     g_error ("Couldn't get session bus");
743     return 2;
744   }
745   dbus_bus_register (bus, &error);
746   dbus_connection_setup_with_g_main(bus, g_main_context_default());
747   dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
748   match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
749   dbus_error_init (&error);
750   dbus_bus_add_match (bus, match, &error);
751   g_free (match);
752   match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
753   dbus_bus_add_match (bus, match, &error);
754   g_free (match);
755   match = g_strdup_printf ("type='signal',interface='%s',member='ChildrenChanged'", atspi_interface_event_object);
756   dbus_bus_add_match (bus, match, &error);
757   g_free (match);
758   match = g_strdup_printf ("type='signal',interface='%s',member='PropertyChange'", atspi_interface_event_object);
759   dbus_bus_add_match (bus, match, &error);
760   g_free (match);
761   match = g_strdup_printf ("type='signal',interface='%s',member='StateChanged'", atspi_interface_event_object);
762   dbus_bus_add_match (bus, match, &error);
763   g_free (match);
764   return 0;
765 }
766
767   static GMainLoop *mainloop;
768
769 /**
770  * atspi_event_main:
771  *
772  * Starts/enters the main event loop for the AT-SPI services.
773  *
774  * (NOTE: This method does not return control, it is exited via a call to
775  *  atspi_event_quit () from within an event handler).
776  *
777  **/
778 void
779 atspi_event_main (void)
780 {
781   mainloop = g_main_loop_new (NULL, FALSE);
782   g_main_loop_run (mainloop);
783 }
784
785 /**
786  * atspi_event_quit:
787  *
788  * Quits the last main event loop for the SPI services,
789  * see atspi_event_main
790  **/
791 void
792 atspi_event_quit (void)
793 {
794   g_main_loop_quit (mainloop);
795 }
796
797 /**
798  * atspi_exit:
799  *
800  * Disconnects from the Accessibility Registry and releases 
801  * any floating resources. Call only once at exit.
802  *
803  * Returns: 0 if there were no leaks, otherwise non zero.
804  **/
805 int
806 atspi_exit (void)
807 {
808   int leaked;
809
810   if (!atspi_inited)
811     {
812       return 0;
813     }
814
815   atspi_inited = FALSE;
816
817   if (live_refs)
818     {
819       leaked = g_hash_table_size (live_refs);
820     }
821   else
822     {
823       leaked = 0;
824     }
825
826   cleanup ();
827
828   return leaked;
829 }
830
831 dbus_bool_t
832 _atspi_dbus_call (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
833 {
834   va_list args;
835   dbus_bool_t retval;
836   DBusError err;
837
838   dbus_error_init (&err);
839   va_start (args, type);
840   retval = dbind_method_call_reentrant_va (_atspi_bus(), obj->app->bus_name, obj->path, interface, method, &err, type, args);
841   va_end (args);
842   if (dbus_error_is_set (&err))
843   {
844     /* TODO: Set gerror */
845     dbus_error_free (&err);
846   }
847   return retval;
848 }
849
850 DBusMessage *
851 _atspi_dbus_call_partial (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
852 {
853   va_list args;
854   dbus_bool_t retval;
855   DBusError err;
856     DBusMessage *msg = NULL, *reply = NULL;
857     DBusMessageIter iter;
858     const char *p;
859
860   dbus_error_init (&err);
861   va_start (args, type);
862
863     msg = dbus_message_new_method_call (obj->app->bus_name, obj->path, interface, method);
864     if (!msg)
865         goto out;
866
867     p = type;
868     dbus_message_iter_init_append (msg, &iter);
869     dbind_any_marshal_va (&iter, &p, args);
870
871     reply = dbind_send_and_allow_reentry (_atspi_bus(), msg, &err);
872 out:
873   va_end (args);
874   if (dbus_error_is_set (&err))
875   {
876     /* TODO: Set gerror */
877     dbus_error_free (&err);
878   }
879   return reply;
880 }
881
882 dbus_bool_t
883 _atspi_dbus_get_property (AtspiAccessible *obj, const char *interface, const char *name, GError **error, const char *type, void *data)
884 {
885   DBusMessage *message, *reply;
886   DBusMessageIter iter, iter_variant;
887   DBusError err;
888   dbus_bool_t retval = FALSE;
889
890   message = dbus_message_new_method_call (obj->app->bus_name, obj->path, "org.freedesktop.DBus.Properties", "Get");
891   if (!message)
892   {
893     // TODO: throw exception
894     goto done;
895   }
896   dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
897   dbus_error_init (&err);
898   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
899   dbus_message_unref (message);
900   if (!reply)
901   {
902     // TODO: throw exception
903     goto done;
904   }
905   dbus_message_iter_init (reply, &iter);
906   dbus_message_iter_recurse (&iter, &iter_variant);
907   if (dbus_message_iter_get_arg_type (&iter_variant) != type[0])
908   {
909     g_warning ("atspi_dbus_get_property: Wrong type: expected %s, got %c\n", type, dbus_message_iter_get_arg_type (&iter_variant));
910     goto done;
911   }
912   dbus_message_iter_get_basic (&iter_variant, data);
913   dbus_message_unref (reply);
914   if (type[0] == 's') *(char **)data = g_strdup (*(char **)data);
915   retval = TRUE;
916 done:
917   return retval;
918 }
919
920 DBusMessage *
921 _atspi_dbus_send_with_reply_and_block (DBusMessage *message)
922 {
923   DBusMessage *reply;
924   DBusError err;
925
926   dbus_error_init (&err);
927   g_warning ("TODO: Write _atspi_dbus_send_with_reply_and_block");
928   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
929   dbus_message_unref (message);
930   return reply;
931 }
932
933 GHashTable *
934 _atspi_dbus_hash_from_message (DBusMessage *message)
935 {
936   GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
937   DBusMessageIter iter, iter_array, iter_dict;
938   const char *signature;
939
940   signature = dbus_message_get_signature (message);
941
942   if (strcmp (signature, "a{ss}") != 0)
943     {
944       g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
945       return NULL;
946     }
947
948   dbus_message_iter_init (message, &iter);
949   dbus_message_iter_recurse (&iter, &iter_array);
950   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
951   {
952     const char *name, *value;
953     dbus_message_iter_recurse (&iter_array, &iter_dict);
954     dbus_message_iter_get_basic (&iter_dict, &name);
955     dbus_message_iter_get_basic (&iter_dict, &value);
956     g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
957     dbus_message_iter_next (&iter_array);
958   }
959   return hash;
960 }
961
962 GArray *
963 _atspi_dbus_attribute_array_from_message (DBusMessage *message)
964 {
965   GArray *array = g_array_new (TRUE, TRUE, sizeof (gchar *));
966   DBusMessageIter iter, iter_array, iter_dict;
967   const char *signature;
968   gint count = 0;
969
970   signature = dbus_message_get_signature (message);
971
972   if (strcmp (signature, "a{ss}") != 0)
973     {
974       g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
975       return NULL;
976     }
977
978   dbus_message_iter_init (message, &iter);
979
980   dbus_message_iter_recurse (&iter, &iter_array);
981   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
982   {
983     const char *name, *value;
984     gchar *str;
985     GArray *new_array;
986     dbus_message_iter_recurse (&iter_array, &iter_dict);
987     dbus_message_iter_get_basic (&iter_dict, &name);
988     dbus_message_iter_get_basic (&iter_dict, &value);
989     str = g_strdup_printf ("%s:;%s", name, value);
990     new_array = g_array_append_val (array, str);
991     if (new_array)
992       array = new_array;
993     dbus_message_iter_next (&iter);;
994   }
995   return array;
996 }
997