Fix most compiler warnings and various miscellaneous problems
[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 "dbus/dbus-glib.h"
33 #include <stdio.h>
34 #include <string.h>
35
36 static void handle_get_items (DBusPendingCall *pending, void *user_data);
37
38 static DBusConnection *bus = NULL;
39 static GHashTable *live_refs = NULL;
40
41 const char *atspi_path_dec = ATSPI_DBUS_PATH_DEC;
42 const char *atspi_path_registry = ATSPI_DBUS_PATH_REGISTRY;
43 const char *atspi_path_root = ATSPI_DBUS_PATH_ROOT;
44 const char *atspi_bus_registry = ATSPI_DBUS_NAME_REGISTRY;
45 const char *atspi_interface_accessible = ATSPI_DBUS_INTERFACE_ACCESSIBLE;
46 const char *atspi_interface_action = ATSPI_DBUS_INTERFACE_ACTION;
47 const char *atspi_interface_application = ATSPI_DBUS_INTERFACE_APPLICATION;
48 const char *atspi_interface_collection = ATSPI_DBUS_INTERFACE_COLLECTION;
49 const char *atspi_interface_component = ATSPI_DBUS_INTERFACE_COMPONENT;
50 const char *atspi_interface_dec = ATSPI_DBUS_INTERFACE_DEC;
51 const char *atspi_interface_device_event_listener = ATSPI_DBUS_INTERFACE_DEVICE_EVENT_LISTENER;
52 const char *atspi_interface_document = ATSPI_DBUS_INTERFACE_DOCUMENT;
53 const char *atspi_interface_editable_text = ATSPI_DBUS_INTERFACE_EDITABLE_TEXT;
54 const char *atspi_interface_event_object = ATSPI_DBUS_INTERFACE_EVENT_OBJECT;
55 const char *atspi_interface_hyperlink = ATSPI_DBUS_INTERFACE_HYPERLINK;
56 const char *atspi_interface_hypertext = ATSPI_DBUS_INTERFACE_HYPERTEXT;
57 const char *atspi_interface_image = ATSPI_DBUS_INTERFACE_IMAGE;
58 const char *atspi_interface_registry = ATSPI_DBUS_INTERFACE_REGISTRY;
59 const char *atspi_interface_selection = ATSPI_DBUS_INTERFACE_SELECTION;
60 const char *atspi_interface_table = ATSPI_DBUS_INTERFACE_TABLE;
61 const char *atspi_interface_text = ATSPI_DBUS_INTERFACE_TEXT;
62 const char *atspi_interface_cache = ATSPI_DBUS_INTERFACE_CACHE;
63 const char *atspi_interface_value = ATSPI_DBUS_INTERFACE_VALUE;
64
65 static const char *interfaces[] =
66 {
67   ATSPI_DBUS_INTERFACE_ACCESSIBLE,
68   ATSPI_DBUS_INTERFACE_ACTION,
69   ATSPI_DBUS_INTERFACE_APPLICATION,
70   ATSPI_DBUS_INTERFACE_COLLECTION,
71   ATSPI_DBUS_INTERFACE_COMPONENT,
72   ATSPI_DBUS_INTERFACE_DOCUMENT,
73   ATSPI_DBUS_INTERFACE_EDITABLE_TEXT,
74   ATSPI_DBUS_INTERFACE_HYPERLINK,
75   ATSPI_DBUS_INTERFACE_HYPERTEXT,
76   ATSPI_DBUS_INTERFACE_IMAGE,
77   "org.a11y.atspi.LoginHelper",
78   ATSPI_DBUS_INTERFACE_SELECTION,
79   ATSPI_DBUS_INTERFACE_TABLE,
80   ATSPI_DBUS_INTERFACE_TEXT,
81   ATSPI_DBUS_INTERFACE_VALUE,
82   NULL
83 };
84
85 gint
86 _atspi_get_iface_num (const char *iface)
87 {
88   /* TODO: Use a binary search or hash to improve performance */
89   int i;
90
91   for (i = 0; interfaces[i]; i++)
92   {
93     if (!strcmp(iface, interfaces[i])) return i;
94   }
95   return -1;
96 }
97
98 static GHashTable *
99 get_live_refs (void)
100 {
101   if (!live_refs) 
102     {
103       live_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
104     }
105   return live_refs;
106 }
107
108 /* TODO: Add an application parameter */
109 DBusConnection *
110 _atspi_bus ()
111 {
112   if (!bus)
113     atspi_init ();
114   return bus;
115 }
116
117 #define APP_IS_REGISTRY(app) (!strcmp (app->bus_name, atspi_bus_registry))
118
119 static void
120 cleanup ()
121 {
122   GHashTable *refs;
123
124   refs = live_refs;
125   live_refs = NULL;
126   if (refs)
127     {
128       g_hash_table_destroy (refs);
129     }
130 }
131
132 static gboolean atspi_inited = FALSE;
133
134 static GHashTable *app_hash = NULL;
135
136 static void
137 handle_get_bus_address (DBusPendingCall *pending, void *user_data)
138 {
139   AtspiApplication *app = user_data;
140   DBusMessage *reply = dbus_pending_call_steal_reply (pending);
141   DBusMessage *message;
142   const char *address;
143   DBusPendingCall *new_pending;
144
145   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN)
146   {
147     if (dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &address,
148                                DBUS_TYPE_INVALID))
149     {
150       DBusError error;
151       dbus_error_init (&error);
152       DBusConnection *bus = dbus_connection_open (address, &error);
153       if (bus)
154       {
155         if (app->bus)
156           dbus_connection_unref (app->bus);
157         app->bus = bus;
158       }
159     }
160   }
161   dbus_message_unref (reply);
162   dbus_pending_call_unref (pending);
163
164   message = dbus_message_new_method_call (app->bus_name,
165                                           "/org/a11y/atspi/cache",
166                                           atspi_interface_cache, "GetItems");
167
168    dbus_connection_send_with_reply (app->bus, message, &new_pending, 2000);
169   dbus_pending_call_set_notify (new_pending, handle_get_items, app, NULL);
170   dbus_message_unref (message);
171 }
172
173 static AtspiApplication *
174 get_application (const char *bus_name)
175 {
176   AtspiApplication *app = NULL;
177   char *bus_name_dup;
178   DBusMessage *message;
179   DBusError error;
180   DBusPendingCall *pending = NULL;
181
182   if (!app_hash)
183   {
184     app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_unref);
185     if (!app_hash) return NULL;
186   }
187   app = g_hash_table_lookup (app_hash, bus_name);
188   if (app) return app;
189   bus_name_dup = g_strdup (bus_name);
190   if (!bus_name_dup) return NULL;
191   // TODO: change below to something that will send state-change:defunct notification if necessary */
192   app = _atspi_application_new (bus_name);
193   if (!app) return NULL;
194   app->bus_name = bus_name_dup;
195   app->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
196   app->bus = dbus_connection_ref (_atspi_bus ());
197   g_hash_table_insert (app_hash, bus_name_dup, app);
198   dbus_error_init (&error);
199   message = dbus_message_new_method_call (bus_name, atspi_path_root,
200                                           atspi_interface_application, "GetApplicationBusAddress");
201
202    dbus_connection_send_with_reply (app->bus, message, &pending, 2000);
203   dbus_pending_call_set_notify (pending, handle_get_bus_address, app, NULL);
204   dbus_message_unref (message);
205   return app;
206 }
207
208 static AtspiAccessible *
209 ref_accessible (const char *app_name, const char *path)
210 {
211   AtspiApplication *app;
212   AtspiAccessible *a;
213
214   if (!strcmp (path, ATSPI_DBUS_PATH_NULL))
215     return NULL;
216
217   app = get_application (app_name);
218
219   if (!strcmp (path, "/org/a11y/atspi/accessible/root"))
220   {
221     if (!app->root)
222     {
223       app->root = atspi_accessible_new (app, atspi_path_root);
224       app->root->accessible_parent = atspi_get_desktop (0);
225     }
226     return g_object_ref (app->root);
227   }
228
229   a = g_hash_table_lookup (app->hash, path);
230   if (a)
231   {
232     return g_object_ref (a);
233   }
234   a = atspi_accessible_new (app, path);
235   if (!a)
236     return NULL;
237   g_hash_table_insert (app->hash, g_strdup (a->parent.path), a);
238   g_object_ref (a);     /* for the hash */
239   return a;
240 }
241
242 static AtspiHyperlink *
243 ref_hyperlink (const char *app_name, const char *path)
244 {
245   AtspiApplication *app = get_application (app_name);
246   AtspiHyperlink *hyperlink;
247
248   if (!strcmp (path, ATSPI_DBUS_PATH_NULL))
249     return NULL;
250
251   hyperlink = g_hash_table_lookup (app->hash, path);
252   if (hyperlink)
253   {
254     return g_object_ref (hyperlink);
255   }
256   hyperlink = atspi_hyperlink_new (app, path);
257   if (!hyperlink)
258     return NULL;
259   g_hash_table_insert (app->hash, g_strdup (hyperlink->parent.path), hyperlink);
260   /* TODO: This should be a weak ref */
261   g_object_ref (hyperlink);     /* for the hash */
262   return hyperlink;
263 }
264
265 typedef struct
266 {
267   char *path;
268   char *parent;
269   GArray *children;
270   GArray *interfaces;
271   char *name;
272   dbus_uint32_t role;
273   char *description;
274   GArray *state_bitflags;
275 } CACHE_ADDITION;
276
277 static DBusHandlerResult
278 handle_remove_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
279 {
280   const char *sender = dbus_message_get_sender (message);
281   AtspiApplication *app;
282   const char *path;
283   DBusMessageIter iter, iter_struct;
284   const char *signature = dbus_message_get_signature (message);
285   AtspiAccessible *a;
286
287   if (strcmp (signature, "(so)") != 0)
288   {
289     g_warning (_("AT-SPI: Unknown signature %s for RemoveAccessible"), signature);
290     return DBUS_HANDLER_RESULT_HANDLED;
291   }
292
293   dbus_message_iter_init (message, &iter);
294   dbus_message_iter_recurse (&iter, &iter_struct);
295   dbus_message_iter_get_basic (&iter_struct, &sender);
296   dbus_message_iter_next (&iter_struct);
297   dbus_message_iter_get_basic (&iter_struct, &path);
298   app = get_application (sender);
299   a = ref_accessible (sender, path);
300   if (!a)
301     return DBUS_HANDLER_RESULT_HANDLED;
302   g_object_run_dispose (G_OBJECT (a));
303   g_hash_table_remove (app->hash, a->parent.path);
304   g_object_unref (a);   /* unref our own ref */
305   return DBUS_HANDLER_RESULT_HANDLED;
306 }
307
308 static gboolean
309 add_app_to_desktop (AtspiAccessible *a, const char *bus_name)
310 {
311   DBusError error;
312
313   dbus_error_init (&error);
314   AtspiAccessible *obj = ref_accessible (bus_name, atspi_path_root);
315   if (obj)
316   {
317     GList *new_list = g_list_append (a->children, obj);
318     if (new_list)
319     {
320       a->children = new_list;
321       return TRUE;
322     }
323   }
324   else
325   {
326     g_warning (_("AT-SPI: Error calling getRoot for %s: %s"), bus_name, error.message);
327   }
328   return FALSE;
329 }
330
331 static void
332 send_children_changed (AtspiAccessible *parent, AtspiAccessible *child, gboolean add)
333 {
334   AtspiEvent e;
335
336   memset (&e, 0, sizeof (e));
337   e.type = (add? "object:children-changed:add": "object:children-changed:remove");
338   e.source = parent;
339   e.detail1 = g_list_index (parent->children, child);
340   e.detail2 = 0;
341   _atspi_send_event (&e);
342 }
343
344 static void
345 unref_object_and_descendants (AtspiAccessible *obj)
346 {
347   GList *l;
348
349   for (l = obj->children; l; l = l->next)
350   {
351     unref_object_and_descendants (l->data);
352   }
353   g_object_unref (obj);
354 }
355
356 static gboolean
357 remove_app_from_desktop (AtspiAccessible *a, const char *bus_name)
358 {
359   GList *l;
360   AtspiAccessible *child;
361
362   for (l = a->children; l; l = l->next)
363   {
364     child = l->data;
365     if (!strcmp (bus_name, child->parent.app->bus_name)) break;
366   }
367   if (!l)
368   {
369     return FALSE;
370   }
371   send_children_changed (a, child, FALSE);
372   a->children = g_list_remove (a->children, child);
373   unref_object_and_descendants (child);
374   return TRUE;
375 }
376
377 static AtspiAccessible *desktop;
378
379 void
380 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
381 {
382   DBusMessageIter iter_struct;
383
384   dbus_message_iter_recurse (iter, &iter_struct);
385   dbus_message_iter_get_basic (&iter_struct, app_name);
386   dbus_message_iter_next (&iter_struct);
387   dbus_message_iter_get_basic (&iter_struct, path);
388   dbus_message_iter_next (iter);
389 }
390
391 static void
392 add_accessible_from_iter (DBusMessageIter *iter)
393 {
394   GList *new_list;
395   DBusMessageIter iter_struct, iter_array;
396   const char *app_name, *path;
397   AtspiAccessible *accessible;
398   const char *name, *description;
399   dbus_uint32_t role;
400
401   dbus_message_iter_recurse (iter, &iter_struct);
402
403   /* get accessible */
404   get_reference_from_iter (&iter_struct, &app_name, &path);
405   accessible = ref_accessible (app_name, path);
406   if (!accessible)
407     return;
408
409   /* Get application: TODO */
410   dbus_message_iter_next (&iter_struct);
411
412   /* get parent */
413   get_reference_from_iter (&iter_struct, &app_name, &path);
414   if (accessible->accessible_parent)
415     g_object_unref (accessible->accessible_parent);
416   accessible->accessible_parent = ref_accessible (app_name, path);
417
418   /* Get children */
419   while (accessible->children)
420   {
421     g_object_unref (accessible->children->data);
422     accessible->children = g_list_remove (accessible->children, accessible->children->data);
423   }
424   dbus_message_iter_recurse (&iter_struct, &iter_array);
425   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
426   {
427     AtspiAccessible *child;
428     get_reference_from_iter (&iter_array, &app_name, &path);
429     child = ref_accessible (app_name, path);
430     new_list = g_list_append (accessible->children, child);
431     if (new_list) accessible->children = new_list;
432   }
433
434   /* interfaces */
435   dbus_message_iter_next (&iter_struct);
436   _atspi_dbus_set_interfaces (accessible, &iter_struct);
437   dbus_message_iter_next (&iter_struct);
438
439   /* name */
440   if (accessible->name)
441     g_free (accessible->name);
442   dbus_message_iter_get_basic (&iter_struct, &name);
443   accessible->name = g_strdup (name);
444   dbus_message_iter_next (&iter_struct);
445
446   /* role */
447   dbus_message_iter_get_basic (&iter_struct, &role);
448   accessible->role = role;
449   dbus_message_iter_next (&iter_struct);
450
451   /* description */
452   if (accessible->description)
453     g_free (accessible->description);
454   dbus_message_iter_get_basic (&iter_struct, &description);
455   accessible->description = g_strdup (description);
456   dbus_message_iter_next (&iter_struct);
457
458   _atspi_dbus_set_state (accessible, &iter_struct);
459   dbus_message_iter_next (&iter_struct);
460
461   accessible->cached_properties |= ATSPI_CACHE_NAME | ATSPI_CACHE_ROLE | ATSPI_CACHE_PARENT;
462   if (!atspi_state_set_contains (accessible->states,
463                                        ATSPI_STATE_MANAGES_DESCENDANTS))
464     accessible->cached_properties |= ATSPI_CACHE_CHILDREN;
465
466   /* This is a bit of a hack since the cache holds a ref, so we don't need
467    * the one provided for us anymore */
468   g_object_unref (accessible);
469 }
470
471 static void
472 handle_get_items (DBusPendingCall *pending, void *user_data)
473 {
474   DBusMessage *reply = dbus_pending_call_steal_reply (pending);
475   DBusMessageIter iter, iter_array;
476
477   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
478   {
479     const char *sender = dbus_message_get_sender (reply);
480     const char *error = NULL;
481     dbus_message_get_args (reply, NULL, DBUS_TYPE_STRING, &error,
482                            DBUS_TYPE_INVALID);
483     g_warning (_("AT-SPI: Error in GetItems, sender=%s, error=%s"), sender, error);
484     dbus_message_unref (reply);
485     dbus_pending_call_unref (pending);
486     return;
487   }
488
489   dbus_message_iter_init (reply, &iter);
490   dbus_message_iter_recurse (&iter, &iter_array);
491   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
492   {
493     add_accessible_from_iter (&iter_array);
494     dbus_message_iter_next (&iter_array);
495   }
496   dbus_message_unref (reply);
497   dbus_pending_call_unref (pending);
498 }
499
500 /* TODO: Do we stil need this function? */
501 static AtspiAccessible *
502 ref_accessible_desktop (AtspiApplication *app)
503 {
504   DBusError error;
505   DBusMessage *message, *reply;
506   DBusMessageIter iter, iter_array;
507   gchar *bus_name_dup;
508
509   if (desktop)
510   {
511     g_object_ref (desktop);
512     return desktop;
513   }
514   desktop = atspi_accessible_new (app, atspi_path_root);
515   if (!desktop)
516   {
517     return NULL;
518   }
519   g_hash_table_insert (app->hash, desktop->parent.path, desktop);
520   g_object_ref (desktop);       /* for the hash */
521   desktop->name = g_strdup ("main");
522   dbus_error_init (&error);
523   message = dbus_message_new_method_call (atspi_bus_registry,
524         atspi_path_root,
525         atspi_interface_accessible,
526         "GetChildren");
527   if (!message)
528     return NULL;
529   reply = _atspi_dbus_send_with_reply_and_block (message, NULL);
530   if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
531   {
532     g_error ("Couldn't get application list: %s", error.message);
533     if (reply)
534       dbus_message_unref (reply);
535     return NULL;
536   }
537   dbus_message_iter_init (reply, &iter);
538   dbus_message_iter_recurse (&iter, &iter_array);
539   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
540   {
541     const char *app_name, *path;
542     get_reference_from_iter (&iter_array, &app_name, &path);
543     add_app_to_desktop (desktop, app_name);
544   }
545   dbus_message_unref (reply);
546
547   /* Record the alternate name as an alias for org.a11y.atspi.Registry */
548   bus_name_dup = g_strdup (dbus_message_get_sender (reply));
549   if (bus_name_dup)
550     g_hash_table_insert (app_hash, bus_name_dup, app);
551
552   return desktop;
553 }
554
555 AtspiAccessible *
556 _atspi_ref_accessible (const char *app, const char *path)
557 {
558   AtspiApplication *a = get_application (app);
559   if (!a) return NULL;
560   if ( APP_IS_REGISTRY(a))
561   {
562     return ref_accessible_desktop (a);
563   }
564   return ref_accessible (app, path);
565 }
566
567 AtspiAccessible *
568 _atspi_dbus_return_accessible_from_message (DBusMessage *message)
569 {
570   DBusMessageIter iter;
571   AtspiAccessible *retval = NULL;
572   const char *signature;
573
574   if (!message)
575     return NULL;
576
577   signature = dbus_message_get_signature (message);
578   if (!strcmp (signature, "(so)"))
579   {
580     dbus_message_iter_init (message, &iter);
581     retval =  _atspi_dbus_return_accessible_from_iter (&iter);
582   }
583   else
584   {
585     g_warning (_("AT-SPI: Called _atspi_dbus_return_accessible_from_message with strange signature %s"), signature);
586   }
587   dbus_message_unref (message);
588   return retval;
589 }
590
591 AtspiAccessible *
592 _atspi_dbus_return_accessible_from_iter (DBusMessageIter *iter)
593 {
594   const char *app_name, *path;
595
596   get_reference_from_iter (iter, &app_name, &path);
597   return ref_accessible (app_name, path);
598 }
599
600 AtspiHyperlink *
601 _atspi_dbus_return_hyperlink_from_message (DBusMessage *message)
602 {
603   DBusMessageIter iter;
604   AtspiHyperlink *retval = NULL;
605   const char *signature = dbus_message_get_signature (message);
606    
607   if (!strcmp (signature, "(so)"))
608   {
609     dbus_message_iter_init (message, &iter);
610     retval =  _atspi_dbus_return_hyperlink_from_iter (&iter);
611   }
612   else
613   {
614     g_warning (_("AT-SPI: Called _atspi_dbus_return_hyperlink_from_message with strange signature %s"), signature);
615   }
616   dbus_message_unref (message);
617   return retval;
618 }
619
620 AtspiHyperlink *
621 _atspi_dbus_return_hyperlink_from_iter (DBusMessageIter *iter)
622 {
623   const char *app_name, *path;
624
625   get_reference_from_iter (iter, &app_name, &path);
626   return ref_hyperlink (app_name, path);
627 }
628
629 const char *cache_signal_type = "((so)(so)(so)a(so)assusau)";
630
631 static DBusHandlerResult
632 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
633 {
634   DBusMessageIter iter;
635   const char *sender = dbus_message_get_sender (message);
636
637   if (strcmp (dbus_message_get_signature (message), cache_signal_type) != 0)
638   {
639     g_warning (_("AT-SPI: AddAccessible with unknown signature %s\n"),
640                dbus_message_get_signature (message));
641     return DBUS_HANDLER_RESULT_HANDLED;
642   }
643
644   dbus_message_iter_init (message, &iter);
645   add_accessible_from_iter (&iter);
646 }
647
648 typedef struct
649 {
650   DBusConnection *bus;
651   DBusMessage *message;
652   void *data;
653 } BusDataClosure;
654
655 static guint process_deferred_messages_id = -1;
656
657 static void
658 process_deferred_message (BusDataClosure *closure)
659 {
660   int type = dbus_message_get_type (closure->message);
661   const char *interface = dbus_message_get_interface (closure->message);
662
663   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
664       !strncmp (interface, "org.a11y.atspi.Event.", 21))
665   {
666     atspi_dbus_handle_event (closure->bus, closure->message, closure->data);
667   }
668   if (dbus_message_is_method_call (closure->message, atspi_interface_device_event_listener, "NotifyEvent"))
669   {
670     atspi_dbus_handle_DeviceEvent (closure->bus,
671                                    closure->message, closure->data);
672   }
673   if (dbus_message_is_signal (closure->message, atspi_interface_cache, "AddAccessible"))
674   {
675     handle_add_accessible (closure->bus, closure->message, closure->data);
676   }
677   if (dbus_message_is_signal (closure->message, atspi_interface_cache, "RemoveAccessible"))
678   {
679     handle_remove_accessible (closure->bus, closure->message, closure->data);
680   }
681 }
682
683 static GList *deferred_messages = NULL;
684
685 gboolean
686 _atspi_process_deferred_messages (gpointer data)
687 {
688   static int in_process_deferred_messages = 0;
689
690   if (in_process_deferred_messages)
691     return TRUE;
692   in_process_deferred_messages = 1;
693   while (deferred_messages != NULL)
694   {
695     BusDataClosure *closure = deferred_messages->data;
696     process_deferred_message (closure);
697     deferred_messages = g_list_remove (deferred_messages, closure);
698     dbus_message_unref (closure->message);
699     dbus_connection_unref (closure->bus);
700     g_free (closure);
701   }
702   /* If data is NULL, assume that we were called from GLib */
703   if (!data)
704     process_deferred_messages_id = -1;
705   in_process_deferred_messages = 0;
706   return FALSE;
707 }
708
709 static DBusHandlerResult
710 defer_message (DBusConnection *connection, DBusMessage *message, void *user_data)
711 {
712   BusDataClosure *closure = g_new (BusDataClosure, 1);
713   GList *new_list;
714
715   if (!closure)
716     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
717   closure->bus = dbus_connection_ref (bus);
718   closure->message = dbus_message_ref (message);
719   closure->data = user_data;
720
721   new_list = g_list_append (deferred_messages, closure);
722   if (new_list)
723     deferred_messages = new_list;
724
725   if (process_deferred_messages_id == -1)
726     process_deferred_messages_id = g_idle_add (_atspi_process_deferred_messages, NULL);
727   return DBUS_HANDLER_RESULT_HANDLED;
728 }
729
730 static DBusHandlerResult
731 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
732 {
733   int type = dbus_message_get_type (message);
734   const char *interface = dbus_message_get_interface (message);
735
736   if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
737       !strncmp (interface, "org.a11y.atspi.Event.", 21))
738   {
739     return defer_message (bus, message, data);
740   }
741   if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "NotifyEvent"))
742   {
743     return defer_message (bus, message, data);
744   }
745   if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
746   {
747     return defer_message (bus, message, data);
748   }
749   if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
750   {
751     return defer_message (bus, message, data);
752   }
753   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
754 }
755
756 static const char *signal_interfaces[] =
757 {
758   "org.a11y.atspi.Event.Object",
759   "org.a11y.atspi.Event.Window",
760   "org.a11y.atspi.Event.Mouse",
761   "org.a11y.atspi.Event.Terminal",
762   "org.a11y.atspi.Event.Document",
763   "org.a11y.atspi.Event.Focus",
764   NULL
765 };
766
767 /*
768  * Returns a 'canonicalized' value for DISPLAY,
769  * with the screen number stripped off if present.
770  *
771  * TODO: Avoid having duplicate functions for this here and in at-spi2-atk
772  */
773 static const gchar *
774 spi_display_name (void)
775 {
776   static const char *canonical_display_name = NULL;
777   if (!canonical_display_name)
778     {
779       const gchar *display_env = g_getenv ("AT_SPI_DISPLAY");
780       if (!display_env)
781         {
782           display_env = g_getenv ("DISPLAY");
783           if (!display_env || !display_env[0])
784             canonical_display_name = ":0";
785           else
786             {
787               gchar *display_p, *screen_p;
788               canonical_display_name = g_strdup (display_env);
789               display_p = g_utf8_strrchr (canonical_display_name, -1, ':');
790               screen_p = g_utf8_strrchr (canonical_display_name, -1, '.');
791               if (screen_p && display_p && (screen_p > display_p))
792                 {
793                   *screen_p = '\0';
794                 }
795             }
796         }
797       else
798         {
799           canonical_display_name = display_env;
800         }
801     }
802   return canonical_display_name;
803 }
804
805 /* TODO: Avoid having duplicate functions for this here and in at-spi2-atk */
806 static DBusConnection *
807 get_accessibility_bus ()
808 {
809   Atom AT_SPI_BUS;
810   Atom actual_type;
811   Display *bridge_display;
812   int actual_format;
813   unsigned char *data = NULL;
814   unsigned long nitems;
815   unsigned long leftover;
816
817   DBusConnection *bus = NULL;
818   DBusError error;
819
820   bridge_display = XOpenDisplay (spi_display_name ());
821   if (!bridge_display)
822     {
823       g_warning (_("AT-SPI: Could not get the display\n"));
824       return NULL;
825     }
826
827   AT_SPI_BUS = XInternAtom (bridge_display, "AT_SPI_BUS", False);
828   XGetWindowProperty (bridge_display,
829                       XDefaultRootWindow (bridge_display),
830                       AT_SPI_BUS, 0L,
831                       (long) BUFSIZ, False,
832                       (Atom) 31, &actual_type, &actual_format,
833                       &nitems, &leftover, &data);
834   XCloseDisplay (bridge_display);
835
836   dbus_error_init (&error);
837
838   if (data == NULL)
839     {
840       g_warning
841         (_("AT-SPI: Accessibility bus not found - Using session bus.\n"));
842       bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
843       if (!bus)
844         {
845           g_warning (_("AT-SPI: Couldn't connect to bus: %s\n"), error.message);
846           return NULL;
847         }
848     }
849   else
850     {
851       bus = dbus_connection_open (data, &error);
852       if (!bus)
853         {
854           g_warning (_("AT-SPI: Couldn't connect to bus: %s\n"), error.message);
855           return NULL;
856         }
857       else
858         {
859           if (!dbus_bus_register (bus, &error))
860             {
861               g_warning (_("AT-SPI: Couldn't register with bus: %s\n"), error.message);
862               return NULL;
863             }
864         }
865     }
866
867   return bus;
868 }
869
870 /**
871  * atspi_init:
872  *
873  * Connects to the accessibility registry and initializes the SPI.
874  *
875  * Returns: 0 on success, otherwise an integer error code.  
876  **/
877 int
878 atspi_init (void)
879 {
880   DBusError error;
881   char *match;
882
883   if (atspi_inited)
884     {
885       return 1;
886     }
887
888   atspi_inited = TRUE;
889
890   g_type_init ();
891
892   get_live_refs();
893
894   dbus_error_init (&error);
895   bus = get_accessibility_bus ();
896   if (!bus)
897   {
898     g_error ("Couldn't get session bus");
899     return 2;
900   }
901   dbus_bus_register (bus, &error);
902   dbus_connection_setup_with_g_main(bus, g_main_context_default());
903   dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
904   dbind_set_timeout (1000);
905   match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
906   dbus_error_init (&error);
907   dbus_bus_add_match (bus, match, &error);
908   g_free (match);
909   match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
910   dbus_bus_add_match (bus, match, &error);
911   g_free (match);
912   match = g_strdup_printf ("type='signal',interface='%s',member='ChildrenChanged'", atspi_interface_event_object);
913   dbus_bus_add_match (bus, match, &error);
914   g_free (match);
915   match = g_strdup_printf ("type='signal',interface='%s',member='PropertyChange'", atspi_interface_event_object);
916   dbus_bus_add_match (bus, match, &error);
917   g_free (match);
918   match = g_strdup_printf ("type='signal',interface='%s',member='StateChanged'", atspi_interface_event_object);
919   dbus_bus_add_match (bus, match, &error);
920   g_free (match);
921   return 0;
922 }
923
924   static GMainLoop *mainloop;
925
926 /**
927  * atspi_event_main:
928  *
929  * Starts/enters the main event loop for the AT-SPI services.
930  *
931  * (NOTE: This method does not return control, it is exited via a call to
932  *  atspi_event_quit () from within an event handler).
933  *
934  **/
935 void
936 atspi_event_main (void)
937 {
938   mainloop = g_main_loop_new (NULL, FALSE);
939   g_main_loop_run (mainloop);
940 }
941
942 /**
943  * atspi_event_quit:
944  *
945  * Quits the last main event loop for the SPI services,
946  * see atspi_event_main
947  **/
948 void
949 atspi_event_quit (void)
950 {
951   g_main_loop_quit (mainloop);
952 }
953
954 /**
955  * atspi_exit:
956  *
957  * Disconnects from the Accessibility Registry and releases 
958  * any floating resources. Call only once at exit.
959  *
960  * Returns: 0 if there were no leaks, otherwise non zero.
961  **/
962 int
963 atspi_exit (void)
964 {
965   int leaked;
966
967   if (!atspi_inited)
968     {
969       return 0;
970     }
971
972   atspi_inited = FALSE;
973
974   if (live_refs)
975     {
976       leaked = g_hash_table_size (live_refs);
977     }
978   else
979     {
980       leaked = 0;
981     }
982
983   cleanup ();
984
985   return leaked;
986 }
987
988 dbus_bool_t
989 _atspi_dbus_call (gpointer obj, const char *interface, const char *method, GError **error, const char *type, ...)
990 {
991   va_list args;
992   dbus_bool_t retval;
993   DBusError err;
994   AtspiObject *aobj = ATSPI_OBJECT (obj);
995
996   if (!aobj->app || !aobj->app->bus)
997   {
998     g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_APPLICATION_GONE,
999                           _("The application no longer exists"));
1000     return FALSE;
1001   }
1002
1003   va_start (args, type);
1004   dbus_error_init (&err);
1005   retval = dbind_method_call_reentrant_va (aobj->app->bus, aobj->app->bus_name,
1006                                            aobj->path, interface, method, &err,
1007                                            type, args);
1008   va_end (args);
1009   _atspi_process_deferred_messages ((gpointer)TRUE);
1010   if (dbus_error_is_set (&err))
1011   {
1012     /* TODO: Set gerror */
1013     dbus_error_free (&err);
1014   }
1015   return retval;
1016 }
1017
1018 DBusMessage *
1019 _atspi_dbus_call_partial (gpointer obj,
1020                           const char *interface,
1021                           const char *method,
1022                           GError **error,
1023                           const char *type, ...)
1024 {
1025   va_list args;
1026
1027   va_start (args, type);
1028   return _atspi_dbus_call_partial_va (obj, interface, method, error, type, args);
1029 }
1030
1031 DBusMessage *
1032 _atspi_dbus_call_partial_va (gpointer obj,
1033                           const char *interface,
1034                           const char *method,
1035                           GError **error,
1036                           const char *type,
1037                           va_list args)
1038 {
1039   AtspiObject *aobj = ATSPI_OBJECT (obj);
1040   DBusError err;
1041     DBusMessage *msg = NULL, *reply = NULL;
1042     DBusMessageIter iter;
1043     const char *p;
1044
1045   dbus_error_init (&err);
1046
1047   if (!aobj->app || !aobj->app->bus)
1048   {
1049     g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_APPLICATION_GONE,
1050                           _("The application no longer exists"));
1051     goto out;
1052   }
1053
1054     msg = dbus_message_new_method_call (aobj->app->bus_name, aobj->path, interface, method);
1055     if (!msg)
1056         goto out;
1057
1058     p = type;
1059     dbus_message_iter_init_append (msg, &iter);
1060     dbind_any_marshal_va (&iter, &p, args);
1061
1062     reply = dbind_send_and_allow_reentry (aobj->app->bus, msg, &err);
1063 out:
1064   va_end (args);
1065   if (msg)
1066     dbus_message_unref (msg);
1067   _atspi_process_deferred_messages ((gpointer)TRUE);
1068   if (dbus_error_is_set (&err))
1069   {
1070     /* TODO: Set gerror */
1071     dbus_error_free (&err);
1072   }
1073   return reply;
1074 }
1075
1076 dbus_bool_t
1077 _atspi_dbus_get_property (gpointer obj, const char *interface, const char *name, GError **error, const char *type, void *data)
1078 {
1079   DBusMessage *message, *reply;
1080   DBusMessageIter iter, iter_variant;
1081   DBusError err;
1082   dbus_bool_t retval = FALSE;
1083   AtspiObject *aobj = ATSPI_OBJECT (obj);
1084
1085   if (!aobj)
1086     return FALSE;
1087
1088   if (!aobj->app || !aobj->app->bus)
1089   {
1090     g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_APPLICATION_GONE,
1091                           _("The application no longer exists"));
1092     return FALSE;
1093   }
1094
1095   message = dbus_message_new_method_call (aobj->app->bus_name,
1096                                           aobj->path,
1097                                           "org.freedesktop.DBus.Properties",
1098                                           "Get");
1099   if (!message)
1100   {
1101     // TODO: throw exception
1102     return FALSE;
1103   }
1104   dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
1105   dbus_error_init (&err);
1106   reply = dbind_send_and_allow_reentry (aobj->app->bus, message, &err);
1107   dbus_message_unref (message);
1108   _atspi_process_deferred_messages ((gpointer)TRUE);
1109   if (!reply)
1110   {
1111     // TODO: throw exception
1112     goto done;
1113   }
1114
1115   if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR)
1116   {
1117     const char *err;
1118     dbus_message_get_args (message, NULL, DBUS_TYPE_STRING, &err, DBUS_TYPE_INVALID);
1119     if (err)
1120       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC, err);
1121     goto done;
1122   }
1123
1124   dbus_message_iter_init (reply, &iter);
1125   if (dbus_message_iter_get_arg_type (&iter) != 'v')
1126   {
1127     g_warning (_("AT-SPI: expected a variant when fetching %s from interface %s; got %s\n"), name, interface, dbus_message_get_signature (reply));
1128     goto done;
1129   }
1130   dbus_message_iter_recurse (&iter, &iter_variant);
1131   if (dbus_message_iter_get_arg_type (&iter_variant) != type[0])
1132   {
1133     g_warning (_("atspi_dbus_get_property: Wrong type: expected %s, got %c\n"), type, dbus_message_iter_get_arg_type (&iter_variant));
1134     goto done;
1135   }
1136   if (!strcmp (type, "(so)"))
1137   {
1138     *((AtspiAccessible **)data) = _atspi_dbus_return_accessible_from_iter (&iter_variant);
1139   }
1140   else
1141   {
1142     dbus_message_iter_get_basic (&iter_variant, data);
1143     if (type [0] == 's')
1144       *(char **)data = g_strdup (*(char **)data);
1145   }
1146   retval = TRUE;
1147 done:
1148   if (reply)
1149     dbus_message_unref (reply);
1150   return retval;
1151 }
1152
1153 DBusMessage *
1154 _atspi_dbus_send_with_reply_and_block (DBusMessage *message, GError **error)
1155 {
1156   DBusMessage *reply;
1157   DBusError err;
1158   AtspiApplication *app;
1159   DBusConnection *bus;
1160
1161   app = get_application (dbus_message_get_destination (message));
1162
1163   if (app && !app->bus)
1164     return NULL;        /* will fail anyway; app has been disposed */
1165
1166   bus = (app ? app->bus : _atspi_bus());
1167   dbus_error_init (&err);
1168   reply = dbind_send_and_allow_reentry (bus, message, &err);
1169   _atspi_process_deferred_messages ((gpointer)TRUE);
1170   dbus_message_unref (message);
1171   if (err.message)
1172   {
1173     if (error)
1174       g_set_error_literal (error, ATSPI_ERROR, ATSPI_ERROR_IPC, err.message);
1175     dbus_error_free (&err);
1176   }
1177   return reply;
1178 }
1179
1180 GHashTable *
1181 _atspi_dbus_return_hash_from_message (DBusMessage *message)
1182 {
1183   DBusMessageIter iter;
1184   GHashTable *ret;
1185
1186   if (!message)
1187     return NULL;
1188
1189   _ATSPI_DBUS_CHECK_SIG (message, "a{ss}", NULL, NULL);
1190
1191   dbus_message_iter_init (message, &iter);
1192   ret = _atspi_dbus_hash_from_iter (&iter);
1193   dbus_message_unref (message);
1194   return ret;
1195 }
1196
1197 GHashTable *
1198 _atspi_dbus_hash_from_iter (DBusMessageIter *iter)
1199 {
1200   GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
1201   DBusMessageIter iter_array, iter_dict;
1202
1203   dbus_message_iter_recurse (iter, &iter_array);
1204   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1205   {
1206     const char *name, *value;
1207     dbus_message_iter_recurse (&iter_array, &iter_dict);
1208     dbus_message_iter_get_basic (&iter_dict, &name);
1209     dbus_message_iter_next (&iter_dict);
1210     dbus_message_iter_get_basic (&iter_dict, &value);
1211     g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
1212     dbus_message_iter_next (&iter_array);
1213   }
1214   return hash;
1215 }
1216
1217 GArray *
1218 _atspi_dbus_return_attribute_array_from_message (DBusMessage *message)
1219 {
1220   DBusMessageIter iter;
1221   GArray *ret;
1222
1223   if (!message)
1224     return NULL;
1225
1226   _ATSPI_DBUS_CHECK_SIG (message, "a{ss}", NULL, NULL);
1227
1228   dbus_message_iter_init (message, &iter);
1229
1230   ret = _atspi_dbus_attribute_array_from_iter (&iter);
1231   dbus_message_unref (message);
1232   return ret;
1233 }
1234
1235 GArray *
1236 _atspi_dbus_attribute_array_from_iter (DBusMessageIter *iter)
1237 {
1238   DBusMessageIter iter_array, iter_dict;
1239   GArray *array = g_array_new (TRUE, TRUE, sizeof (gchar *));
1240
1241   dbus_message_iter_recurse (iter, &iter_array);
1242   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1243   {
1244     const char *name, *value;
1245     gchar *str;
1246     GArray *new_array;
1247     dbus_message_iter_recurse (&iter_array, &iter_dict);
1248     dbus_message_iter_get_basic (&iter_dict, &name);
1249     dbus_message_iter_next (&iter_dict);
1250     dbus_message_iter_get_basic (&iter_dict, &value);
1251     str = g_strdup_printf ("%s:%s", name, value);
1252     new_array = g_array_append_val (array, str);
1253     if (new_array)
1254       array = new_array;
1255     dbus_message_iter_next (&iter_array);;
1256   }
1257   return array;
1258 }
1259
1260 void
1261 _atspi_dbus_set_interfaces (AtspiAccessible *accessible, DBusMessageIter *iter)
1262 {
1263   DBusMessageIter iter_array;
1264
1265   accessible->interfaces = 0;
1266   dbus_message_iter_recurse (iter, &iter_array);
1267   while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
1268   {
1269     const char *iface;
1270     gint n;
1271     dbus_message_iter_get_basic (&iter_array, &iface);
1272     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
1273     n = _atspi_get_iface_num (iface);
1274     if (n == -1)
1275     {
1276       g_warning (_("AT-SPI: Unknown interface %s"), iface);
1277     }
1278     else
1279       accessible->interfaces |= (1 << n);
1280     dbus_message_iter_next (&iter_array);
1281   }
1282   accessible->cached_properties |= ATSPI_CACHE_INTERFACES;
1283 }
1284
1285 void
1286 _atspi_dbus_set_state (AtspiAccessible *accessible, DBusMessageIter *iter)
1287 {
1288   DBusMessageIter iter_array;
1289   gint count;
1290   dbus_uint32_t *states;
1291
1292   dbus_message_iter_recurse (iter, &iter_array);
1293   dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
1294   if (count != 2)
1295   {
1296     g_warning (_("AT-SPI: expected 2 values in states array; got %d\n"), count);
1297     if (!accessible->states)
1298       accessible->states = _atspi_state_set_new_internal (accessible, 0);
1299   }
1300   else
1301   {
1302     guint64 val = ((guint64)states [1]) << 32;
1303     val += states [0];
1304     if (!accessible->states)
1305       accessible->states = _atspi_state_set_new_internal (accessible, val);
1306     else
1307       accessible->states->states = val;
1308   }
1309   accessible->cached_properties |= ATSPI_CACHE_STATES;
1310 }
1311
1312 GQuark
1313 atspi_error_quark (void)
1314 {
1315   return g_quark_from_static_string ("atspi_error");
1316 }
1317