2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2001, 2002 Sun Microsystems Inc.,
6 * Copyright 2001, 2002 Ximian, Inc.
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.
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.
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.
26 * Basic SPI initialization and event loop function prototypes
30 #include "atspi-private.h"
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;
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;
60 static const char *interfaces[] =
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,
81 _atspi_get_iface_num (const char *iface)
83 /* TODO: Use a binary search or hash to improve performance */
86 for (i = 0; interfaces[i]; i++)
88 if (!strcmp(iface, interfaces[i])) return i;
98 live_refs = g_hash_table_new (g_direct_hash, g_direct_equal);
103 /* TODO: Add an application parameter */
112 #define APP_IS_REGISTRY(app) (!strcmp (app->bus_name, atspi_bus_registry))
123 g_hash_table_destroy (refs);
127 static gboolean atspi_inited = FALSE;
129 static GHashTable *app_hash = NULL;
131 static AtspiApplication *
132 get_application (const char *bus_name)
134 AtspiApplication *app = NULL;
139 app_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_hash_table_unref);
140 if (!app_hash) return NULL;
142 app = g_hash_table_lookup (app_hash, bus_name);
144 bus_name_dup = g_strdup (bus_name);
145 if (!bus_name_dup) return NULL;
146 // TODO: change below to something that will send state-change:defunct notification if necessary */
147 app = _atspi_application_new (bus_name);
148 if (!app) return NULL;
149 app->bus_name = bus_name_dup;
150 if (APP_IS_REGISTRY (app))
152 app->hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
156 app->hash = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_object_unref);
158 g_hash_table_insert (app_hash, bus_name_dup, app);
162 static AtspiAccessible *
163 ref_accessible (const char *app_name, const char *path)
165 AtspiApplication *app = get_application (app_name);
168 if (!strcmp (path, "/org/a11y/atspi/accessible/root"))
172 app->root = atspi_accessible_new (app, atspi_path_root);
173 app->root->accessible_parent = atspi_get_desktop (0);
175 return g_object_ref (app->root);
178 a = g_hash_table_lookup (app->hash, path);
184 a = atspi_accessible_new (app, path);
187 g_hash_table_insert (app->hash, a->path, a);
188 g_object_ref (a); /* for the hash */
201 GArray *state_bitflags;
204 static DBusHandlerResult
205 handle_remove_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
207 const char *sender = dbus_message_get_sender (message);
208 AtspiApplication *app;
210 DBusMessageIter iter, iter_struct;
211 const char *signature = dbus_message_get_signature (message);
215 if (strcmp (signature, "(so)") != 0)
217 g_warning ("at-spi: Unknown signature %s for RemoveAccessible", signature);
218 return DBUS_HANDLER_RESULT_HANDLED;
221 dbus_message_iter_init (message, &iter);
222 dbus_message_iter_recurse (&iter, &iter_struct);
223 dbus_message_iter_get_basic (&iter_struct, &sender);
224 dbus_message_iter_get_basic (&iter_struct, &path);
225 app = get_application (sender);
226 a = ref_accessible (sender, path);
227 if (a->accessible_parent && g_list_find (a->accessible_parent->children, a))
229 a->accessible_parent->children = g_list_remove (a->accessible_parent->children, a);
232 g_hash_table_remove (app->hash, app->bus_name);
233 g_object_unref (a); /* unref our own ref */
234 return DBUS_HANDLER_RESULT_HANDLED;
238 add_app_to_desktop (AtspiAccessible *a, const char *bus_name)
243 dbus_error_init (&error);
244 AtspiAccessible *obj = ref_accessible (bus_name, atspi_path_root);
247 GList *new_list = g_list_append (a->children, obj);
250 a->children = new_list;
256 g_warning ("Error calling getRoot for %s: %s", bus_name, error.message);
262 send_children_changed (AtspiAccessible *parent, AtspiAccessible *child, gboolean add)
266 memset (&e, 0, sizeof(e));
267 e.type = (add? "object:children-changed:add": "object:children-changed:remove");
269 e.detail1 = g_list_index (parent->children, child);
271 g_warning ("atspi: TODO: Finish events");
272 atspi_dispatch_event (&e);
277 unref_object_and_descendants (AtspiAccessible *obj)
281 for (l = obj->children; l; l = l->next)
283 unref_object_and_descendants (l->data);
285 g_object_unref (obj);
289 remove_app_from_desktop (AtspiAccessible *a, const char *bus_name)
292 AtspiAccessible *child;
294 for (l = a->children; l; l = l->next)
297 if (!strcmp (bus_name, child->app->bus_name)) break;
301 g_warning ("Removing unregistered app %s; doing nothing\n", bus_name);
304 send_children_changed (a, child, FALSE);
305 a->children = g_list_remove (a->children, child);
306 unref_object_and_descendants (child);
310 static AtspiAccessible *desktop;
313 get_reference_from_iter (DBusMessageIter *iter, const char **app_name, const char **path)
315 DBusMessageIter iter_struct;
317 dbus_message_iter_recurse (iter, &iter_struct);
318 dbus_message_iter_get_basic (&iter_struct, app_name);
319 dbus_message_iter_next (&iter_struct);
320 dbus_message_iter_get_basic (&iter_struct, path);
321 dbus_message_iter_next (iter);
325 add_accessible_from_iter (DBusMessageIter *iter)
329 DBusMessageIter iter_struct, iter_array;
330 const char *app_name, *path;
331 AtspiApplication *app;
332 AtspiAccessible *accessible;
333 const char *name, *description;
335 dbus_uint32_t *states;
338 dbus_message_iter_recurse (iter, &iter_struct);
341 get_reference_from_iter (&iter_struct, &app_name, &path);
342 accessible = ref_accessible (app_name, path);
344 /* Get application: TODO */
345 dbus_message_iter_next (&iter_struct);
348 get_reference_from_iter (&iter_struct, &app_name, &path);
349 accessible->accessible_parent = ref_accessible (app_name, path);
352 dbus_message_iter_recurse (&iter_struct, &iter_array);
353 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
355 AtspiAccessible *child;
356 get_reference_from_iter (&iter_array, &app_name, &path);
357 child = ref_accessible (app_name, path);
358 new_list = g_list_append (accessible->children, child);
359 if (new_list) accessible->children = new_list;
363 accessible->interfaces = 0;
364 dbus_message_iter_next (&iter_struct);
365 dbus_message_iter_recurse (&iter_struct, &iter_array);
366 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
370 dbus_message_iter_get_basic (&iter_array, &iface);
371 if (!strcmp (iface, "org.freedesktop.DBus.Introspectable")) continue;
372 n = _atspi_get_iface_num (iface);
375 g_warning ("Unknown interface %s", iface);
377 else accessible->interfaces |= (1 << n);
378 dbus_message_iter_next (&iter_array);
380 dbus_message_iter_next (&iter_struct);
383 dbus_message_iter_get_basic (&iter_struct, &name);
384 accessible->name = g_strdup (name);
385 dbus_message_iter_next (&iter_struct);
388 dbus_message_iter_get_basic (&iter_struct, &role);
389 accessible->role = role;
390 dbus_message_iter_next (&iter_struct);
393 dbus_message_iter_get_basic (&iter_struct, &description);
394 accessible->description = g_strdup (description);
395 dbus_message_iter_next (&iter_struct);
397 dbus_message_iter_recurse (&iter_struct, &iter_array);
398 dbus_message_iter_get_fixed_array (&iter_array, &states, &count);
401 g_warning ("at-spi: expected 2 values in states array; got %d\n", count);
402 accessible->states = atspi_state_set_new (0);
406 guint64 val = ((guint64)states [1]) << 32;
408 accessible->states = atspi_state_set_new (val);
410 dbus_message_iter_next (&iter_struct);
412 /* This is a bit of a hack since the cache holds a ref, so we don't need
413 * the one provided for us anymore */
414 g_object_unref (accessible);
418 add_accessibles (const char *app_name)
421 DBusMessage *message, *reply;
422 DBusMessageIter iter, iter_array;
424 AtspiApplication *app = get_application (app_name);
425 /* TODO: Move this functionality into app initializer? */
426 dbus_error_init (&error);
427 message = dbus_message_new_method_call (app_name, "/org/a11y/atspi/cache", atspi_interface_cache, "GetItems");
428 reply = _atspi_dbus_send_with_reply_and_block (message);
429 if (!reply || strcmp (dbus_message_get_signature (reply), "a((so)(so)(so)a(so)assusau)") != 0)
431 g_warning ("at-spi: Error in GetItems");
434 dbus_message_unref (reply);
436 dbus_message_iter_init (reply, &iter);
437 dbus_message_iter_recurse (&iter, &iter_array);
438 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
440 add_accessible_from_iter (&iter_array);
441 dbus_message_iter_next (&iter_array);
443 dbus_message_unref (reply);
446 /* TODO: Do we stil need this function? */
447 static AtspiAccessible *
448 ref_accessible_desktop (AtspiApplication *app)
454 DBusMessage *message, *reply;
455 DBusMessageIter iter, iter_array;
459 g_object_ref (desktop);
462 desktop = atspi_accessible_new (app, atspi_path_root);
467 g_hash_table_insert (app->hash, desktop->path, desktop);
468 g_object_ref (desktop); /* for the hash */
469 desktop->name = g_strdup ("main");
470 dbus_error_init (&error);
471 message = dbus_message_new_method_call (atspi_bus_registry,
473 atspi_interface_accessible,
477 reply = _atspi_dbus_send_with_reply_and_block (message);
478 if (!reply || strcmp (dbus_message_get_signature (reply), "a(so)") != 0)
480 g_error ("Couldn't get application list: %s", error.message);
482 dbus_message_unref (reply);
485 dbus_message_iter_init (reply, &iter);
486 dbus_message_iter_recurse (&iter, &iter_array);
487 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
489 const char *app_name, *path;
490 get_reference_from_iter (&iter_array, &app_name, &path);
491 add_accessibles (app_name);
492 add_app_to_desktop (desktop, app_name);
494 dbus_message_unref (reply);
499 _atspi_ref_accessible (const char *app, const char *path)
501 AtspiApplication *a = get_application (app);
503 if ( APP_IS_REGISTRY(a))
505 return ref_accessible_desktop (a);
507 return ref_accessible (app, path);
511 _atspi_dbus_return_accessible_from_message (DBusMessage *message)
513 DBusMessageIter iter;
514 const char *app_name, *path;
515 AtspiAccessible *retval = NULL;
516 const char *signature = dbus_message_get_signature (message);
518 if (!strcmp (signature, "(so)"))
520 dbus_message_iter_init (message, &iter);
521 get_reference_from_iter (&iter, &app_name, &path);
522 retval = _atspi_ref_accessible (app_name, path);
526 g_warning ("Atspi: Called __atspi_dbus_return_accessible_from_message with strange signature %s", signature);
528 dbus_message_unref (message);
532 /* TODO: Remove this function. We should not need it anymore.
533 * If we do, it's a bug.
536 _atspi_ref_related_accessible (AtspiAccessible *obj, const AtspiReference *ref)
538 const char *app = (ref->name && ref->name[0]? ref->name: obj->app->bus_name);
539 return ref_accessible (app, obj->path);
542 const char *cache_signal_type = "((so)(so)a(so)assusau)";
544 static DBusHandlerResult
545 handle_add_accessible (DBusConnection *bus, DBusMessage *message, void *user_data)
547 DBusMessageIter iter;
548 const char *sender = dbus_message_get_sender (message);
549 AtspiApplication *app = get_application (sender);
550 const char *type = cache_signal_type;
552 if (strcmp (dbus_message_get_signature (message), cache_signal_type) != 0)
554 g_warning ("atspi: AddAccessible with unknown signature %s\n", dbus_message_get_signature (message));
558 dbus_message_iter_init (message, &iter);
559 add_accessible_from_iter (&iter);
562 static DBusHandlerResult
563 atspi_dbus_filter (DBusConnection *bus, DBusMessage *message, void *data)
565 int type = dbus_message_get_type (message);
566 const char *interface = dbus_message_get_interface (message);
567 const char *member = dbus_message_get_member (message);
571 if (type == DBUS_MESSAGE_TYPE_SIGNAL &&
572 !strncmp (interface, "org.a11y.atspi.Event.", 28))
574 g_warning ("atspi: TODO: event");
575 //return handle_event (bus, message, data);
577 if (dbus_message_is_method_call (message, atspi_interface_device_event_listener, "notifyEvent"))
579 g_warning ("atspi: TODO: DeviceEvent");
580 //return handle_device_event (bus, message, data);
582 if (dbus_message_is_signal (message, atspi_interface_cache, "AddAccessible"))
584 return handle_add_accessible (bus, message, data);
586 if (dbus_message_is_signal (message, atspi_interface_cache, "RemoveAccessible"))
588 return handle_remove_accessible (bus, message, data);
590 /* TODO: Handle ChildrenChanged, StateChanged, PropertyChanged */
591 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
594 static const char *signal_interfaces[] =
596 "org.a11y.atspi.Event.Object",
597 "org.a11y.atspi.Event.Window",
598 "org.a11y.atspi.Event.Mouse",
599 "org.a11y.atspi.Event.Terminal",
600 "org.a11y.atspi.Event.Document",
601 "org.a11y.atspi.Event.Focus",
608 * Connects to the accessibility registry and initializes the SPI.
610 * Returns: 0 on success, otherwise an integer error code.
631 dbus_error_init (&error);
632 bus = dbus_bus_get (DBUS_BUS_SESSION, &error);
635 g_error ("Couldn't get session bus");
638 dbus_bus_register (bus, &error);
639 dbus_connection_setup_with_g_main(bus, g_main_context_default());
640 dbus_connection_add_filter (bus, atspi_dbus_filter, NULL, NULL);
641 match = g_strdup_printf ("type='signal',interface='%s',member='AddAccessible'", atspi_interface_cache);
642 dbus_error_init (&error);
643 dbus_bus_add_match (bus, match, &error);
645 match = g_strdup_printf ("type='signal',interface='%s',member='RemoveAccessible'", atspi_interface_cache);
646 dbus_bus_add_match (bus, match, &error);
648 for (i = 0; signal_interfaces[i]; i++)
650 match = g_strdup_printf ("type='signal',interface='%s'", signal_interfaces[i]);
651 dbus_bus_add_match (bus, match, &error);
657 static GMainLoop *mainloop;
662 * Starts/enters the main event loop for the AT-SPI services.
664 * (NOTE: This method does not return control, it is exited via a call to
665 * atspi_event_quit () from within an event handler).
669 atspi_event_main (void)
671 mainloop = g_main_loop_new (NULL, FALSE);
672 g_main_loop_run (mainloop);
678 * Quits the last main event loop for the SPI services,
679 * see atspi_event_main
682 atspi_event_quit (void)
684 g_main_loop_quit (mainloop);
690 * Disconnects from the Accessibility Registry and releases
691 * any floating resources. Call only once at exit.
693 * Returns: 0 if there were no leaks, otherwise non zero.
705 atspi_inited = FALSE;
709 leaked = g_hash_table_size (live_refs);
722 _atspi_dbus_call (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
728 dbus_error_init (&err);
729 va_start (args, type);
730 retval = dbind_method_call_reentrant_va (_atspi_bus(), obj->app->bus_name, obj->path, interface, method, &err, type, args);
732 if (dbus_error_is_set (&err))
734 /* TODO: Set gerror */
735 dbus_error_free (&err);
741 _atspi_dbus_call_partial (AtspiAccessible *obj, const char *interface, const char *method, GError **error, const char *type, ...)
746 DBusMessage *msg = NULL, *reply = NULL;
747 DBusMessageIter iter;
750 dbus_error_init (&err);
751 va_start (args, type);
753 msg = dbus_message_new_method_call (obj->app->bus_name, obj->path, interface, method);
758 dbus_message_iter_init_append (msg, &iter);
759 dbind_any_marshal_va (&iter, &p, args);
761 reply = dbind_send_and_allow_reentry (_atspi_bus(), msg, &err);
764 if (dbus_error_is_set (&err))
766 /* TODO: Set gerror */
767 dbus_error_free (&err);
773 _atspi_dbus_get_property (AtspiAccessible *obj, const char *interface, const char *name, GError **error, const char *type, void *data)
775 DBusMessage *message, *reply;
776 DBusMessageIter iter, iter_variant;
778 dbus_bool_t retval = FALSE;
780 message = dbus_message_new_method_call (obj->app->bus_name, obj->path, "org.freedesktop.DBus.Properties", "Get");
783 // TODO: throw exception
786 dbus_message_append_args (message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
787 dbus_error_init (&err);
788 reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
789 dbus_message_unref (message);
792 // TODO: throw exception
795 dbus_message_iter_init (reply, &iter);
796 dbus_message_iter_recurse (&iter, &iter_variant);
797 if (dbus_message_iter_get_arg_type (&iter_variant) != type[0])
799 g_warning ("atspi_dbus_get_property: Wrong type: expected %s, got %c\n", type, dbus_message_iter_get_arg_type (&iter_variant));
802 dbus_message_iter_get_basic (&iter_variant, data);
803 dbus_message_unref (reply);
804 if (type[0] == 's') *(char **)data = g_strdup (*(char **)data);
811 _atspi_dbus_send_with_reply_and_block (DBusMessage *message)
816 dbus_error_init (&err);
817 g_warning ("TODO: Write _atspi_dbus_send_with_reply_and_block");
818 reply = dbus_connection_send_with_reply_and_block (_atspi_bus(), message, 1000, &err);
819 dbus_message_unref (message);
824 _atspi_dbus_hash_from_message (DBusMessage *message)
826 GHashTable *hash = g_hash_table_new (g_str_hash, g_str_equal);
827 DBusMessageIter iter, iter_array, iter_dict;
828 const char *signature;
830 signature = dbus_message_get_signature (message);
832 if (strcmp (signature, "a{ss}") != 0)
834 g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
838 dbus_message_iter_init (message, &iter);
839 dbus_message_iter_recurse (&iter, &iter_array);
840 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
842 const char *name, *value;
843 dbus_message_iter_recurse (&iter_array, &iter_dict);
844 dbus_message_iter_get_basic (&iter_dict, &name);
845 dbus_message_iter_get_basic (&iter_dict, &value);
846 g_hash_table_insert (hash, g_strdup (name), g_strdup (value));
847 dbus_message_iter_next (&iter_array);
853 _atspi_dbus_attribute_array_from_message (DBusMessage *message)
855 GArray *array = g_array_new (TRUE, TRUE, sizeof (gchar *));
856 DBusMessageIter iter, iter_array, iter_dict;
857 const char *signature;
860 signature = dbus_message_get_signature (message);
862 if (strcmp (signature, "a{ss}") != 0)
864 g_warning ("Trying to get hash from message of unexpected type %s\n", signature);
868 dbus_message_iter_init (message, &iter);
870 dbus_message_iter_recurse (&iter, &iter_array);
871 while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID)
873 const char *name, *value;
876 dbus_message_iter_recurse (&iter_array, &iter_dict);
877 dbus_message_iter_get_basic (&iter_dict, &name);
878 dbus_message_iter_get_basic (&iter_dict, &value);
879 str = g_strdup_printf ("%s:;%s", name, value);
880 new_array = g_array_append_val (array, str);
883 dbus_message_iter_next (&iter);;