2 * AT-SPI - Assistive Technology Service Provider Interface
3 * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
5 * Copyright 2008 Novell, Inc.
6 * Copyright 2008, 2009 Codethink Ltd.
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.
29 #include "accessible-register.h"
32 * This module is responsible for keeping track of all the AtkObjects in
33 * the application, so that they can be accessed remotely and placed in
34 * a client side cache.
36 * To access an AtkObject remotely we need to provide a D-Bus object
37 * path for it. The D-Bus object paths used have a standard prefix
38 * (SPI_ATK_OBJECT_PATH_PREFIX). Appended to this prefix is a string
39 * representation of an integer reference. So to access an AtkObject
40 * remotely we keep a Hashtable that maps the given reference to
41 * the AtkObject pointer. An object in this hash table is said to be 'registered'.
43 * The architecture of AT-SPI dbus is such that AtkObjects are not
44 * remotely reference counted. This means that we need to keep track of
45 * object destruction. When an object is destroyed it must be 'deregistered'
46 * To do this lookup we keep a dbus-id attribute on each AtkObject.
48 * In addition to accessing the objects remotely we must be able to update
49 * the client side cache. This is done using the 'update' signal of the
50 * org.freedesktop.atspi.Tree interface. The update signal should send out
51 * all of the cacheable data for an Accessible object.
58 * This code seems very brittle.
59 * I would prefer changes to be made to
60 * gail and the ATK interface so that all Accessible
61 * objects are registered with an exporting module.
63 * This is the same system as Qt has with the QAccessibleBridge
64 * and QAccessibleBridgePlugin. It entails some rather
65 * large structural changes to ATK though:
67 * Removing infinite spaces (Child access no longer references child).
68 * Removing lazy creation of accessible objects.
71 #define SPI_ATK_OBJECT_PATH_PREFIX "/org/freedesktop/atspi/accessible"
72 #define SPI_ATK_OBJECT_PATH_DESKTOP "/root"
74 #define SPI_ATK_PATH_PREFIX_LENGTH 33
75 #define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "/%d"
78 static GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
80 static guint reference_counter = 0;
82 static GStaticRecMutex registration_mutex = G_STATIC_REC_MUTEX_INIT;
84 /*---------------------------------------------------------------------------*/
86 static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT;
87 static gboolean recursion_check = FALSE;
90 recursion_check_and_set ()
93 g_static_mutex_lock (&recursion_check_guard);
94 ret = recursion_check;
95 recursion_check = TRUE;
96 g_static_mutex_unlock (&recursion_check_guard);
101 recursion_check_unset ()
103 g_static_mutex_lock (&recursion_check_guard);
104 recursion_check = FALSE;
105 g_static_mutex_unlock (&recursion_check_guard);
108 /*---------------------------------------------------------------------------*/
111 * Each AtkObject must be asssigned a D-Bus path (Reference)
113 * This function provides an integer reference for a new
117 assign_reference(void)
120 /* Reference of 0 not allowed as used as direct key in hash table */
121 if (reference_counter == 0)
123 return reference_counter;
127 * Returns the reference of the object, or 0 if it is not registered.
130 object_to_ref (AtkObject *accessible)
132 return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
136 * Converts the Accessible object reference to its D-Bus object path
139 atk_dbus_ref_to_path (guint ref)
141 return g_strdup_printf(SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref);
144 /*---------------------------------------------------------------------------*/
147 * Callback for when a registered AtkObject is destroyed.
149 * Removes the AtkObject from the reference lookup tables, meaning
150 * it is no longer exposed over D-Bus.
153 deregister_accessible (gpointer data, GObject *accessible)
156 g_return_if_fail (ATK_IS_OBJECT (accessible));
158 ref = object_to_ref (ATK_OBJECT(accessible));
161 spi_emit_cache_removal (ref, atk_adaptor_app_data->bus);
162 g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
167 * Called to register an AtkObject with AT-SPI and expose it over D-Bus.
170 register_accessible (AtkObject *accessible)
173 g_return_if_fail (ATK_IS_OBJECT(accessible));
175 ref = assign_reference();
177 g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible);
178 g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref));
179 g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
182 /*---------------------------------------------------------------------------*/
186 * This function checks that the ref-count of an accessible
189 * There is not currently any remote reference counting
190 * in AT-SPI D-Bus so objects that are remotely owned are not
193 * TODO Add debug wrapper
196 non_owned_accessible (AtkObject *accessible)
198 if ((G_OBJECT (accessible))->ref_count <= 1)
200 g_warning ("AT-SPI: Child referenced that is not owned by its parent");
208 #endif /* SPI_ATK_DEBUG */
210 /*---------------------------------------------------------------------------*/
213 has_manages_descendants (AtkObject *accessible)
216 gboolean result = FALSE;
218 /* This is dangerous, refing the state set
219 * seems to do wierd things to the tree & cause recursion
220 * by modifying the tree alot.
222 state = atk_object_ref_state_set (accessible);
223 if (atk_state_set_contains_state (state, ATK_STATE_MANAGES_DESCENDANTS))
226 g_warning ("AT-SPI: Object with 'Manages descendants' states not currently handled by AT-SPI");
230 g_object_unref (state);
236 append_children (AtkObject *accessible, GQueue *traversal)
240 gint count = atk_object_get_n_accessible_children (accessible);
242 if (count < 0) count = 0;
243 for (i =0; i < count; i++)
245 current = atk_object_ref_accessible_child (accessible, i);
249 non_owned_accessible (current);
251 if (!has_manages_descendants (current))
252 g_queue_push_tail (traversal, current);
254 g_object_unref (G_OBJECT (current));
260 * Registers a subtree of accessible objects
261 * rooted at the accessible object provided.
263 * The leaf nodes do not have their children
264 * registered. A node is considered a leaf
265 * if it has the state "manages-descendants"
266 * or if it has already been registered.
269 register_subtree (AtkObject *accessible)
275 g_return_if_fail (ATK_IS_OBJECT (accessible));
277 traversal = g_queue_new ();
278 emit_update = g_queue_new ();
280 g_object_ref (accessible);
281 g_queue_push_tail (traversal, accessible);
283 while (!g_queue_is_empty (traversal))
285 current = g_queue_pop_head (traversal);
286 g_queue_push_tail (emit_update, current);
287 if (!object_to_ref (current))
289 register_accessible (current);
291 g_debug ("REG - %s - %d - %s", atk_object_get_name (current),
292 atk_object_get_role (current),
293 atk_dbus_object_to_path (current));
295 append_children (current, traversal);
299 while (!g_queue_is_empty (emit_update))
301 current = g_queue_pop_head (emit_update);
302 spi_emit_cache_update (current, atk_adaptor_app_data->bus);
303 g_object_unref (G_OBJECT (current));
306 g_queue_free (traversal);
307 g_queue_free (emit_update);
310 /*---------------------------------------------------------------------------*/
313 * Called when an already registered object is updated in such a
314 * way that client side cache needs to be updated.
317 update_accessible (AtkObject *accessible)
320 g_return_if_fail (ATK_IS_OBJECT(accessible));
322 ref = object_to_ref (accessible);
325 spi_emit_cache_update (accessible, atk_adaptor_app_data->bus);
329 /*---------------------------------------------------------------------------*/
332 atk_dbus_foreach_registered(GHFunc func, gpointer data)
334 g_hash_table_foreach(ref2ptr, func, data);
338 * Used to lookup an AtkObject from its D-Bus path.
341 atk_dbus_path_to_object (const char *path)
346 g_return_val_if_fail (path, NULL);
348 if (strncmp(path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0)
351 path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */
353 if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_DESKTOP, path))
354 return atk_get_root();
360 data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
362 return ATK_OBJECT (data);
368 * TODO WARNING HACK This function is dangerous.
369 * It should only be called before sending an event on an
370 * object that has not already been registered.
373 atk_dbus_object_attempt_registration (AtkObject *accessible)
377 ref = object_to_ref (accessible);
380 /* See if the object is attached to the main tree */
381 AtkObject *current, *prev = NULL;
384 /* This should iterate until it hits a NULL or registered parent */
386 current = atk_object_get_parent (accessible);
388 cref = object_to_ref (current);
389 while (current && !cref)
392 current = atk_object_get_parent (current);
394 cref = object_to_ref (current);
397 /* A registered parent, with non-registered child, has been found */
400 register_subtree (prev);
403 /* The object SHOULD be registered now. If it isn't - I give up */
404 ref = object_to_ref (accessible);
407 return atk_dbus_ref_to_path (ref);
412 g_debug ("AT-SPI: Could not register a non-attached accessible object");
419 return atk_dbus_ref_to_path (ref);
425 * Used to lookup a D-Bus path from the AtkObject.
428 atk_dbus_object_to_path (AtkObject *accessible)
432 ref = object_to_ref (accessible);
436 return atk_dbus_ref_to_path (ref);
440 atk_dbus_desktop_object_path ()
442 return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_DESKTOP);
445 /*---------------------------------------------------------------------------*/
447 typedef gboolean (*TreeUpdateAction) (GSignalInvocationHint *signal_hint,
448 guint n_param_values,
449 const GValue *param_values,
451 AtkObject *accessible);
454 * Events are not evaluated for non-registered accessibles.
456 * When a property change is made on a registered accessible
457 * the client side cache should be updated.
459 * When a parent is changed the subtree is de-registered
460 * if the parent is not attached to the root accessible.
462 /* TODO Turn this function into a macro? */
464 tree_update_wrapper (GSignalInvocationHint *signal_hint,
465 guint n_param_values,
466 const GValue *param_values,
468 TreeUpdateAction action)
470 AtkObject *accessible;
472 g_static_rec_mutex_lock (®istration_mutex);
474 /* Ensure that only registered accessibles
475 * have their signals processed.
477 accessible = ATK_OBJECT(g_value_get_object (¶m_values[0]));
478 g_return_val_if_fail (ATK_IS_OBJECT (accessible), TRUE);
480 if (object_to_ref (accessible))
483 if (recursion_check_and_set ())
484 g_warning ("AT-SPI: Recursive use of registration module");
486 g_debug ("AT-SPI: Tree update listener");
488 action (signal_hint, n_param_values, param_values, data, accessible);
490 recursion_check_unset ();
493 g_static_rec_mutex_unlock (®istration_mutex);
499 tree_update_state_action (GSignalInvocationHint *signal_hint,
500 guint n_param_values,
501 const GValue *param_values,
503 AtkObject *accessible)
505 update_accessible (accessible);
510 tree_update_state_listener (GSignalInvocationHint *signal_hint,
511 guint n_param_values,
512 const GValue *param_values,
515 tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_state_action);
520 tree_update_property_action (GSignalInvocationHint *signal_hint,
521 guint n_param_values,
522 const GValue *param_values,
524 AtkObject *accessible)
526 AtkPropertyValues *values;
527 const gchar *pname = NULL;
529 values = (AtkPropertyValues*) g_value_get_pointer (¶m_values[1]);
530 pname = values[0].property_name;
531 if (strcmp (pname, "accessible-name") == 0 ||
532 strcmp (pname, "accessible-description") == 0 ||
533 strcmp (pname, "accessible-parent") == 0)
535 update_accessible (accessible);
537 /* Parent value us updated by child-add signal of parent object */
542 tree_update_property_listener (GSignalInvocationHint *signal_hint,
543 guint n_param_values,
544 const GValue *param_values,
547 tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_property_action);
552 tree_update_children_action (GSignalInvocationHint *signal_hint,
553 guint n_param_values,
554 const GValue *param_values,
556 AtkObject *accessible)
558 const gchar *detail = NULL;
561 if (has_manages_descendants (accessible)) return TRUE;
562 if (signal_hint->detail)
563 detail = g_quark_to_string (signal_hint->detail);
565 if (!strcmp (detail, "add"))
568 int index = g_value_get_uint (param_values + 1);
569 child = g_value_get_pointer (param_values + 2);
571 if (!ATK_IS_OBJECT (child))
573 child = atk_object_ref_accessible_child (accessible, index);
575 non_owned_accessible (child);
578 register_subtree (child);
579 update_accessible (accessible);
585 tree_update_children_listener (GSignalInvocationHint *signal_hint,
586 guint n_param_values,
587 const GValue *param_values,
590 tree_update_wrapper (signal_hint, n_param_values, param_values, data, tree_update_children_action);
594 /*---------------------------------------------------------------------------*/
597 spi_atk_register_toplevel_added (AtkObject *accessible,
601 g_static_rec_mutex_lock (®istration_mutex);
603 g_return_if_fail (ATK_IS_OBJECT (accessible));
605 if (object_to_ref (accessible))
608 if (recursion_check_and_set ())
609 g_warning ("AT-SPI: Recursive use of registration module");
611 g_debug ("AT-SPI: Toplevel added listener");
613 if (!ATK_IS_OBJECT (child))
615 child = atk_object_ref_accessible_child (accessible, index);
617 non_owned_accessible (child);
620 register_subtree (child);
621 update_accessible (accessible);
623 recursion_check_unset ();
626 g_static_rec_mutex_unlock (®istration_mutex);
630 spi_atk_register_toplevel_removed (AtkObject *accessible,
634 g_static_rec_mutex_lock (®istration_mutex);
636 g_return_if_fail (ATK_IS_OBJECT (accessible));
638 if (object_to_ref (accessible))
641 if (recursion_check_and_set ())
642 g_warning ("AT-SPI: Recursive use of registration module");
644 g_debug ("AT-SPI: Toplevel removed listener");
646 update_accessible (accessible);
647 recursion_check_unset ();
650 g_static_rec_mutex_unlock (®istration_mutex);
654 * Initializes required global data. The update and removal lists
655 * and the reference lookup tables.
657 * Initializes all of the required D-Bus interfaces.
660 atk_dbus_initialize (AtkObject *root)
663 ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
666 if (g_thread_supported ())
667 g_message ("AT-SPI: Threads enabled");
669 g_debug ("AT-SPI: Initial Atk tree regisration");
672 register_subtree (root);
674 atk_add_global_event_listener (tree_update_property_listener, "Gtk:AtkObject:property-change");
675 atk_add_global_event_listener (tree_update_children_listener, "Gtk:AtkObject:children-changed");
676 atk_add_global_event_listener (tree_update_state_listener, "Gtk:AtkObject:state-change");
678 g_signal_connect (root,
679 "children-changed::add",
680 (GCallback) spi_atk_register_toplevel_added,
682 g_signal_connect (root,
683 "children-changed::remove",
684 (GCallback) spi_atk_register_toplevel_removed,
688 /*END------------------------------------------------------------------------*/