Various 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
32 static DBusConnection *bus = NULL;
33 static GHashTable *apps = NULL;
34 static GHashTable *live_refs = NULL;
35 static GQueue *exception_handlers = NULL;
36 static DBusError exception;
37
38 const char *atspi_path_dec = ATSPI_DBUS_PATH_DEC;
39 const char *atspi_path_registry = ATSPI_DBUS_PATH_REGISTRY;
40 const char *atspi_path_root = ATSPI_DBUS_PATH_ROOT;
41 const char *atspi_bus_registry = ATSPI_DBUS_NAME_REGISTRY;
42 const char *atspi_interface_accessible = ATSPI_DBUS_INTERFACE_ACCESSIBLE;
43 const char *atspi_interface_action = ATSPI_DBUS_INTERFACE_ACTION;
44 const char *atspi_interface_application = ATSPI_DBUS_INTERFACE_APPLICATION;
45 const char *atspi_interface_component = ATSPI_DBUS_INTERFACE_COMPONENT;
46 const char *atspi_interface_dec = ATSPI_DBUS_INTERFACE_DEC;
47 const char *atspi_interface_device_event_listener = ATSPI_DBUS_INTERFACE_DEVICE_EVENT_LISTENER;
48 const char *atspi_interface_document = ATSPI_DBUS_INTERFACE_DOCUMENT;
49 const char *atspi_interface_editable_text = ATSPI_DBUS_INTERFACE_EDITABLE_TEXT;
50 const char *atspi_interface_hyperlink = ATSPI_DBUS_INTERFACE_HYPERLINK;
51 const char *atspi_interface_hypertext = ATSPI_DBUS_INTERFACE_HYPERTEXT;
52 const char *atspi_interface_image = ATSPI_DBUS_INTERFACE_IMAGE;
53 const char *atspi_interface_registry = ATSPI_DBUS_INTERFACE_REGISTRY;
54 const char *atspi_interface_selection = ATSPI_DBUS_INTERFACE_SELECTION;
55 const char *atspi_interface_table = ATSPI_DBUS_INTERFACE_TABLE;
56 const char *atspi_interface_text = ATSPI_DBUS_INTERFACE_TEXT;
57 const char *atspi_interface_cache = ATSPI_DBUS_INTERFACE_CACHE;
58 const char *atspi_interface_value = ATSPI_DBUS_INTERFACE_VALUE;
59
60 static const char *interfaces[] =
61 {
62   ATSPI_DBUS_INTERFACE_ACCESSIBLE,
63   ATSPI_DBUS_INTERFACE_ACTION,
64   ATSPI_DBUS_INTERFACE_APPLICATION,
65   ATSPI_DBUS_INTERFACE_COLLECTION,
66   ATSPI_DBUS_INTERFACE_COMPONENT,
67   ATSPI_DBUS_INTERFACE_DOCUMENT,
68   ATSPI_DBUS_INTERFACE_EDITABLE_TEXT,
69   ATSPI_DBUS_INTERFACE_HYPERLINK,
70   ATSPI_DBUS_INTERFACE_HYPERTEXT,
71   ATSPI_DBUS_INTERFACE_IMAGE,
72   "org.a11y.atspi.LoginHelper",
73   ATSPI_DBUS_INTERFACE_SELECTION,
74   ATSPI_DBUS_INTERFACE_TABLE,
75   ATSPI_DBUS_INTERFACE_TEXT,
76   ATSPI_DBUS_INTERFACE_VALUE,
77   NULL
78 };
79
80 static gint get_iface_num (const char *iface)
81 {
82   /* TODO: Use a binary search or hash to improve performance */
83   int i;
84
85   for (i = 0; interfaces[i]; i++)
86   {
87     if (!strcmp(iface, interfaces[i])) return i;
88   }
89   return -1;
90 }
91
92 static GHashTable *
93 get_live_refs (void)
94 {
95   if (!live_refs) 
96     {
97       live_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
98     }
99   return live_refs;
100 }
101
102 /* TODO: Add an application parameter */
103 DBusConnection *
104 _atspi_bus ()
105 {
106   if (!bus)
107     atspi_init ();
108   return bus;
109 }
110
111 #define APP_IS_REGISTRY(app) (!strcmp (app->bus_name, atspi_bus_registry))
112
113 static void
114 cleanup ()
115 {
116   GHashTable *refs;
117
118   refs = live_refs;
119   live_refs = NULL;
120   if (refs)
121     {
122       g_hash_table_destroy (refs);
123     }
124 }
125
126 static gboolean atspi_inited = FALSE;
127
128 static GHashTable *app_hash = NULL;
129
130 static AtspiApplication *
131 get_application (const char *bus_name)
132 {
133   AtspiApplication *app = NULL;
134   char *bus_name_dup;
135
136   if (!app_hash)
137   {
138     app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_unref);
139     if (!app_hash) return NULL;
140   }
141   app = g_hash_table_lookup (app_hash, bus_name);
142   if (app) return app;
143   bus_name_dup = g_strdup (bus_name);
144   if (!bus_name_dup) return NULL;
145   // TODO: change below to something that will send state-change:defunct notification if necessary */
146   app = _atspi_application_new (bus_name);
147   if (!app) return NULL;
148   app->bus_name = bus_name_dup;
149   if (APP_IS_REGISTRY (app))
150   {
151     app->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
152   }
153   else
154   {
155     app->hash = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_object_unref);
156   }
157   g_hash_table_insert (app_hash, bus_name_dup, app);
158   return app;
159 }
160
161 static AtspiAccessible *
162 ref_accessible (const char *app_name, const char *path)
163 {
164   AtspiApplication *app = get_application (app_name);
165   AtspiAccessible *a;
166
167   if (!strcmp (path, "/org/a11y/atspi/accessible/root"))
168   {
169     if (!app->root)
170     {
171       app->root = atspi_accessible_new (app, atspi_path_root);
172       app->root->accessible_parent = atspi_get_desktop (0);
173     }
174     return g_object_ref (app->root);
175   }
176
177   a = g_hash_table_lookup (app->hash, path);
178   if (a)
179   {
180     g_object_ref (a);
181     return a;
182   }
183   a = atspi_accessible_new (app, path);
184   if (!a)
185     return NULL;
186   g_hash_table_insert (app->hash, a->path, a);
187   g_object_ref (a);     /* for the hash */
188   return a;
189 }
190
191 typedef struct
192 {
193   char *path;
194   char *parent;
195   GArray *children;
196   GArray *interfaces;
197   char *name;
198   dbus_uint32_t role;
199   char *description;
200   GArray *state_bitflags;
201 } CACHE_ADDITION;
202
203 static DBusHandlerResult
204 handle_remove_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
205 {
206   const char *sender = dbus_message_get_sender (message);
207   AtspiApplication *app;
208   const char *path;
209   DBusMessageIter iter, iter_struct;
210   const char *signature = dbus_message_get_signature (message);
211   AtspiAccessible *a;
212   int id;
213
214   if (strcmp (signature, "(so)") != 0)
215   {
216     g_warning ("at-spi: Unknown signature %s for RemoveAccessible", signature);
217     return DBUS_HANDLER_RESULT_HANDLED;
218   }
219
220   dbus_message_iter_init (message, &iter);
221   dbus_message_iter_recurse (&iter, &iter_struct);
222   dbus_message_iter_get_basic (&iter_struct, &sender);
223   dbus_message_iter_get_basic (&iter_struct, &path);
224   app = get_application (sender);
225   a = ref_accessible (sender, path);
226   if (a->accessible_parent && g_list_find (a->accessible_parent->children, a))
227   {
228     a->accessible_parent->children = g_list_remove (a->accessible_parent->children, a);
229     g_object_unref (a);
230   }
231   g_hash_table_remove (app->hash, app->bus_name);
232   g_object_unref (a);   /* unref our own ref */
233   return DBUS_HANDLER_RESULT_HANDLED;
234 }
235
236 static gboolean
237 add_app_to_desktop (AtspiAccessible *a, const char *bus_name)
238 {
239   DBusError error;
240   char *root_path;
241
242   dbus_error_init (&error);
243   AtspiAccessible *obj = ref_accessible (bus_name, atspi_path_root);
244   if (obj)
245   {
246     GList *new_list = g_list_append (a->children, obj);
247     if (new_list)
248     {
249       a->children = new_list;
250       return TRUE;
251     }
252   }
253   else
254   {
255     g_warning ("Error calling getRoot for %s: %s", bus_name, error.message);
256   }
257   return FALSE;
258 }
259
260 static void
261 send_children_changed (AtspiAccessible *parent, AtspiAccessible *child, gboolean add)
262 {
263   AtspiEvent e;
264
265   memset (&e, 0, sizeof(e));
266   e.type = (add? "object:children-changed:add": "object:children-changed:remove");
267   e.source = parent;
268   e.detail1 = g_list_index (parent->children, child);
269 #if 0
270   g_warning ("atspi: TODO: Finish events");
271   atspi_dispatch_event (&e);
272 #endif
273 }
274
275 static void
276 unref_object_and_descendants (AtspiAccessible *obj)
277 {
278   GList *l;
279
280   for (l = obj->children; l; l = l->next)
281   {
282     unref_object_and_descendants (l->data);
283   }
284   g_object_unref (obj);
285 }
286
287 static gboolean
288 remove_app_from_desktop (AtspiAccessible *a, const char *bus_name)
289 {
290   GList *l;
291   AtspiAccessible *child;
292
293   for (l = a->children; l; l = l->next)
294   {
295     child = l->data;
296     if (!strcmp (bus_name, child->app->bus_name)) break;
297   }
298   if (!l)
299   {
300     g_warning ("Removing unregistered app %s; doing nothing\n", bus_name);
301     return FALSE;
302   }
303   send_children_changed (a, child, FALSE);
304   a->children = g_list_remove (a->children, child);
305   unref_object_and_descendants (child);
306   return TRUE;
307 }
308
309 static AtspiAccessible *desktop;
310
311 static void
312 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
313 {
314   DBusMessageIter iter_struct;
315
316   dbus_message_iter_recurse (iter, &iter_struct);
317   dbus_message_iter_get_basic (&iter_struct, app_name);
318   dbus_message_iter_next (&iter_struct);
319   dbus_message_iter_get_basic (&iter_struct, path);
320   dbus_message_iter_next (iter);
321 }
322
323 static void
324 add_accessible_from_iter (DBusMessageIter *iter)
325 {
326   gint i;
327   GList *new_list;
328   DBusMessageIter iter_struct, iter_array;
329   const char *app_name, *path;
330   AtspiApplication *app;
331   AtspiAccessible *accessible;
332   const char *name, *description;
333   dbus_uint32_t role;
334   dbus_uint32_t *states;
335   int count;
336
337   dbus_message_iter_recurse (iter, &iter_struct);
338
339   /* get accessible */
340   get_reference_from_iter (&iter_struct, &app_name, &path);
341   accessible = ref_accessible (app_name, path);
342
343   /* Get application: TODO */
344   dbus_message_iter_next (&iter_struct);
345
346   /* get parent */
347   get_reference_from_iter (&iter_struct, &app_name, &path);
348   accessible->accessible_parent = ref_accessible (app_name, path);
349
350   /* Get children */
351   dbus_message_iter_recurse (&iter_struct, &iter_array);
352   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
353   {
354     AtspiAccessible *child;
355     get_reference_from_iter (&iter_array, &app_name, &path);
356     child = ref_accessible (app_name, path);
357     new_list = g_list_append (accessible->children, child);
358     if (new_list) accessible->children = new_list;
359   }
360
361   /* interfaces */
362   accessible->interfaces = 0;
363   dbus_message_iter_next (&iter_struct);
364   dbus_message_iter_recurse (&iter_struct, &iter_array);
365   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
366   {
367     const char *iface;
368     gint n;
369     dbus_message_iter_get_basic (&iter_array, &iface);
370     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
371     n = get_iface_num (iface);
372     if (n == -1)
373     {
374       g_warning ("Unknown interface %s", iface);
375     }
376     else accessible->interfaces |= (1 << n);
377     dbus_message_iter_next (&iter_array);
378   }
379   dbus_message_iter_next (&iter_struct);
380
381   /* name */
382   dbus_message_iter_get_basic (&iter_struct, &name);
383   accessible->name = g_strdup (name);
384   dbus_message_iter_next (&iter_struct);
385
386   /* role */
387   dbus_message_iter_get_basic (&iter_struct, &role);
388   accessible->role = role;
389   dbus_message_iter_next (&iter_struct);
390
391   /* description */
392   dbus_message_iter_get_basic (&iter_struct, &description);
393   accessible->description = g_strdup (description);
394   dbus_message_iter_next (&iter_struct);
395
396   dbus_message_iter_recurse (&iter_struct, &iter_array);
397   dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
398   if (count != 2)
399   {
400     g_warning ("at-spi: expected 2 values in states array; got %d\n", count);
401     accessible->states = atspi_state_set_new (0);
402   }
403   else
404   {
405     guint64 val = ((guint64)states [1]) << 32;
406     val += states [0];
407     accessible->states = atspi_state_set_new (val);
408   }
409   dbus_message_iter_next (&iter_struct);
410
411   /* This is a bit of a hack since the cache holds a ref, so we don't need
412    * the one provided for us anymore */
413   g_object_unref (accessible);
414 }
415
416 static void
417 add_accessibles (const char *app_name)
418 {
419   DBusError error;
420   DBusMessage *message, *reply;
421   DBusMessageIter iter, iter_array;
422
423   AtspiApplication *app = get_application (app_name);
424   /* TODO: Move this functionality into app initializer? */
425   dbus_error_init (&error);
426   message = dbus_message_new_method_call (app_name, "/org/a11y/atspi/cache", atspi_interface_cache, "GetItems");
427   reply = _atspi_dbus_send_with_reply_and_block (message);
428   if (!reply || strcmp (dbus_message_get_signature (reply), "a((so)(so)(so)a(so)assusau)") != 0)
429   {
430     g_warning ("at-spi: Error in GetItems");
431     return;
432     if (reply)
433       dbus_message_unref (reply);
434   }
435   dbus_message_iter_init (reply, &iter);
436   dbus_message_iter_recurse (&iter, &iter_array);
437   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
438   {
439     add_accessible_from_iter (&iter_array);
440     dbus_message_iter_next (&iter_array);
441   }
442   dbus_message_unref (reply);
443 }
444
445 /* TODO: Do we stil need this function? */
446 static AtspiAccessible *
447 ref_accessible_desktop (AtspiApplication *app)
448 {
449   DBusError error;
450   GArray *apps = NULL;
451   GArray *additions;
452   gint i;
453   DBusMessage *message, *reply;
454   DBusMessageIter iter, iter_array;
455
456   if (desktop)
457   {
458     g_object_ref (desktop);
459     return desktop;
460   }
461   desktop = atspi_accessible_new (app, atspi_path_root);
462   if (!desktop)
463   {
464     return NULL;
465   }
466   g_hash_table_insert (app->hash, desktop->path, desktop);
467   g_object_ref (desktop);       /* for the hash */
468   desktop->name = g_strdup ("main");
469   dbus_error_init (&error);
470   message = dbus_message_new_method_call (atspi_bus_registry,
471         atspi_path_root,
472         atspi_interface_accessible,
473         "GetChildren");
474   if (!message)
475     return;
476   reply = _atspi_dbus_send_with_reply_and_block (message);
477   if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
478   {
479     g_error ("Couldn't get application list: %s", error.message);
480     if (reply)
481       dbus_message_unref (reply);
482     return;
483   }
484   dbus_message_iter_init (reply, &iter);
485   dbus_message_iter_recurse (&iter, &iter_array);
486   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
487   {
488     const char *app_name, *path;
489     get_reference_from_iter (&iter_array, &app_name, &path);
490     add_accessibles (app_name);
491     add_app_to_desktop (desktop, app_name);
492   }
493   dbus_message_unref (reply);
494   return desktop;
495 }
496
497 AtspiAccessible *
498 _atspi_ref_accessible (const char *app, const char *path)
499 {
500   AtspiApplication *a = get_application (app);
501   if (!a) return NULL;
502   if ( APP_IS_REGISTRY(a))
503   {
504     return ref_accessible_desktop (a);
505   }
506   return ref_accessible (app, path);
507 }
508
509 AtspiAccessible *
510 _atspi_ref_related_accessible (AtspiAccessible *obj, const AtspiReference *ref)
511 {
512   const char *app = (ref->name && ref->name[0]? ref->name: obj->app->bus_name);
513   return ref_accessible (app, obj->path);
514 }
515
516 const char *cache_signal_type = "((so)(so)a(so)assusau)";
517
518 static DBusHandlerResult
519 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
520 {
521   DBusMessageIter iter;
522   const char *sender = dbus_message_get_sender (message);
523   AtspiApplication *app = get_application (sender);
524   const char *type = cache_signal_type;
525
526   if (strcmp (dbus_message_get_signature (message), cache_signal_type) != 0)
527   {
528     g_warning ("atspi: AddAccessible with unknown signature %s\n", dbus_message_get_signature (message));
529     return;
530   }
531
532   dbus_message_iter_init (message, &iter);
533   add_accessible_from_iter (&iter);
534 }
535
536 static DBusHandlerResult
537 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
538 {
539   int type = dbus_message_get_type (message);
540   const char *interface = dbus_message_get_interface (message);
541   const char *member = dbus_message_get_member (message); 
542   dbus_uint32_t v;
543   char *bus_name;
544
545   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
546       !strncmp (interface, "org.a11y.atspi.Event.", 28))
547   {
548     g_warning ("atspi: TODO: event");
549     //return handle_event (bus, message, data);
550   }
551   if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "notifyEvent"))
552   {
553     g_warning ("atspi: TODO: DeviceEvent");
554     //return handle_device_event (bus, message, data);
555   }
556   if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
557   {
558     return handle_add_accessible (bus, message, data);
559   }
560   if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
561   {
562     return handle_remove_accessible (bus, message, data);
563   }
564   /* TODO: Handle ChildrenChanged, StateChanged, PropertyChanged */
565   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
566 }
567
568 static const char *signal_interfaces[] =
569 {
570   "org.a11y.atspi.Event.Object",
571   "org.a11y.atspi.Event.Window",
572   "org.a11y.atspi.Event.Mouse",
573   "org.a11y.atspi.Event.Terminal",
574   "org.a11y.atspi.Event.Document",
575   "org.a11y.atspi.Event.Focus",
576   NULL
577 };
578
579 /**
580  * atspi_init:
581  *
582  * Connects to the accessibility registry and initializes the SPI.
583  *
584  * Returns: 0 on success, otherwise an integer error code.  
585  **/
586 int
587 atspi_init (void)
588 {
589   DBusError error;
590   char *match;
591   int i;
592
593   if (atspi_inited)
594     {
595       return 1;
596     }
597
598   atspi_inited = TRUE;
599
600   g_type_init ();
601
602   get_live_refs();
603   g_atexit (cleanup);
604
605   dbus_error_init (&error);
606   bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
607   if (!bus)
608   {
609     g_error ("Couldn't get session bus");
610     return 2;
611   }
612   dbus_bus_register (bus, &error);
613   dbus_connection_setup_with_g_main(bus, g_main_context_default());
614   dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
615   match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
616   dbus_error_init (&error);
617   dbus_bus_add_match (bus, match, &error);
618   g_free (match);
619   match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
620   dbus_bus_add_match (bus, match, &error);
621   g_free (match);
622   for (i = 0; signal_interfaces[i]; i++)
623   {
624     match = g_strdup_printf ("type='signal',interface='%s'", signal_interfaces[i]);
625     dbus_bus_add_match (bus, match, &error);
626     g_free (match);
627   }
628   return 0;
629 }
630
631   static GMainLoop *mainloop;
632
633 /**
634  * atspi_event_main:
635  *
636  * Starts/enters the main event loop for the AT-SPI services.
637  *
638  * (NOTE: This method does not return control, it is exited via a call to
639  *  atspi_event_quit () from within an event handler).
640  *
641  **/
642 void
643 atspi_event_main (void)
644 {
645   mainloop = g_main_loop_new (NULL, FALSE);
646   g_main_loop_run (mainloop);
647 }
648
649 /**
650  * atspi_event_quit:
651  *
652  * Quits the last main event loop for the SPI services,
653  * see atspi_event_main
654  **/
655 void
656 atspi_event_quit (void)
657 {
658   g_main_loop_quit (mainloop);
659 }
660
661 /**
662  * atspi_exit:
663  *
664  * Disconnects from the Accessibility Registry and releases 
665  * any floating resources. Call only once at exit.
666  *
667  * Returns: 0 if there were no leaks, otherwise non zero.
668  **/
669 int
670 atspi_exit (void)
671 {
672   int leaked;
673
674   if (!atspi_inited)
675     {
676       return 0;
677     }
678
679   atspi_inited = FALSE;
680
681   if (live_refs)
682     {
683       leaked = g_hash_table_size (live_refs);
684     }
685   else
686     {
687       leaked = 0;
688     }
689
690   cleanup ();
691
692   return leaked;
693 }
694
695 dbus_bool_t
696 _atspi_dbus_call (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
697 {
698   va_list args;
699   dbus_bool_t retval;
700   DBusError err;
701
702   dbus_error_init (&err);
703   va_start (args, type);
704   retval = dbind_method_call_reentrant_va (_atspi_bus(), obj->app->bus_name, obj->path, interface, method, &err, type, args);
705   va_end (args);
706   if (dbus_error_is_set (&err))
707   {
708     /* TODO: Set gerror */
709     dbus_error_free (&err);
710   }
711   return retval;
712 }
713
714 DBusMessage *
715 _atspi_dbus_call_partial (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
716 {
717   va_list args;
718   dbus_bool_t retval;
719   DBusError err;
720     DBusMessage *msg = NULL, *reply = NULL;
721     DBusMessageIter iter;
722     const char *p;
723
724   dbus_error_init (&err);
725   va_start (args, type);
726
727     msg = dbus_message_new_method_call (obj->app->bus_name, obj->path, interface, method);
728     if (!msg)
729         goto out;
730
731     p = type;
732     dbus_message_iter_init_append (msg, &iter);
733     dbind_any_marshal_va (&iter, &p, args);
734
735     reply = dbind_send_and_allow_reentry (_atspi_bus(), msg, &err);
736 out:
737   va_end (args);
738   if (dbus_error_is_set (&err))
739   {
740     /* TODO: Set gerror */
741     dbus_error_free (&err);
742   }
743   return reply;
744 }
745
746 dbus_bool_t
747 _atspi_dbus_get_property (AtspiAccessible *obj, const char *interface, const char *name, GError **error, const char *type, void *data)
748 {
749   DBusMessage *message, *reply;
750   DBusMessageIter iter, iter_variant;
751   DBusError err;
752   dbus_bool_t retval = FALSE;
753
754   message = dbus_message_new_method_call (obj->app->bus_name, obj->path, "org.freedesktop.DBus.Properties", "Get");
755   if (!message)
756   {
757     // TODO: throw exception
758     goto done;
759   }
760   dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
761   dbus_error_init (&err);
762   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
763   dbus_message_unref (message);
764   if (!reply)
765   {
766     // TODO: throw exception
767     goto done;
768   }
769   dbus_message_iter_init (reply, &iter);
770   dbus_message_iter_recurse (&iter, &iter_variant);
771   if (dbus_message_iter_get_arg_type (&iter_variant) != type[0])
772   {
773     g_warning ("atspi_dbus_get_property: Wrong type: expected %s, got %c\n", type, dbus_message_iter_get_arg_type (&iter_variant));
774     goto done;
775   }
776   dbus_message_iter_get_basic (&iter_variant, data);
777   dbus_message_unref (reply);
778   if (type[0] == 's') *(char **)data = g_strdup (*(char **)data);
779   retval = TRUE;
780 done:
781   return retval;
782 }
783
784 DBusMessage *
785 _atspi_dbus_send_with_reply_and_block (DBusMessage *message)
786 {
787   DBusMessage *reply;
788   DBusError err;
789
790   dbus_error_init (&err);
791   g_warning ("TODO: Write _atspi_dbus_send_with_reply_and_block");
792   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
793   dbus_message_unref (message);
794   return reply;
795 }
796
797 GHashTable *
798 _atspi_dbus_hash_from_message (DBusMessage *message)
799 {
800   GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
801   DBusMessageIter iter, iter_array, iter_dict;
802   const char *signature;
803
804   signature = dbus_message_get_signature (message);
805
806   if (strcmp (signature, "a{ss}") != 0)
807     {
808       g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
809       return NULL;
810     }
811
812   dbus_message_iter_init (message, &iter);
813   dbus_message_iter_recurse (&iter, &iter_array);
814   do
815   {
816     const char *name, *value;
817     dbus_message_iter_recurse (&iter_array, &iter_dict);
818     dbus_message_iter_get_basic (&iter_dict, &name);
819     dbus_message_iter_get_basic (&iter_dict, &value);
820     g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
821   } while (dbus_message_iter_next (&iter_array));
822   return hash;
823 }
824