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