2009-01-11 Mike Gorse <mgorse@novell.com>
[platform/core/uifw/at-spi2-atk.git] / atk-adaptor / atk-dbus.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  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "accessible.h"
28
29 /* TODO 
30  * Need to add concurrency support.
31  */
32
33 #define ATK_BRIDGE_OBJECT_PATH_PREFIX "/org/freedesktop/atspi/accessible"
34 #define ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE ATK_BRIDGE_OBJECT_PATH_PREFIX "/%d"
35 #define ATK_BRIDGE_PATH_PREFIX_LENGTH 33
36
37 /*
38  * This module is responsible for keeping track of all the AtkObjects in
39  * the application, so that they can be accessed remotely and placed in
40  * a client side cache.
41  *
42  * To access an AtkObject remotely we need to provide a D-Bus object 
43  * path for it. The D-Bus object paths used have a standard prefix
44  * (ATK_BRIDGE_OBJECT_PATH_PREFIX). Appended to this prefix is a string
45  * representation of an integer reference. So to access an AtkObject 
46  * remotely we keep a Hashtable that maps the given reference to 
47  * the AtkObject pointer. An object in this hash table is said to be 'registered'.
48  *
49  * The architecture of AT-SPI dbus is such that AtkObjects are not
50  * remotely reference counted. This means that we need to keep track of
51  * object destruction. When an object is destroyed it must be 'deregistered'
52  * To do this lookup we keep a dbus-id attribute on each AtkObject.
53  *
54  * In addition to accessing the objects remotely we must be able to update
55  * the client side cache. This is done using the 'update' signal of the 
56  * org.freedesktop.atspi.Tree interface. The update signal should send out
57  * all of the cacheable data for each AtkObject that has changed since the
58  * last update. It should also provide a list of objects that have been
59  * removed since the last update.
60  * 
61  * To enbale this two hash tables are kept. One keeps a list of AtkObjects
62  * that have been updated. The other a list of objects that have been
63  * removed since the last 'update' signal. The reason they are
64  * stored as hash tables is to ensure that if an AtkObject is removed
65  * and then added between update signals then the object is easy to delete
66  * from the 'update' list without doing a costly lookup.
67  *
68  * The mappings will be called 'reference lookup tables'
69  * The hashtable containing AtkObjects that need updating in the client side
70  * cache will be called the 'update list'
71  * The hashtable containing the AtkObjects that need removing from the client
72  * side cache will be called the 'removal list'
73  */
74
75 GHashTable *ref2ptr = NULL; /* Used for converting a D-Bus path (Reference) to the object pointer */
76
77 GHashTable *update_list = NULL; /* Stores the objects that need a client side cache update */
78 GHashTable *remove_list = NULL; /* Stores the objects that need to be removed from the client side cache */
79
80 static guint counter = 1;
81
82 /*---------------------------------------------------------------------------*/
83
84 /*
85  * Each AtkObject must be asssigned a D-Bus path (Reference)
86  *
87  * This function provides an integer reference for a new
88  * AtkObject.
89  */
90 static guint
91 assign_reference(void)
92 {
93   counter++;
94   /* Reference of 0 not allowed as used as direct key in hash table */
95   if (counter == 0)
96     counter++;
97     return counter;
98 }
99
100 /*---------------------------------------------------------------------------*/
101
102 /*
103  * Called when a registered AtkObject is deleted.
104  *
105  * Removes the AtkObject from the reference lookup tables.
106  * Adds the reference of the object to the removal list.
107  */
108 static void
109 deregister_accessible(gpointer data, GObject *accessible)
110 {
111   guint ref;
112
113   g_assert(ATK_IS_OBJECT(accessible));
114
115
116   ref = GPOINTER_TO_INT(g_object_get_data (accessible, "dbus-id"));
117
118   /* Remove from update list */
119   g_hash_table_remove(update_list, GINT_TO_POINTER(ref));
120
121   if (ref != 0)
122     {
123       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
124       /* Add to removal list */
125       /* 
126        * TODO
127        * Pyatspi client side exceptions have occured indicating
128        * that an object has been removed twice.
129        * This should not be possible and needs investigation.
130        */
131       g_hash_table_insert(remove_list, GINT_TO_POINTER(ref), NULL);
132     }
133
134   atk_tree_cache_needs_update();
135 }
136
137 /*---------------------------------------------------------------------------*/
138
139 /*
140  * Registers a new AtkObject.
141  *
142  * Adds the AtkObject to the reference lookup tables.
143  * Adds the AtkObject to the update list.
144  */
145 static guint
146 register_accessible (AtkObject *accessible)
147 {
148   guint reference;
149
150   g_assert(ATK_IS_OBJECT(accessible));
151
152   reference = assign_reference();
153
154   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(reference), accessible);
155   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(reference));
156   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
157
158   /* Add to update list */
159   g_hash_table_insert (update_list, GINT_TO_POINTER(reference), accessible);
160
161   atk_tree_cache_needs_update();
162
163   return reference;
164 }
165
166 /*---------------------------------------------------------------------------*/
167
168 /* TODO Turn these into an iterator API - Think about the locking */
169
170 void
171 atk_dbus_foreach_registered(GHFunc func, gpointer data)
172 {
173   g_hash_table_foreach(ref2ptr, func, data);
174 }
175
176 /*---------------------------------------------------------------------------*/
177
178 void
179 atk_dbus_foreach_update_list(GHFunc func, gpointer data)
180 {
181   g_hash_table_foreach(update_list, func, data);
182   g_hash_table_remove_all(update_list);
183 }
184
185 /*---------------------------------------------------------------------------*/
186
187 void
188 atk_dbus_foreach_remove_list(GHFunc func, gpointer data)
189 {
190   g_hash_table_foreach(remove_list, func, data);
191   g_hash_table_remove_all(remove_list);
192 }
193
194 /*---------------------------------------------------------------------------*/
195
196 /* 
197  * Called on an AtkObject when it has changed in such a way
198  * that the client side cache of the object will need updating.
199  */
200 void 
201 atk_dbus_notify_change(AtkObject *accessible)
202 {
203   guint ref;
204   g_assert(ATK_IS_OBJECT(accessible));
205   
206   if (!g_object_get_data (G_OBJECT (accessible), "dbus-id"))
207     {
208       register_accessible(accessible);
209     }
210   else
211     {
212       ref = g_object_get_data (G_OBJECT (accessible), "dbus-id");
213       g_hash_table_insert (update_list, ref, accessible);
214       atk_tree_cache_needs_update();
215     }
216 }
217
218 /*---------------------------------------------------------------------------*/
219
220 /*
221  * Used to lookup an AtkObject from its D-Bus path.
222  */
223 AtkObject *
224 atk_dbus_get_object (const char *path)
225 {
226   guint index;
227   void *data;
228
229   g_assert (path);
230  
231   if (strncmp(path, ATK_BRIDGE_OBJECT_PATH_PREFIX, ATK_BRIDGE_PATH_PREFIX_LENGTH) != 0) 
232     return NULL;
233
234   path += ATK_BRIDGE_PATH_PREFIX_LENGTH; /* Skip over the prefix */
235
236   if (path[0] == '\0')
237      return atk_get_root();
238   if (path[0] != '/')
239      return NULL;
240
241   path++;
242   index = atoi (path);
243   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
244   if (data)
245     return ATK_OBJECT (data);
246   else
247     return NULL;
248 }
249
250 /*---------------------------------------------------------------------------*/
251
252 gchar *
253 atk_dbus_get_path_from_ref(guint ref)
254 {
255   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref);
256 }
257
258 /*---------------------------------------------------------------------------*/
259
260 /*
261  * Used to lookup a D-Bus path from the AtkObject.
262  *
263  * Objects without a path are registered and provided with one.
264  */
265 gchar *
266 atk_dbus_get_path (AtkObject *accessible)
267 {
268   guint index;
269
270   g_assert (accessible);
271
272   index = GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
273   if (!index)
274     index = register_accessible(G_OBJECT(accessible));
275
276   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, index);
277 }
278
279 /*---------------------------------------------------------------------------*/
280
281 /*
282  * Used to recursively register accessibles.
283  *
284  * When children are added to an accessible we need to 
285  * iterate over the new subtree provided to register new accessibles.
286  */
287 guint
288 atk_dbus_register_subtree(AtkObject *accessible)
289 {
290   AtkObject *child;
291   guint i, n_children;
292   guint ref;
293
294   ref = GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
295   if (!ref)
296      ref = register_accessible(accessible);
297
298   n_children = atk_object_get_n_accessible_children(accessible);
299   for (i=0; i < n_children; i++)
300     {
301       child = atk_object_ref_accessible_child(accessible, i);
302       atk_dbus_register_subtree(child);
303     } 
304   return ref;
305
306
307 /*---------------------------------------------------------------------------*/
308
309 /* 
310  * Marshals a single object into a D-Bus message.
311  *
312  * Unrefs the AtkObject if unref is true.
313  */
314 DBusMessage *
315 spi_dbus_return_object (DBusMessage *message, AtkObject *obj, gboolean unref)
316 {
317   DBusMessage *reply;
318   gchar *path;
319   
320   path = atk_dbus_get_path (obj);
321
322   if (unref)
323     g_object_unref (obj);
324
325   reply = dbus_message_new_method_return (message);
326   if (reply)
327     {
328       dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, path,
329                                 DBUS_TYPE_INVALID);
330     }
331   return reply;
332 }
333
334 /*---------------------------------------------------------------------------*/
335
336 /*
337  * Marshals a variant containing the object reference into the message iter
338  * provided.
339  *
340  * Unrefs the object if unref is true.
341  */
342 dbus_bool_t
343 spi_dbus_return_v_object (DBusMessageIter *iter, AtkObject *obj, int unref)
344 {
345   char *path;
346   
347   path = atk_dbus_get_path (obj);
348
349   if (unref)
350     g_object_unref (obj);
351
352   return droute_return_v_object (iter, path);
353 }
354
355 /*---------------------------------------------------------------------------*/
356
357 /*
358  * Initializes required global data. The update and removal lists
359  * and the reference lookup tables.
360  *
361  * Initializes all of the required D-Bus interfaces.
362  */
363 void
364 atk_dbus_initialize (DRouteData * data)
365 {
366   if (!ref2ptr)
367     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
368   if (!update_list)
369     update_list = g_hash_table_new(g_direct_hash, g_direct_equal);
370   if (!remove_list)
371     remove_list = g_hash_table_new(g_direct_hash, g_direct_equal);
372
373   /* Get the root accessible and add */
374   atk_dbus_register_subtree(atk_get_root());
375
376   spi_initialize_accessible (data);
377   spi_initialize_action(data);
378   spi_initialize_collection (data);
379   spi_initialize_component (data);
380   spi_initialize_document (data);
381   spi_initialize_editabletext (data);
382   spi_initialize_hyperlink (data);
383   spi_initialize_hypertext (data);
384   spi_initialize_image (data);
385   spi_initialize_selection (data);
386   spi_initialize_table (data);
387   spi_initialize_text (data);
388   spi_initialize_value (data);
389   spi_initialize_introspectable(data, (DRouteGetDatumFunction) atk_dbus_get_object);
390 }
391
392 /*END------------------------------------------------------------------------*/
393