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=7ca416fd2aef8494006e3873e3ee2d57d6157e88;hp=6b89ba593f643049be5737220746a9ff0874451b;hb=d1e52eb847dcabf7a2be07dbd71cfcb4e0805306;hpb=3a2b426db4d014adced9ee844d27ca3a485bd985 diff --git a/atk-adaptor/accessible-register.c b/atk-adaptor/accessible-register.c index 6b89ba5..7ca416f 100644 --- a/atk-adaptor/accessible-register.c +++ b/atk-adaptor/accessible-register.c @@ -3,7 +3,7 @@ * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap) * * Copyright 2008 Novell, Inc. - * Copyright 2008, 2009 Codethink Ltd. + * Copyright 2008, 2009, 2010 Codethink Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -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'. @@ -53,348 +45,276 @@ * object destruction. When an object is destroyed it must be 'deregistered' * To do this lookup we keep a dbus-id attribute on each AtkObject. * - * In addition to accessing the objects remotely we must be able to update - * 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 */ +#define SPI_ATK_PATH_PREFIX_LENGTH 27 +#define SPI_ATK_OBJECT_PATH_PREFIX "/org/a11y/atspi/accessible/" +#define SPI_ATK_OBJECT_PATH_ROOT "root" -static guint counter = 1; +#define SPI_ATK_OBJECT_REFERENCE_TEMPLATE SPI_ATK_OBJECT_PATH_PREFIX "%d" -/*---------------------------------------------------------------------------*/ +#define SPI_DBUS_ID "spi-dbus-id" -/* - * Each AtkObject must be asssigned a D-Bus path (Reference) - * - * This function provides an integer reference for a new - * AtkObject. - */ -static guint -assign_reference(void) -{ - counter++; - /* Reference of 0 not allowed as used as direct key in hash table */ - if (counter == 0) - counter++; -} +SpiRegister *spi_global_register = NULL; -/*---------------------------------------------------------------------------*/ +static const gchar * spi_register_root_path = SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_ROOT; -void -atk_dbus_foreach_registered(GHFunc func, gpointer data) +enum { - g_hash_table_foreach(ref2ptr, func, data); -} + OBJECT_REGISTERED, + OBJECT_DEREGISTERED, + LAST_SIGNAL +}; +static guint register_signals[LAST_SIGNAL] = { 0 }; /*---------------------------------------------------------------------------*/ -/* - * Called when a registered AtkObject is deleted. - * Removes the AtkObject from the reference lookup tables. - * Sets the client side cache to be updated. - */ static void -deregister_accessible(gpointer data, GObject *accessible) -{ - guint ref; - gchar *path; - - g_assert(ATK_IS_OBJECT(accessible)); +spi_register_finalize (GObject * object); +/*---------------------------------------------------------------------------*/ - ref = atk_dbus_object_to_ref (ATK_OBJECT(accessible)); +G_DEFINE_TYPE (SpiRegister, spi_register, G_TYPE_OBJECT) - 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); - } +static void spi_register_class_init (SpiRegisterClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + spi_register_parent_class = g_type_class_ref (G_TYPE_OBJECT); + + object_class->finalize = spi_register_finalize; + + register_signals [OBJECT_REGISTERED] = + g_signal_new ("object-registered", + SPI_REGISTER_TYPE, + G_SIGNAL_ACTION, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); + + register_signals [OBJECT_DEREGISTERED] = + g_signal_new ("object-deregistered", + SPI_REGISTER_TYPE, + G_SIGNAL_ACTION, + 0, + NULL, + NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, + 1, + G_TYPE_OBJECT); } -/*---------------------------------------------------------------------------*/ - -/* 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. - * - */ +static void +spi_register_init (SpiRegister * reg) +{ + reg->ref2ptr = g_hash_table_new (g_direct_hash, g_direct_equal); + reg->reference_counter = 0; +} -/* - * This function registers the object so that it is exported - * over D-Bus and schedules an update to client side cache. - */ -static guint -export (GList **uplist, AtkObject *accessible) +static void +deregister_object (gpointer data, GObject * gobj) { - guint ref; - gchar *path; + SpiRegister *reg = SPI_REGISTER (data); - g_assert(ATK_IS_OBJECT(accessible)); + spi_register_deregister_object (reg, gobj, FALSE); +} - ref = assign_reference(); +static void +spi_register_remove_weak_ref (gpointer key, gpointer val, gpointer reg) +{ + g_object_weak_unref (val, deregister_object, reg); +} - 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); +static void +spi_register_finalize (GObject * object) +{ + SpiRegister *reg = SPI_REGISTER (object); - *uplist = g_list_prepend (*uplist, accessible); + g_hash_table_foreach (reg->ref2ptr, spi_register_remove_weak_ref, reg); + g_hash_table_unref (reg->ref2ptr); - return ref; + G_OBJECT_CLASS (spi_register_parent_class)->finalize (object); } +/*---------------------------------------------------------------------------*/ + /* - * Exports all the dependencies of an AtkObject. - * This is the subtree and the ancestors. - * - * Dependencies are the objects that get included in the - * cache, and therefore need to be registered before the - * update signal is sent. + * Each AtkObject must be asssigned a D-Bus path (Reference) * - * This does a depth first traversal of a subtree of AtkObject - * and exports them as Accessible objects if they are not exported - * already. + * This function provides an integer reference for a new + * AtkObject. * - * It exports all ancestors of the object if they are not - * exported already. + * TODO: Make this reference a little more unique, this is shoddy. */ static guint -export_deps (AtkObject *accessible) +assign_reference (SpiRegister * reg) { - AtkObject *current, *tmp; - GQueue *stack; - GList *uplist = NULL; - guint i, ref; - gboolean recurse; - - - /* Export subtree including object itself */ - /*========================================*/ - ref = atk_dbus_object_to_ref (accessible); - if (ref) - return ref; - - stack = g_queue_new (); - - current = g_object_ref (accessible); - ref = export (&uplist, current); - g_queue_push_head (stack, GINT_TO_POINTER (0)); - - /* - * The index held on the stack is the next child node - * that needs processing at the corresponding level in the tree. - */ - while (!g_queue_is_empty (stack)) - { - /* This while loop finds the next node that needs processing, - * if one exists. - */ - 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)) - { - recurse = TRUE; - } - else - { - i++; - g_object_unref (G_OBJECT (tmp)); - } - } - if (recurse) - { - /* Still children to process */ - 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 */ - g_queue_push_head (stack, GINT_TO_POINTER (0)); - } - else - { - /* No more children, move to parent */ - 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; + reg->reference_counter++; + /* Reference of 0 not allowed as used as direct key in hash table */ + if (reg->reference_counter == 0) + reg->reference_counter++; + return reg->reference_counter; } /*---------------------------------------------------------------------------*/ -/* Called to register an AtkObject with AT-SPI and expose it over D-Bus. */ -guint -atk_dbus_register_accessible (AtkObject *accessible) +/* + * Returns the reference of the object, or 0 if it is not registered. + */ +static guint +object_to_ref (GObject * gobj) { - guint ref; - g_assert(ATK_IS_OBJECT(accessible)); - - return export_deps (accessible); + return GPOINTER_TO_INT (g_object_get_data (gobj, SPI_DBUS_ID)); } -/* Called when an already registered object is updated in such a - * way that client side cache needs to be updated. +/* + * Converts the Accessible object reference to its D-Bus object path */ -guint -atk_dbus_update_accessible (AtkObject *accessible) +static gchar * +ref_to_path (guint ref) { - guint ref = 0; - GList *uplist = NULL; - g_assert(ATK_IS_OBJECT(accessible)); - - ref = atk_dbus_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); - } - return ref; + return g_strdup_printf (SPI_ATK_OBJECT_REFERENCE_TEMPLATE, ref); } /*---------------------------------------------------------------------------*/ /* - * Returns the reference of the object, or 0 if it is not exported over D-Bus. + * 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. */ -guint -atk_dbus_object_to_ref (AtkObject *accessible) +void +spi_register_deregister_object (SpiRegister *reg, GObject *gobj, gboolean unref) { - return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id")); + guint ref; + + ref = object_to_ref (gobj); + if (ref != 0) + { + g_signal_emit (reg, + register_signals [OBJECT_DEREGISTERED], + 0, + gobj); + if (unref) + g_object_weak_unref (gobj, deregister_object, reg); + g_hash_table_remove (reg->ref2ptr, GINT_TO_POINTER (ref)); + +#ifdef SPI_ATK_DEBUG + g_debug ("DEREG - %d", ref); +#endif + } } -/* - * Converts the Accessible object reference to its D-Bus object path - */ -gchar * -atk_dbus_ref_to_path (guint ref) +static void +register_object (SpiRegister * reg, GObject * gobj) { - return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref); + guint ref; + g_return_if_fail (G_IS_OBJECT (gobj)); + + ref = assign_reference (reg); + + g_hash_table_insert (reg->ref2ptr, GINT_TO_POINTER (ref), gobj); + g_object_set_data (G_OBJECT (gobj), SPI_DBUS_ID, GINT_TO_POINTER (ref)); + g_object_weak_ref (G_OBJECT (gobj), deregister_object, reg); + +#ifdef SPI_ATK_DEBUG + g_debug ("REG - %d", ref); +#endif + + g_signal_emit (reg, register_signals [OBJECT_REGISTERED], 0, gobj); } +/*---------------------------------------------------------------------------*/ + /* - * Used to lookup an AtkObject from its D-Bus path. + * Used to lookup an GObject from its D-Bus path. + * + * If the D-Bus path is not found this function returns NULL. */ -AtkObject * -atk_dbus_path_to_object (const char *path) +GObject * +spi_register_path_to_object (SpiRegister * reg, const char *path) { guint index; void *data; - g_assert (path); + g_return_val_if_fail (path, NULL); - 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') - return atk_get_root(); - if (path[0] != '/') - return NULL; + /* Map the root path to the root object. */ + if (!g_strcmp0 (SPI_ATK_OBJECT_PATH_ROOT, path)) + return G_OBJECT (spi_global_app_data->root); - path++; index = atoi (path); - data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index)); + data = g_hash_table_lookup (reg->ref2ptr, GINT_TO_POINTER (index)); if (data) - return ATK_OBJECT (data); + return G_OBJECT(data); else return NULL; } +GObject * +spi_global_register_path_to_object (const char * path) +{ + return spi_register_path_to_object (spi_global_register, path); +} /* - * Used to lookup a D-Bus path from the AtkObject. + * Used to lookup a D-Bus path from the GObject. + * + * If the objects is not already registered, + * this function will register it. */ gchar * -atk_dbus_object_to_path (AtkObject *accessible) +spi_register_object_to_path (SpiRegister * reg, GObject * gobj) { guint ref; - g_assert(ATK_IS_OBJECT(accessible)); - ref = atk_dbus_object_to_ref (accessible); + if (gobj == NULL) + return NULL; + + /* Map the root object to the root path. */ + if ((void *)gobj == (void *)spi_global_app_data->root) + return g_strdup (spi_register_root_path); + + ref = object_to_ref (gobj); if (!ref) - return NULL; + { + register_object (reg, gobj); + ref = object_to_ref (gobj); + } + + if (!ref) + return NULL; else - return atk_dbus_ref_to_path (ref); + return ref_to_path (ref); } -/*---------------------------------------------------------------------------*/ - +guint +spi_register_object_to_ref (GObject * gobj) +{ + return object_to_ref (gobj); +} + /* - * Initializes required global data. The update and removal lists - * and the reference lookup tables. - * - * Initializes all of the required D-Bus interfaces. + * Gets the path that indicates the accessible desktop object. + * This object is logically located on the registry daemon and not + * within any particular application. */ -void -atk_dbus_initialize (AtkObject *root) +gchar * +spi_register_root_object_path () { - if (!ref2ptr) - ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal); - - /* Get the root accessible and add */ - atk_dbus_register_accessible (root); + return g_strdup (SPI_ATK_OBJECT_PATH_PREFIX SPI_ATK_OBJECT_PATH_ROOT); } /*END------------------------------------------------------------------------*/ -