X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fat-spi2-atk.git;a=blobdiff_plain;f=atk-adaptor%2Faccessible-register.c;h=f92c078e44b30c73280843d6abae31b33df99965;hp=6b89ba593f643049be5737220746a9ff0874451b;hb=dc745797c46af3dcf093e75ef0758d8cc433427f;hpb=3a2b426db4d014adced9ee844d27ca3a485bd985 diff --git a/atk-adaptor/accessible-register.c b/atk-adaptor/accessible-register.c index 6b89ba5..f92c078 100644 --- a/atk-adaptor/accessible-register.c +++ b/atk-adaptor/accessible-register.c @@ -28,14 +28,6 @@ #include "bridge.h" #include "accessible-register.h" -/* TODO - * Need to add concurrency support. - */ - -#define ATK_BRIDGE_OBJECT_PATH_PREFIX "/org/freedesktop/atspi/accessible" -#define ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE ATK_BRIDGE_OBJECT_PATH_PREFIX "/%d" -#define ATK_BRIDGE_PATH_PREFIX_LENGTH 33 - /* * This module is responsible for keeping track of all the AtkObjects in * the application, so that they can be accessed remotely and placed in @@ -43,7 +35,7 @@ * * To access an AtkObject remotely we need to provide a D-Bus object * path for it. The D-Bus object paths used have a standard prefix - * (ATK_BRIDGE_OBJECT_PATH_PREFIX). Appended to this prefix is a string + * (SPI_ATK_OBJECT_PATH_PREFIX). Appended to this prefix is a string * representation of an integer reference. So to access an AtkObject * remotely we keep a Hashtable that maps the given reference to * the AtkObject pointer. An object in this hash table is said to be 'registered'. @@ -57,12 +49,62 @@ * the client side cache. This is done using the 'update' signal of the * org.freedesktop.atspi.Tree interface. The update signal should send out * all of the cacheable data for an Accessible object. + * */ -GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */ +/* + * FIXME + * + * This code seems very brittle. + * I would prefer changes to be made to + * gail and the ATK interface so that all Accessible + * objects are registered with an exporting module. + * + * This is the same system as Qt has with the QAccessibleBridge + * and QAccessibleBridgePlugin. It entails some rather + * large structural changes to ATK though: + * + * Removing infinite spaces (Child access no longer references child). + * Removing lazy creation of accessible objects. + */ + +#define SPI_ATK_OBJECT_PATH_PREFIX "/org/freedesktop/atspi/accessible" +#define SPI_ATK_OBJECT_PATH_DESKTOP "/root" + +#define SPI_ATK_PATH_PREFIX_LENGTH 33 +#define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "/%d" + + +static GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */ static guint counter = 1; +static GStaticRecMutex registration_mutex = G_STATIC_REC_MUTEX_INIT; + +/*---------------------------------------------------------------------------*/ + +static GStaticMutex recursion_check_guard = G_STATIC_MUTEX_INIT; +static gboolean recursion_check = FALSE; + +static gboolean +recursion_check_and_set () +{ + gboolean ret; + g_static_mutex_lock (&recursion_check_guard); + ret = recursion_check; + recursion_check = TRUE; + g_static_mutex_unlock (&recursion_check_guard); + return ret; +} + +static void +recursion_check_unset () +{ + g_static_mutex_lock (&recursion_check_guard); + recursion_check = FALSE; + g_static_mutex_unlock (&recursion_check_guard); +} + /*---------------------------------------------------------------------------*/ /* @@ -78,99 +120,55 @@ assign_reference(void) /* Reference of 0 not allowed as used as direct key in hash table */ if (counter == 0) counter++; + return counter; } -/*---------------------------------------------------------------------------*/ +/* + * Returns the reference of the object, or 0 if it is not registered. + */ +static guint +object_to_ref (AtkObject *accessible) +{ + return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id")); +} -void -atk_dbus_foreach_registered(GHFunc func, gpointer data) +/* + * Converts the Accessible object reference to its D-Bus object path + */ +static gchar * +ref_to_path (guint ref) { - g_hash_table_foreach(ref2ptr, func, data); + return g_strdup_printf(SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref); } /*---------------------------------------------------------------------------*/ /* - * Called when a registered AtkObject is deleted. - * Removes the AtkObject from the reference lookup tables. - * Sets the client side cache to be updated. + * Callback for when a registered AtkObject is destroyed. + * + * Removes the AtkObject from the reference lookup tables, meaning + * it is no longer exposed over D-Bus. */ static void -deregister_accessible(gpointer data, GObject *accessible) +deregister_accessible (gpointer data, GObject *accessible) { guint ref; - gchar *path; - - g_assert(ATK_IS_OBJECT(accessible)); - - - ref = atk_dbus_object_to_ref (ATK_OBJECT(accessible)); + g_assert (ATK_IS_OBJECT (accessible)); + ref = object_to_ref (ATK_OBJECT(accessible)); if (ref != 0) { g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref)); - /* - * TODO - * Pyatspi client side exceptions have occured indicating - * that an object has been removed twice. - * This should not be possible and needs investigation. - */ - spi_emit_cache_removal (ref, atk_adaptor_app_data->bus); } } -/*---------------------------------------------------------------------------*/ - -/* FIXME - * Horrible hack warning. - * - * Problem 1: - * - * In an ideal world there would be a signal "Accessible created" that we could - * use to register all new AtkObjects with D-Bus. The AtkObjects would be - * created at the time of their implementing widget. This is how things - * happen in Qt and its damn sensible. - * - * In GTK Gail objects are created 'lazily' when they are accessed. This is - * presumably an optimization to reduce memory. I happen to think its a very - * very bad one. Anyway, there is no signal, and Gail objects don't get created - * automatically for each widget, so how do we register AtkObjects with D-Bus? - * - * Answer, we have one guaranteed AtkObject, the root. We traverse the tree provided - * by the root object, registering as we go. When new objects are created we use - * the children-changed signal of their parent to find out. As we don't know - * if a new object has any children that have not been registered we must traverse - * the decendants of every new object to find AtkObjects that have not been registered. - * - * Problem 2: - * - * For whatever reason events are generated for objects that have not yet been - * registered with D-Bus. This means that when translating an Atk signal to an - * AT-SPI one it may be neccessary to register objects first. - * - * The caveat is that when registering an object somewhere in the middle of the - * AtkObject tree there is no guarantee that its parent objects have been registered. - * So when registering a new object we also need to register its parents back to the - * root object. - * - * Other solutions: - * - * The original solution was completely recursive. So when the reference of an AtkObject - * was requested it would be registered there and then. I didn't like the recursive - * solution, it was a very very deep stack in some cases. - * - */ - /* - * This function registers the object so that it is exported - * over D-Bus and schedules an update to client side cache. + * Called to register an AtkObject with AT-SPI and expose it over D-Bus. */ -static guint -export (GList **uplist, AtkObject *accessible) +static void +register_accessible (AtkObject *accessible) { guint ref; - gchar *path; - g_assert(ATK_IS_OBJECT(accessible)); ref = assign_reference(); @@ -178,47 +176,89 @@ export (GList **uplist, AtkObject *accessible) g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible); g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref)); g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL); - - *uplist = g_list_prepend (*uplist, accessible); - - return ref; } +/*---------------------------------------------------------------------------*/ + +#ifdef SPI_ATK_DEBUG /* - * Exports all the dependencies of an AtkObject. - * This is the subtree and the ancestors. + * This function checks that the ref-count of an accessible + * is greater than 1. * - * Dependencies are the objects that get included in the - * cache, and therefore need to be registered before the - * update signal is sent. + * There is not currently any remote reference counting + * in AT-SPI D-Bus so objects that are remotely owned are not + * allowed. * - * This does a depth first traversal of a subtree of AtkObject - * and exports them as Accessible objects if they are not exported - * already. + * TODO Add debug wrapper + */ +static gboolean +non_owned_accessible (AtkObject *accessible) +{ + if ((G_OBJECT (accessible))->ref_count <= 1) + { + g_warning ("AT-SPI: Child referenced that is not owned by its parent"); + return TRUE; + } + else + { + return FALSE; + } +} +#endif /* SPI_ATK_DEBUG */ + +/*---------------------------------------------------------------------------*/ + +static gboolean +has_manages_descendants (AtkObject *accessible) +{ + AtkStateSet *state; + gboolean result = FALSE; + + /* This is dangerous, refing the state set + * seems to do wierd things to the tree & cause recursion + * by modifying the tree alot. + */ + state = atk_object_ref_state_set (accessible); + if (atk_state_set_contains_state (state, ATK_STATE_MANAGES_DESCENDANTS)) + { +#ifdef SPI_ATK_DEBUG + g_warning ("AT-SPI: Object with 'Manages descendants' states not currently handled by AT-SPI"); +#endif + result = TRUE; + } + g_object_unref (state); + + return result; +} + +/* + * Registers a subtree of accessible objects + * rooted at the accessible object provided. * - * It exports all ancestors of the object if they are not - * exported already. + * The leaf nodes do not have their children + * registered. A node is considered a leaf + * if it has the state "manages-descendants" + * or if it has already been registered. */ -static guint -export_deps (AtkObject *accessible) +void +register_subtree (AtkObject *accessible) { AtkObject *current, *tmp; GQueue *stack; - GList *uplist = NULL; - guint i, ref; + guint i; gboolean recurse; - /* Export subtree including object itself */ - /*========================================*/ - ref = atk_dbus_object_to_ref (accessible); - if (ref) - return ref; + current = g_object_ref (accessible); + if (has_manages_descendants (current)) + { + g_object_unref (current); + return; + } stack = g_queue_new (); - current = g_object_ref (accessible); - ref = export (&uplist, current); + register_accessible (current); g_queue_push_head (stack, GINT_TO_POINTER (0)); /* @@ -227,108 +267,91 @@ export_deps (AtkObject *accessible) */ while (!g_queue_is_empty (stack)) { - /* This while loop finds the next node that needs processing, - * if one exists. - */ + /* Find the next child node that needs processing */ + i = GPOINTER_TO_INT(g_queue_peek_head (stack)); recurse = FALSE; + while (i < atk_object_get_n_accessible_children (current) && recurse == FALSE) { tmp = atk_object_ref_accessible_child (current, i); - if (!atk_dbus_object_to_ref (tmp)) + +#ifdef SPI_ATK_DEBUG + non_owned_accessible (tmp); +#endif + + if (object_to_ref (tmp)) { - recurse = TRUE; + /* If its already registered, just update */ + spi_emit_cache_update (tmp, atk_adaptor_app_data->bus); + } + else if (has_manages_descendants (tmp)) + { + /* If it has manages descendants, just register and update */ + register_accessible (tmp); + spi_emit_cache_update (tmp, atk_adaptor_app_data->bus); } else { - i++; + recurse = TRUE; + } + + if (!recurse) + { g_object_unref (G_OBJECT (tmp)); } + + i++; } + if (recurse) { - /* Still children to process */ + /* Push onto stack */ current = tmp; - export (&uplist, current); - /* Update parent nodes next child index */ - g_queue_peek_head_link (stack)->data = GINT_TO_POINTER (i+1); - /* Push a new child index for the current node */ + register_accessible (current); + + g_queue_peek_head_link (stack)->data = GINT_TO_POINTER (i); g_queue_push_head (stack, GINT_TO_POINTER (0)); } else { - /* No more children, move to parent */ + /* Pop from stack */ + spi_emit_cache_update (current, atk_adaptor_app_data->bus); tmp = current; current = atk_object_get_parent (current); g_object_unref (G_OBJECT (tmp)); g_queue_pop_head (stack); } } - - /* Export all neccessary ancestors of the object */ - /*===============================================*/ - current = atk_object_get_parent (accessible); - while (current && !atk_dbus_object_to_ref (current)) - { - export (&uplist, current); - } - - spi_emit_cache_update (uplist, atk_adaptor_app_data->bus); - g_list_free (uplist); - return ref; + g_queue_free (stack); } /*---------------------------------------------------------------------------*/ -/* Called to register an AtkObject with AT-SPI and expose it over D-Bus. */ -guint -atk_dbus_register_accessible (AtkObject *accessible) -{ - guint ref; - g_assert(ATK_IS_OBJECT(accessible)); - - return export_deps (accessible); -} - -/* Called when an already registered object is updated in such a +/* + * Called when an already registered object is updated in such a * way that client side cache needs to be updated. */ -guint -atk_dbus_update_accessible (AtkObject *accessible) +static void +update_accessible (AtkObject *accessible) { guint ref = 0; - GList *uplist = NULL; g_assert(ATK_IS_OBJECT(accessible)); - ref = atk_dbus_object_to_ref (accessible); + ref = object_to_ref (accessible); if (ref) { - uplist = g_list_prepend (uplist, accessible); - spi_emit_cache_update (uplist, atk_adaptor_app_data->bus); - g_list_free (uplist); + spi_emit_cache_update (accessible, atk_adaptor_app_data->bus); } - return ref; } /*---------------------------------------------------------------------------*/ -/* - * Returns the reference of the object, or 0 if it is not exported over D-Bus. - */ -guint -atk_dbus_object_to_ref (AtkObject *accessible) -{ - return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id")); -} - -/* - * Converts the Accessible object reference to its D-Bus object path - */ -gchar * -atk_dbus_ref_to_path (guint ref) +void +atk_dbus_foreach_registered(GHFunc func, gpointer data) { - return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref); + g_hash_table_foreach(ref2ptr, func, data); } /* @@ -342,12 +365,12 @@ atk_dbus_path_to_object (const char *path) g_assert (path); - if (strncmp(path, ATK_BRIDGE_OBJECT_PATH_PREFIX, ATK_BRIDGE_PATH_PREFIX_LENGTH) != 0) + if (strncmp(path, SPI_ATK_OBJECT_PATH_PREFIX, SPI_ATK_PATH_PREFIX_LENGTH) != 0) return NULL; - path += ATK_BRIDGE_PATH_PREFIX_LENGTH; /* Skip over the prefix */ + path += SPI_ATK_PATH_PREFIX_LENGTH; /* Skip over the prefix */ - if (path[0] == '\0') + if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_DESKTOP, path)) return atk_get_root(); if (path[0] != '/') return NULL; @@ -361,7 +384,6 @@ atk_dbus_path_to_object (const char *path) return NULL; } - /* * Used to lookup a D-Bus path from the AtkObject. */ @@ -369,18 +391,132 @@ gchar * atk_dbus_object_to_path (AtkObject *accessible) { guint ref; - g_assert(ATK_IS_OBJECT(accessible)); - ref = atk_dbus_object_to_ref (accessible); + ref = object_to_ref (accessible); if (!ref) return NULL; else - return atk_dbus_ref_to_path (ref); + return ref_to_path (ref); +} + +gchar * +atk_dbus_desktop_object_path () +{ + return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_DESKTOP); } /*---------------------------------------------------------------------------*/ /* + * Events are not evaluated for non-registered accessibles. + * + * When a property change is made on a registered accessible + * the client side cache should be updated. + * + * When a parent is changed the subtree is de-registered + * if the parent is not attached to the root accessible. + */ +static gboolean +tree_update_listener (GSignalInvocationHint *signal_hint, + guint n_param_values, + const GValue *param_values, + gpointer data) +{ + AtkObject *accessible; + AtkPropertyValues *values; + const gchar *pname = NULL; + + g_static_rec_mutex_lock (®istration_mutex); + + /* Ensure that only registered accessibles + * have their signals processed. + */ + accessible = g_value_get_object (¶m_values[0]); + g_assert (ATK_IS_OBJECT (accessible)); + + if (object_to_ref (accessible)) + { +#ifdef SPI_ATK_DEBUG + if (recursion_check_and_set ()) + g_warning ("AT-SPI: Recursive use of registration module"); +#endif + + values = (AtkPropertyValues*) g_value_get_pointer (¶m_values[1]); + pname = values[0].property_name; + if (strcmp (pname, "accessible-name") == 0 || + strcmp (pname, "accessible-description") == 0) + { + update_accessible (accessible); + } + /* Parent value us updated by child-add signal of parent object */ + + recursion_check_unset (); + } + + g_static_rec_mutex_unlock (®istration_mutex); + + return TRUE; +} + +/* + * Events are not evaluated for non registered accessibles. + * + * When the children of a registered accessible are changed + * the subtree, rooted at the child is registered. + */ +static gboolean +tree_update_children_listener (GSignalInvocationHint *signal_hint, + guint n_param_values, + const GValue *param_values, + gpointer data) +{ + AtkObject *accessible; + const gchar *detail = NULL; + AtkObject *child; + + g_static_rec_mutex_lock (®istration_mutex); + + /* Ensure that only registered accessibles + * have their signals processed. + */ + accessible = g_value_get_object (¶m_values[0]); + g_assert (ATK_IS_OBJECT (accessible)); + + if (object_to_ref (accessible)) + { +#ifdef SPI_ATK_DEBUG + if (recursion_check_and_set ()) + g_warning ("AT-SPI: Recursive use of registration module"); +#endif + + if (signal_hint->detail) + detail = g_quark_to_string (signal_hint->detail); + + if (!strcmp (detail, "add")) + { + gpointer child; + int index = g_value_get_uint (param_values + 1); + child = g_value_get_pointer (param_values + 2); + + if (!ATK_IS_OBJECT (child)) + { + child = atk_object_ref_accessible_child (accessible, index); +#ifdef SPI_ATK_DEBUG + non_owned_accessible (child); +#endif + } + register_subtree (child); + } + + recursion_check_unset (); + } + + g_static_rec_mutex_unlock (®istration_mutex); + + return TRUE; +} + +/* * Initializes required global data. The update and removal lists * and the reference lookup tables. * @@ -392,8 +528,15 @@ atk_dbus_initialize (AtkObject *root) if (!ref2ptr) ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal); - /* Get the root accessible and add */ - atk_dbus_register_accessible (root); +#ifdef SPI_ATK_DEBUG + if (g_thread_supported ()) + g_message ("AT-SPI: Threads enabled"); +#endif + + register_subtree (root); + + atk_add_global_event_listener (tree_update_listener, "Gtk:AtkObject:property-change"); + atk_add_global_event_listener (tree_update_children_listener, "Gtk:AtkObject:children-changed"); } /*END------------------------------------------------------------------------*/