Box some types, add missing header file, and other 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   e.type = (add? "object:children-changed:add": "object:children-changed:remove");
270   e.source = parent;
271   e.detail1 = g_list_index (parent->children, child);
272   e.detail2 = 0;
273   g_value_unset (&e.any);
274   _atspi_send_event (&e);
275 }
276
277 static void
278 unref_object_and_descendants (AtspiAccessible *obj)
279 {
280   GList *l;
281
282   for (l = obj->children; l; l = l->next)
283   {
284     unref_object_and_descendants (l->data);
285   }
286   g_object_unref (obj);
287 }
288
289 static gboolean
290 remove_app_from_desktop (AtspiAccessible *a, const char *bus_name)
291 {
292   GList *l;
293   AtspiAccessible *child;
294
295   for (l = a->children; l; l = l->next)
296   {
297     child = l->data;
298     if (!strcmp (bus_name, child->app->bus_name)) break;
299   }
300   if (!l)
301   {
302     g_warning ("Removing unregistered app %s; doing nothing\n", bus_name);
303     return FALSE;
304   }
305   send_children_changed (a, child, FALSE);
306   a->children = g_list_remove (a->children, child);
307   unref_object_and_descendants (child);
308   return TRUE;
309 }
310
311 static AtspiAccessible *desktop;
312
313 void
314 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
315 {
316   DBusMessageIter iter_struct;
317
318   dbus_message_iter_recurse (iter, &iter_struct);
319   dbus_message_iter_get_basic (&iter_struct, app_name);
320   dbus_message_iter_next (&iter_struct);
321   dbus_message_iter_get_basic (&iter_struct, path);
322   dbus_message_iter_next (iter);
323 }
324
325 static void
326 add_accessible_from_iter (DBusMessageIter *iter)
327 {
328   gint i;
329   GList *new_list;
330   DBusMessageIter iter_struct, iter_array;
331   const char *app_name, *path;
332   AtspiApplication *app;
333   AtspiAccessible *accessible;
334   const char *name, *description;
335   dbus_uint32_t role;
336   dbus_uint32_t *states;
337   int count;
338
339   dbus_message_iter_recurse (iter, &iter_struct);
340
341   /* get accessible */
342   get_reference_from_iter (&iter_struct, &app_name, &path);
343   accessible = ref_accessible (app_name, path);
344
345   /* Get application: TODO */
346   dbus_message_iter_next (&iter_struct);
347
348   /* get parent */
349   get_reference_from_iter (&iter_struct, &app_name, &path);
350   accessible->accessible_parent = ref_accessible (app_name, path);
351
352   /* Get children */
353   dbus_message_iter_recurse (&iter_struct, &iter_array);
354   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
355   {
356     AtspiAccessible *child;
357     get_reference_from_iter (&iter_array, &app_name, &path);
358     child = ref_accessible (app_name, path);
359     new_list = g_list_append (accessible->children, child);
360     if (new_list) accessible->children = new_list;
361   }
362
363   /* interfaces */
364   accessible->interfaces = 0;
365   dbus_message_iter_next (&iter_struct);
366   dbus_message_iter_recurse (&iter_struct, &iter_array);
367   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
368   {
369     const char *iface;
370     gint n;
371     dbus_message_iter_get_basic (&iter_array, &iface);
372     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
373     n = _atspi_get_iface_num (iface);
374     if (n == -1)
375     {
376       g_warning ("Unknown interface %s", iface);
377     }
378     else accessible->interfaces |= (1 << n);
379     dbus_message_iter_next (&iter_array);
380   }
381   dbus_message_iter_next (&iter_struct);
382
383   /* name */
384   dbus_message_iter_get_basic (&iter_struct, &name);
385   accessible->name = g_strdup (name);
386   dbus_message_iter_next (&iter_struct);
387
388   /* role */
389   dbus_message_iter_get_basic (&iter_struct, &role);
390   accessible->role = role;
391   dbus_message_iter_next (&iter_struct);
392
393   /* description */
394   dbus_message_iter_get_basic (&iter_struct, &description);
395   accessible->description = g_strdup (description);
396   dbus_message_iter_next (&iter_struct);
397
398   dbus_message_iter_recurse (&iter_struct, &iter_array);
399   dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
400   if (count != 2)
401   {
402     g_warning ("at-spi: expected 2 values in states array; got %d\n", count);
403     accessible->states = atspi_state_set_new (accessible, 0);
404   }
405   else
406   {
407     guint64 val = ((guint64)states [1]) << 32;
408     val += states [0];
409     accessible->states = atspi_state_set_new (accessible, val);
410   }
411   dbus_message_iter_next (&iter_struct);
412
413   /* This is a bit of a hack since the cache holds a ref, so we don't need
414    * the one provided for us anymore */
415   g_object_unref (accessible);
416 }
417
418 static void
419 add_accessibles (const char *app_name)
420 {
421   DBusError error;
422   DBusMessage *message, *reply;
423   DBusMessageIter iter, iter_array;
424
425   AtspiApplication *app = get_application (app_name);
426   /* TODO: Move this functionality into app initializer? */
427   dbus_error_init (&error);
428   message = dbus_message_new_method_call (app_name, "/org/a11y/atspi/cache", atspi_interface_cache, "GetItems");
429   reply = _atspi_dbus_send_with_reply_and_block (message);
430   if (!reply || strcmp (dbus_message_get_signature (reply), "a((so)(so)(so)a(so)assusau)") != 0)
431   {
432     g_warning ("at-spi: Error in GetItems");
433     return;
434     if (reply)
435       dbus_message_unref (reply);
436   }
437   dbus_message_iter_init (reply, &iter);
438   dbus_message_iter_recurse (&iter, &iter_array);
439   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
440   {
441     add_accessible_from_iter (&iter_array);
442     dbus_message_iter_next (&iter_array);
443   }
444   dbus_message_unref (reply);
445 }
446
447 /* TODO: Do we stil need this function? */
448 static AtspiAccessible *
449 ref_accessible_desktop (AtspiApplication *app)
450 {
451   DBusError error;
452   GArray *apps = NULL;
453   GArray *additions;
454   gint i;
455   DBusMessage *message, *reply;
456   DBusMessageIter iter, iter_array;
457
458   if (desktop)
459   {
460     g_object_ref (desktop);
461     return desktop;
462   }
463   desktop = atspi_accessible_new (app, atspi_path_root);
464   if (!desktop)
465   {
466     return NULL;
467   }
468   g_hash_table_insert (app->hash, desktop->path, desktop);
469   g_object_ref (desktop);       /* for the hash */
470   desktop->name = g_strdup ("main");
471   dbus_error_init (&error);
472   message = dbus_message_new_method_call (atspi_bus_registry,
473         atspi_path_root,
474         atspi_interface_accessible,
475         "GetChildren");
476   if (!message)
477     return;
478   reply = _atspi_dbus_send_with_reply_and_block (message);
479   if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
480   {
481     g_error ("Couldn't get application list: %s", error.message);
482     if (reply)
483       dbus_message_unref (reply);
484     return;
485   }
486   dbus_message_iter_init (reply, &iter);
487   dbus_message_iter_recurse (&iter, &iter_array);
488   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
489   {
490     const char *app_name, *path;
491     get_reference_from_iter (&iter_array, &app_name, &path);
492     add_accessibles (app_name);
493     add_app_to_desktop (desktop, app_name);
494   }
495   dbus_message_unref (reply);
496   return desktop;
497 }
498
499 AtspiAccessible *
500 _atspi_ref_accessible (const char *app, const char *path)
501 {
502   AtspiApplication *a = get_application (app);
503   if (!a) return NULL;
504   if ( APP_IS_REGISTRY(a))
505   {
506     return ref_accessible_desktop (a);
507   }
508   return ref_accessible (app, path);
509 }
510
511 AtspiAccessible *
512 _atspi_dbus_return_accessible_from_message (DBusMessage *message)
513 {
514   DBusMessageIter iter;
515   AtspiAccessible *retval = NULL;
516   const char *signature = dbus_message_get_signature (message);
517    
518   if (!strcmp (signature, "(so)"))
519   {
520     dbus_message_iter_init (message, &iter);
521     retval =  _atspi_dbus_return_accessible_from_iter (&iter);
522   }
523   else
524   {
525     g_warning ("Atspi: Called __atspi_dbus_return_accessible_from_message with strange signature %s", signature);
526   }
527   dbus_message_unref (message);
528   return retval;
529 }
530
531 AtspiAccessible *
532 _atspi_dbus_return_accessible_from_iter (DBusMessageIter *iter)
533 {
534   const char *app_name, *path;
535
536   get_reference_from_iter (iter, &app_name, &path);
537   return ref_accessible (app_name, path);
538 }
539
540 /* TODO: Remove this function. We should not need it anymore.
541  * If we do, it's a bug.
542  */
543 AtspiAccessible *
544 _atspi_ref_related_accessible (AtspiAccessible *obj, const AtspiReference *ref)
545 {
546   const char *app = (ref->name && ref->name[0]? ref->name: obj->app->bus_name);
547   return ref_accessible (app, obj->path);
548 }
549
550 const char *cache_signal_type = "((so)(so)(so)a(so)assusau)";
551
552 static DBusHandlerResult
553 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
554 {
555   DBusMessageIter iter;
556   const char *sender = dbus_message_get_sender (message);
557   AtspiApplication *app = get_application (sender);
558   const char *type = cache_signal_type;
559
560   if (strcmp (dbus_message_get_signature (message), cache_signal_type) != 0)
561   {
562     g_warning ("atspi: AddAccessible with unknown signature %s\n", dbus_message_get_signature (message));
563     return;
564   }
565
566   dbus_message_iter_init (message, &iter);
567   add_accessible_from_iter (&iter);
568 }
569
570 static DBusHandlerResult
571 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
572 {
573   int type = dbus_message_get_type (message);
574   const char *interface = dbus_message_get_interface (message);
575   const char *member = dbus_message_get_member (message); 
576   dbus_uint32_t v;
577   char *bus_name;
578
579   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
580       !strncmp (interface, "org.a11y.atspi.Event.", 21))
581   {
582     return atspi_dbus_handle_event (bus, message, data);
583   }
584   if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "notifyEvent"))
585   {
586     g_warning ("atspi: TODO: DeviceEvent");
587     //return handle_device_event (bus, message, data);
588   }
589   if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
590   {
591     return handle_add_accessible (bus, message, data);
592   }
593   if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
594   {
595     return handle_remove_accessible (bus, message, data);
596   }
597   /* TODO: Handle ChildrenChanged, StateChanged, PropertyChanged */
598   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
599 }
600
601 static const char *signal_interfaces[] =
602 {
603   "org.a11y.atspi.Event.Object",
604   "org.a11y.atspi.Event.Window",
605   "org.a11y.atspi.Event.Mouse",
606   "org.a11y.atspi.Event.Terminal",
607   "org.a11y.atspi.Event.Document",
608   "org.a11y.atspi.Event.Focus",
609   NULL
610 };
611
612 /*
613  * Returns a 'canonicalized' value for DISPLAY,
614  * with the screen number stripped off if present.
615  *
616  * TODO: Avoid having duplicate functions for this here and in at-spi2-atk
617  */
618 static const gchar *
619 spi_display_name (void)
620 {
621   static const char *canonical_display_name = NULL;
622   if (!canonical_display_name)
623     {
624       const gchar *display_env = g_getenv ("AT_SPI_DISPLAY");
625       if (!display_env)
626         {
627           display_env = g_getenv ("DISPLAY");
628           if (!display_env || !display_env[0])
629             canonical_display_name = ":0";
630           else
631             {
632               gchar *display_p, *screen_p;
633               canonical_display_name = g_strdup (display_env);
634               display_p = g_utf8_strrchr (canonical_display_name, -1, ':');
635               screen_p = g_utf8_strrchr (canonical_display_name, -1, '.');
636               if (screen_p && display_p && (screen_p > display_p))
637                 {
638                   *screen_p = '\0';
639                 }
640             }
641         }
642       else
643         {
644           canonical_display_name = display_env;
645         }
646     }
647   return canonical_display_name;
648 }
649
650 /* TODO: Avoid having duplicate functions for this here and in at-spi2-atk */
651 static DBusConnection *
652 get_accessibility_bus ()
653 {
654   Atom AT_SPI_BUS;
655   Atom actual_type;
656   Display *bridge_display;
657   int actual_format;
658   unsigned char *data = NULL;
659   unsigned long nitems;
660   unsigned long leftover;
661
662   DBusConnection *bus = NULL;
663   DBusError error;
664
665   bridge_display = XOpenDisplay (spi_display_name ());
666   if (!bridge_display)
667     {
668       g_warning ("AT_SPI: Could not get the display\n");
669       return NULL;
670     }
671
672   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
673   XGetWindowProperty (bridge_display,
674                       XDefaultRootWindow (bridge_display),
675                       AT_SPI_BUS, 0L,
676                       (long) BUFSIZ, False,
677                       (Atom) 31, &actual_type, &actual_format,
678                       &nitems, &leftover, &data);
679
680   dbus_error_init (&error);
681
682   if (data == NULL)
683     {
684       g_warning
685         ("AT-SPI: Accessibility bus not found - Using session bus.\n");
686       bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
687       if (!bus)
688         {
689           g_warning ("AT-SPI: Couldn't connect to bus: %s\n", error.message);
690           return NULL;
691         }
692     }
693   else
694     {
695       bus = dbus_connection_open (data, &error);
696       if (!bus)
697         {
698           g_warning ("AT-SPI: Couldn't connect to bus: %s\n", error.message);
699           return NULL;
700         }
701       else
702         {
703           if (!dbus_bus_register (bus, &error))
704             {
705               g_warning ("AT-SPI: Couldn't register with bus: %s\n", error.message);
706               return NULL;
707             }
708         }
709     }
710
711   return bus;
712 }
713
714 /**
715  * atspi_init:
716  *
717  * Connects to the accessibility registry and initializes the SPI.
718  *
719  * Returns: 0 on success, otherwise an integer error code.  
720  **/
721 int
722 atspi_init (void)
723 {
724   DBusError error;
725   char *match;
726   int i;
727
728   if (atspi_inited)
729     {
730       return 1;
731     }
732
733   atspi_inited = TRUE;
734
735   g_type_init ();
736
737   get_live_refs();
738
739   dbus_error_init (&error);
740   bus = get_accessibility_bus ();
741   if (!bus)
742   {
743     g_error ("Couldn't get session bus");
744     return 2;
745   }
746   dbus_bus_register (bus, &error);
747   dbus_connection_setup_with_g_main(bus, g_main_context_default());
748   dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
749   match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
750   dbus_error_init (&error);
751   dbus_bus_add_match (bus, match, &error);
752   g_free (match);
753   match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
754   dbus_bus_add_match (bus, match, &error);
755   g_free (match);
756   match = g_strdup_printf ("type='signal',interface='%s',member='ChildrenChanged'", atspi_interface_event_object);
757   dbus_bus_add_match (bus, match, &error);
758   g_free (match);
759   match = g_strdup_printf ("type='signal',interface='%s',member='PropertyChange'", atspi_interface_event_object);
760   dbus_bus_add_match (bus, match, &error);
761   g_free (match);
762   match = g_strdup_printf ("type='signal',interface='%s',member='StateChanged'", atspi_interface_event_object);
763   dbus_bus_add_match (bus, match, &error);
764   g_free (match);
765   return 0;
766 }
767
768   static GMainLoop *mainloop;
769
770 /**
771  * atspi_event_main:
772  *
773  * Starts/enters the main event loop for the AT-SPI services.
774  *
775  * (NOTE: This method does not return control, it is exited via a call to
776  *  atspi_event_quit () from within an event handler).
777  *
778  **/
779 void
780 atspi_event_main (void)
781 {
782   mainloop = g_main_loop_new (NULL, FALSE);
783   g_main_loop_run (mainloop);
784 }
785
786 /**
787  * atspi_event_quit:
788  *
789  * Quits the last main event loop for the SPI services,
790  * see atspi_event_main
791  **/
792 void
793 atspi_event_quit (void)
794 {
795   g_main_loop_quit (mainloop);
796 }
797
798 /**
799  * atspi_exit:
800  *
801  * Disconnects from the Accessibility Registry and releases 
802  * any floating resources. Call only once at exit.
803  *
804  * Returns: 0 if there were no leaks, otherwise non zero.
805  **/
806 int
807 atspi_exit (void)
808 {
809   int leaked;
810
811   if (!atspi_inited)
812     {
813       return 0;
814     }
815
816   atspi_inited = FALSE;
817
818   if (live_refs)
819     {
820       leaked = g_hash_table_size (live_refs);
821     }
822   else
823     {
824       leaked = 0;
825     }
826
827   cleanup ();
828
829   return leaked;
830 }
831
832 dbus_bool_t
833 _atspi_dbus_call (gpointer obj, const char *interface, const char *method, GError **error, const char *type, ...)
834 {
835   va_list args;
836   dbus_bool_t retval;
837   DBusError err;
838   AtspiAccessible *accessible = ATSPI_ACCESSIBLE (obj);
839
840   dbus_error_init (&err);
841   va_start (args, type);
842   retval = dbind_method_call_reentrant_va (_atspi_bus(), accessible->app->bus_name, accessible->path, interface, method, &err, type, args);
843   va_end (args);
844   if (dbus_error_is_set (&err))
845   {
846     /* TODO: Set gerror */
847     dbus_error_free (&err);
848   }
849   return retval;
850 }
851
852 DBusMessage *
853 _atspi_dbus_call_partial (gpointer obj, const char *interface, const char *method, GError **error, const char *type, ...)
854 {
855   AtspiAccessible *accessible = ATSPI_ACCESSIBLE (obj);
856   va_list args;
857   dbus_bool_t retval;
858   DBusError err;
859     DBusMessage *msg = NULL, *reply = NULL;
860     DBusMessageIter iter;
861     const char *p;
862
863   dbus_error_init (&err);
864   va_start (args, type);
865
866     msg = dbus_message_new_method_call (accessible->app->bus_name, accessible->path, interface, method);
867     if (!msg)
868         goto out;
869
870     p = type;
871     dbus_message_iter_init_append (msg, &iter);
872     dbind_any_marshal_va (&iter, &p, args);
873
874     reply = dbind_send_and_allow_reentry (_atspi_bus(), msg, &err);
875 out:
876   va_end (args);
877   if (dbus_error_is_set (&err))
878   {
879     /* TODO: Set gerror */
880     dbus_error_free (&err);
881   }
882   return reply;
883 }
884
885 dbus_bool_t
886 _atspi_dbus_get_property (AtspiAccessible *obj, const char *interface, const char *name, GError **error, const char *type, void *data)
887 {
888   DBusMessage *message, *reply;
889   DBusMessageIter iter, iter_variant;
890   DBusError err;
891   dbus_bool_t retval = FALSE;
892
893   message = dbus_message_new_method_call (obj->app->bus_name, obj->path, "org.freedesktop.DBus.Properties", "Get");
894   if (!message)
895   {
896     // TODO: throw exception
897     goto done;
898   }
899   dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
900   dbus_error_init (&err);
901   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
902   dbus_message_unref (message);
903   if (!reply)
904   {
905     // TODO: throw exception
906     goto done;
907   }
908   dbus_message_iter_init (reply, &iter);
909   dbus_message_iter_recurse (&iter, &iter_variant);
910   if (dbus_message_iter_get_arg_type (&iter_variant) != type[0])
911   {
912     g_warning ("atspi_dbus_get_property: Wrong type: expected %s, got %c\n", type, dbus_message_iter_get_arg_type (&iter_variant));
913     goto done;
914   }
915   dbus_message_iter_get_basic (&iter_variant, data);
916   dbus_message_unref (reply);
917   if (type[0] == 's') *(char **)data = g_strdup (*(char **)data);
918   retval = TRUE;
919 done:
920   return retval;
921 }
922
923 DBusMessage *
924 _atspi_dbus_send_with_reply_and_block (DBusMessage *message)
925 {
926   DBusMessage *reply;
927   DBusError err;
928
929   dbus_error_init (&err);
930   /* TODO: Write this function; allow reentrancy */
931   reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
932   dbus_message_unref (message);
933   return reply;
934 }
935
936 GHashTable *
937 _atspi_dbus_hash_from_message (DBusMessage *message)
938 {
939   GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
940   DBusMessageIter iter, iter_array, iter_dict;
941   const char *signature;
942
943   signature = dbus_message_get_signature (message);
944
945   if (strcmp (signature, "a{ss}") != 0)
946     {
947       g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
948       return NULL;
949     }
950
951   dbus_message_iter_init (message, &iter);
952   dbus_message_iter_recurse (&iter, &iter_array);
953   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
954   {
955     const char *name, *value;
956     dbus_message_iter_recurse (&iter_array, &iter_dict);
957     dbus_message_iter_get_basic (&iter_dict, &name);
958     dbus_message_iter_get_basic (&iter_dict, &value);
959     g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
960     dbus_message_iter_next (&iter_array);
961   }
962   return hash;
963 }
964
965 GArray *
966 _atspi_dbus_attribute_array_from_message (DBusMessage *message)
967 {
968   GArray *array = g_array_new (TRUE, TRUE, sizeof (gchar *));
969   DBusMessageIter iter, iter_array, iter_dict;
970   const char *signature;
971   gint count = 0;
972
973   signature = dbus_message_get_signature (message);
974
975   if (strcmp (signature, "a{ss}") != 0)
976     {
977       g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
978       return NULL;
979     }
980
981   dbus_message_iter_init (message, &iter);
982
983   dbus_message_iter_recurse (&iter, &iter_array);
984   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
985   {
986     const char *name, *value;
987     gchar *str;
988     GArray *new_array;
989     dbus_message_iter_recurse (&iter_array, &iter_dict);
990     dbus_message_iter_get_basic (&iter_dict, &name);
991     dbus_message_iter_get_basic (&iter_dict, &value);
992     str = g_strdup_printf ("%s:;%s", name, value);
993     new_array = g_array_append_val (array, str);
994     if (new_array)
995       array = new_array;
996     dbus_message_iter_next (&iter);;
997   }
998   return array;
999 }
1000