2009-01-12 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / atk-adaptor / accessible-register.c
1 /*
2  * AT-SPI - Assistive Technology Service Provider Interface
3  * (Gnome Accessibility Project; http://developer.gnome.org/projects/gap)
4  *
5  * Copyright 2008 Novell, Inc.
6  * Copyright 2008, 2009 Codethink Ltd.
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 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "bridge.h"
29 #include "accessible-register.h"
30
31 /* TODO
32  * Need to add concurrency support.
33  */
34
35 #define ATK_BRIDGE_OBJECT_PATH_PREFIX "/org/freedesktop/atspi/accessible"
36 #define ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE ATK_BRIDGE_OBJECT_PATH_PREFIX "/%d"
37 #define ATK_BRIDGE_PATH_PREFIX_LENGTH 33
38
39 /*
40  * This module is responsible for keeping track of all the AtkObjects in
41  * the application, so that they can be accessed remotely and placed in
42  * a client side cache.
43  *
44  * To access an AtkObject remotely we need to provide a D-Bus object 
45  * path for it. The D-Bus object paths used have a standard prefix
46  * (ATK_BRIDGE_OBJECT_PATH_PREFIX). Appended to this prefix is a string
47  * representation of an integer reference. So to access an AtkObject 
48  * remotely we keep a Hashtable that maps the given reference to 
49  * the AtkObject pointer. An object in this hash table is said to be 'registered'.
50  *
51  * The architecture of AT-SPI dbus is such that AtkObjects are not
52  * remotely reference counted. This means that we need to keep track of
53  * object destruction. When an object is destroyed it must be 'deregistered'
54  * To do this lookup we keep a dbus-id attribute on each AtkObject.
55  *
56  * In addition to accessing the objects remotely we must be able to update
57  * the client side cache. This is done using the 'update' signal of the 
58  * org.freedesktop.atspi.Tree interface. The update signal should send out
59  * all of the cacheable data for an Accessible object.
60  */
61
62 GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
63
64 static guint counter = 1;
65
66 /*---------------------------------------------------------------------------*/
67
68 /*
69  * Each AtkObject must be asssigned a D-Bus path (Reference)
70  *
71  * This function provides an integer reference for a new
72  * AtkObject.
73  */
74 static guint
75 assign_reference(void)
76 {
77   counter++;
78   /* Reference of 0 not allowed as used as direct key in hash table */
79   if (counter == 0)
80     counter++;
81 }
82
83 /*---------------------------------------------------------------------------*/
84
85 void
86 atk_dbus_foreach_registered(GHFunc func, gpointer data)
87 {
88   g_hash_table_foreach(ref2ptr, func, data);
89 }
90
91 /*---------------------------------------------------------------------------*/
92
93 /*
94  * Called when a registered AtkObject is deleted.
95  * Removes the AtkObject from the reference lookup tables.
96  * Sets the client side cache to be updated.
97  */
98 static void
99 deregister_accessible(gpointer data, GObject *accessible)
100 {
101   guint ref;
102   gchar *path;
103
104   g_assert(ATK_IS_OBJECT(accessible));
105
106
107   ref = atk_dbus_object_to_ref (ATK_OBJECT(accessible));
108
109   if (ref != 0)
110     {
111       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
112       /*
113        * TODO
114        * Pyatspi client side exceptions have occured indicating
115        * that an object has been removed twice.
116        * This should not be possible and needs investigation.
117        */
118       spi_emit_cache_removal (ref, atk_adaptor_app_data->bus);
119     }
120 }
121
122 /*---------------------------------------------------------------------------*/
123
124 /* FIXME
125  * Horrible hack warning.
126  *
127  * Problem 1:
128  *
129  * In an ideal world there would be a signal "Accessible created" that we could
130  * use to register all new AtkObjects with D-Bus. The AtkObjects would be
131  * created at the time of their implementing widget. This is how things
132  * happen in Qt and its damn sensible.
133  *
134  * In GTK Gail objects are created 'lazily' when they are accessed. This is
135  * presumably an optimization to reduce memory. I happen to think its a very
136  * very bad one. Anyway, there is no signal, and Gail objects don't get created
137  * automatically for each widget, so how do we register AtkObjects with D-Bus?
138  *
139  * Answer, we have one guaranteed AtkObject, the root. We traverse the tree provided
140  * by the root object, registering as we go. When new objects are created we use
141  * the children-changed signal of their parent to find out. As we don't know
142  * if a new object has any children that have not been registered we must traverse
143  * the decendants of every new object to find AtkObjects that have not been registered.
144  *
145  * Problem 2:
146  *
147  * For whatever reason events are generated for objects that have not yet been
148  * registered with D-Bus. This means that when translating an Atk signal to an
149  * AT-SPI one it may be neccessary to register objects first. 
150  *
151  * The caveat is that when registering an object somewhere in the middle of the
152  * AtkObject tree there is no guarantee that its parent objects have been registered.
153  * So when registering a new object we also need to register its parents back to the
154  * root object.
155  *
156  * Other solutions:
157  *
158  * The original solution was completely recursive. So when the reference of an AtkObject
159  * was requested it would be registered there and then. I didn't like the recursive
160  * solution, it was a very very deep stack in some cases.
161  *
162  */
163
164 /*
165  * This function registers the object so that it is exported
166  * over D-Bus and schedules an update to client side cache.
167  */
168 static guint
169 export (GList **uplist, AtkObject *accessible)
170 {
171   guint ref;
172   gchar *path;
173
174   g_assert(ATK_IS_OBJECT(accessible));
175
176   ref = assign_reference();
177
178   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible);
179   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref));
180   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
181
182   *uplist = g_list_prepend (*uplist, accessible);
183
184   return ref;
185 }
186
187 /*
188  * Exports all the dependencies of an AtkObject.
189  * This is the subtree and the ancestors.
190  *
191  * Dependencies are the objects that get included in the
192  * cache, and therefore need to be registered before the
193  * update signal is sent.
194  *
195  * This does a depth first traversal of a subtree of AtkObject
196  * and exports them as Accessible objects if they are not exported
197  * already.
198  *
199  * It exports all ancestors of the object if they are not
200  * exported already.
201  */
202 static guint
203 export_deps (AtkObject *accessible)
204 {
205   AtkObject *current, *tmp;
206   GQueue    *stack;
207   GList     *uplist = NULL;
208   guint      i, ref;
209   gboolean   recurse;
210
211
212   /* Export subtree including object itself */
213   /*========================================*/
214   ref = atk_dbus_object_to_ref (accessible);
215   if (ref)
216       return ref;
217
218   stack = g_queue_new ();
219
220   current = g_object_ref (accessible);
221   ref = export (&uplist, current);
222   g_queue_push_head (stack, GINT_TO_POINTER (0));
223
224   /*
225    * The index held on the stack is the next child node
226    * that needs processing at the corresponding level in the tree.
227    */
228   while (!g_queue_is_empty (stack))
229     {
230       /* This while loop finds the next node that needs processing,
231        * if one exists.
232        */
233       i = GPOINTER_TO_INT(g_queue_peek_head (stack));
234       recurse = FALSE;
235       while (i < atk_object_get_n_accessible_children (current) &&
236              recurse == FALSE)
237         {
238           tmp = atk_object_ref_accessible_child (current, i);
239           if (!atk_dbus_object_to_ref (tmp))
240             {
241               recurse = TRUE;
242             }
243           else
244             {
245               i++;
246               g_object_unref (G_OBJECT (tmp));
247             }
248         }
249       if (recurse)
250         {
251           /* Still children to process */
252           current = tmp;
253           export (&uplist, current);
254           /* Update parent nodes next child index */
255           g_queue_peek_head_link (stack)->data = GINT_TO_POINTER (i+1);
256           /* Push a new child index for the current node */
257           g_queue_push_head (stack, GINT_TO_POINTER (0));
258         }
259       else
260         {
261           /* No more children, move to parent */
262           tmp = current;
263           current = atk_object_get_parent (current);
264           g_object_unref (G_OBJECT (tmp));
265           g_queue_pop_head (stack);
266         }
267     }
268
269   /* Export all neccessary ancestors of the object */
270   /*===============================================*/
271   current = atk_object_get_parent (accessible);
272   while (current && !atk_dbus_object_to_ref (current))
273     {
274       export (&uplist, current);
275     }
276
277   spi_emit_cache_update (uplist, atk_adaptor_app_data->bus);
278   g_list_free (uplist);
279   return ref;
280 }
281
282 /*---------------------------------------------------------------------------*/
283
284 /* Called to register an AtkObject with AT-SPI and expose it over D-Bus. */
285 guint
286 atk_dbus_register_accessible (AtkObject *accessible)
287 {
288   guint ref;
289   g_assert(ATK_IS_OBJECT(accessible));
290
291   return export_deps (accessible);
292 }
293
294 /* Called when an already registered object is updated in such a
295  * way that client side cache needs to be updated.
296  */
297 guint
298 atk_dbus_update_accessible (AtkObject *accessible)
299 {
300   guint  ref = 0;
301   GList *uplist = NULL;
302   g_assert(ATK_IS_OBJECT(accessible));
303
304   ref = atk_dbus_object_to_ref (accessible);
305   if (ref)
306     {
307       uplist = g_list_prepend (uplist, accessible);
308       spi_emit_cache_update (uplist, atk_adaptor_app_data->bus);
309       g_list_free (uplist);
310     }
311   return ref;
312 }
313
314 /*---------------------------------------------------------------------------*/
315
316 /*
317  * Returns the reference of the object, or 0 if it is not exported over D-Bus.
318  */
319 guint
320 atk_dbus_object_to_ref (AtkObject *accessible)
321 {
322   return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
323 }
324
325 /*
326  * Converts the Accessible object reference to its D-Bus object path
327  */
328 gchar *
329 atk_dbus_ref_to_path (guint ref)
330 {
331   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref);
332 }
333
334 /*
335  * Used to lookup an AtkObject from its D-Bus path.
336  */
337 AtkObject *
338 atk_dbus_path_to_object (const char *path)
339 {
340   guint index;
341   void *data;
342
343   g_assert (path);
344
345   if (strncmp(path, ATK_BRIDGE_OBJECT_PATH_PREFIX, ATK_BRIDGE_PATH_PREFIX_LENGTH) != 0) 
346     return NULL;
347
348   path += ATK_BRIDGE_PATH_PREFIX_LENGTH; /* Skip over the prefix */
349
350   if (path[0] == '\0')
351      return atk_get_root();
352   if (path[0] != '/')
353      return NULL;
354
355   path++;
356   index = atoi (path);
357   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
358   if (data)
359     return ATK_OBJECT (data);
360   else
361     return NULL;
362 }
363
364
365 /*
366  * Used to lookup a D-Bus path from the AtkObject.
367  */
368 gchar *
369 atk_dbus_object_to_path (AtkObject *accessible)
370 {
371   guint ref;
372   g_assert(ATK_IS_OBJECT(accessible));
373
374   ref = atk_dbus_object_to_ref (accessible);
375   if (!ref)
376       return NULL;
377   else
378       return atk_dbus_ref_to_path (ref);
379 }
380
381 /*---------------------------------------------------------------------------*/
382
383 /*
384  * Initializes required global data. The update and removal lists
385  * and the reference lookup tables.
386  *
387  * Initializes all of the required D-Bus interfaces.
388  */
389 void
390 atk_dbus_initialize (AtkObject *root)
391 {
392   if (!ref2ptr)
393     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
394
395   /* Get the root accessible and add */
396   atk_dbus_register_accessible (root);
397 }
398
399 /*END------------------------------------------------------------------------*/
400