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 each AtkObject that has changed since the
60  * last update. It should also provide a list of objects that have been
61  * removed since the last update.
62  */
63
64 GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
65
66 static guint counter = 1;
67
68 /*---------------------------------------------------------------------------*/
69
70 /*
71  * Each AtkObject must be asssigned a D-Bus path (Reference)
72  *
73  * This function provides an integer reference for a new
74  * AtkObject.
75  */
76 static guint
77 assign_reference(void)
78 {
79   counter++;
80   /* Reference of 0 not allowed as used as direct key in hash table */
81   if (counter == 0)
82     counter++;
83 }
84
85 /*---------------------------------------------------------------------------*/
86
87 void
88 atk_dbus_foreach_registered(GHFunc func, gpointer data)
89 {
90   g_hash_table_foreach(ref2ptr, func, data);
91 }
92
93 /*---------------------------------------------------------------------------*/
94
95 /*
96  * Called when a registered AtkObject is deleted.
97  * Removes the AtkObject from the reference lookup tables.
98  * Sets the client side cache to be updated.
99  */
100 static void
101 deregister_accessible(gpointer data, GObject *accessible)
102 {
103   guint ref;
104   gchar *path;
105
106   g_assert(ATK_IS_OBJECT(accessible));
107
108
109   ref = atk_dbus_object_to_ref (ATK_OBJECT(accessible));
110
111   if (ref != 0)
112     {
113       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
114       /*
115        * TODO
116        * Pyatspi client side exceptions have occured indicating
117        * that an object has been removed twice.
118        * This should not be possible and needs investigation.
119        */
120       spi_emit_cache_removal (ref, atk_adaptor_app_data->bus);
121     }
122 }
123
124 /*---------------------------------------------------------------------------*/
125
126 /*
127  * This function registers the object so that it is exported
128  * over D-Bus and schedules an update to client side cache.
129  */
130 static guint
131 export (GList **uplist, AtkObject *accessible)
132 {
133   guint ref;
134   gchar *path;
135
136   g_assert(ATK_IS_OBJECT(accessible));
137
138   ref = assign_reference();
139
140   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(ref), accessible);
141   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(ref));
142   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
143
144   *uplist = g_list_prepend (*uplist, accessible);
145
146   return ref;
147 }
148
149 /*
150  * This does a depth first traversal of a subtree of AtkObject
151  * and exports them as Accessible objects if they are not exported
152  * already.
153  */
154 static guint
155 export_subtree (AtkObject *accessible)
156 {
157   AtkObject *current, *tmp;
158   GQueue    *stack;
159   GList     *uplist = NULL;
160   guint      i, ref;
161   gboolean   recurse;
162
163   stack = g_queue_new ();
164
165   current = g_object_ref (accessible);
166   ref = export (&uplist, current);
167   g_queue_push_head (stack, GINT_TO_POINTER (0));
168
169   /*
170    * The index held on the stack is the next child node
171    * that needs processing at the corresponding level in the tree.
172    */
173   while (!g_queue_is_empty (stack))
174     {
175       /* This while loop finds the next node that needs processing,
176        * if one exists.
177        */
178       i = GPOINTER_TO_INT(g_queue_peek_head (stack));
179       recurse = FALSE;
180       while (i < atk_object_get_n_accessible_children (current) &&
181              recurse == FALSE)
182         {
183           tmp = atk_object_ref_accessible_child (current, i);
184           if (!atk_dbus_object_to_ref (tmp))
185             {
186               recurse = TRUE;
187             }
188           else
189             {
190               i++;
191               g_object_unref (G_OBJECT (tmp));
192             }
193         }
194       if (recurse)
195         {
196           /* Still children to process */
197           current = tmp;
198           export (&uplist, current);
199           /* Update parent nodes next child index */
200           g_queue_peek_head_link (stack)->data = GINT_TO_POINTER (i+1);
201           /* Push a new child index for the current node */
202           g_queue_push_head (stack, GINT_TO_POINTER (0));
203         }
204       else
205         {
206           /* No more children, move to parent */
207           tmp = current;
208           current = atk_object_get_parent (current);
209           g_object_unref (G_OBJECT (tmp));
210           g_queue_pop_head (stack);
211         }
212     }
213   spi_emit_cache_update (uplist, atk_adaptor_app_data->bus);
214   g_list_free (uplist);
215   return ref;
216 }
217
218 /*---------------------------------------------------------------------------*/
219
220 /* Called to register an AtkObject with AT-SPI and expose it over D-Bus. */
221 guint
222 atk_dbus_register_accessible (AtkObject *accessible)
223 {
224   guint ref;
225   g_assert(ATK_IS_OBJECT(accessible));
226
227   ref = atk_dbus_object_to_ref (accessible);
228   if (!ref)
229       return export_subtree (accessible);
230   else
231       return ref;
232 }
233
234 /* Called when an already registered object is updated in such a
235  * way that client side cache needs to be updated.
236  */
237 guint
238 atk_dbus_update_accessible (AtkObject *accessible)
239 {
240   guint ref = 0;
241   g_assert(ATK_IS_OBJECT(accessible));
242
243   ref = atk_dbus_object_to_ref (accessible);
244   if (ref)
245     {
246       spi_emit_cache_update (accessible, atk_adaptor_app_data->bus);
247     }
248   return ref;
249 }
250
251 /*---------------------------------------------------------------------------*/
252
253 /*
254  * Returns the reference of the object, or 0 if it is not exported over D-Bus.
255  */
256 guint
257 atk_dbus_object_to_ref (AtkObject *accessible)
258 {
259   return GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
260 }
261
262 /*
263  * Converts the Accessible object reference to its D-Bus object path
264  */
265 gchar *
266 atk_dbus_ref_to_path (guint ref)
267 {
268   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref);
269 }
270
271 /*
272  * Used to lookup an AtkObject from its D-Bus path.
273  */
274 AtkObject *
275 atk_dbus_path_to_object (const char *path)
276 {
277   guint index;
278   void *data;
279
280   g_assert (path);
281
282   if (strncmp(path, ATK_BRIDGE_OBJECT_PATH_PREFIX, ATK_BRIDGE_PATH_PREFIX_LENGTH) != 0) 
283     return NULL;
284
285   path += ATK_BRIDGE_PATH_PREFIX_LENGTH; /* Skip over the prefix */
286
287   if (path[0] == '\0')
288      return atk_get_root();
289   if (path[0] != '/')
290      return NULL;
291
292   path++;
293   index = atoi (path);
294   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
295   if (data)
296     return ATK_OBJECT (data);
297   else
298     return NULL;
299 }
300
301
302 /*
303  * Used to lookup a D-Bus path from the AtkObject.
304  */
305 gchar *
306 atk_dbus_object_to_path (AtkObject *accessible)
307 {
308   guint ref;
309   g_assert(ATK_IS_OBJECT(accessible));
310
311   ref = atk_dbus_object_to_ref (accessible);
312   if (!ref)
313       return NULL;
314   else
315       return atk_dbus_ref_to_path (ref);
316 }
317
318 /*---------------------------------------------------------------------------*/
319
320 /*
321  * Initializes required global data. The update and removal lists
322  * and the reference lookup tables.
323  *
324  * Initializes all of the required D-Bus interfaces.
325  */
326 void
327 atk_dbus_initialize (AtkObject *root)
328 {
329   if (!ref2ptr)
330     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
331
332   /* Get the root accessible and add */
333   atk_dbus_register_accessible (root);
334 }
335
336 /*END------------------------------------------------------------------------*/
337