94b8a8a962bf05257a1dfb7bab5557063a057f70
[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 }
98
99 /*---------------------------------------------------------------------------*/
100
101 /*
102  * Called when a registered AtkObject is deleted.
103  *
104  * Removes the AtkObject from the reference lookup tables.
105  * Adds the reference of the object to the removal list.
106  */
107 static void
108 deregister_accessible(gpointer data, GObject *accessible)
109 {
110   guint ref;
111
112   g_assert(ATK_IS_OBJECT(accessible));
113
114
115   ref = GPOINTER_TO_INT(g_object_get_data (accessible, "dbus-id"));
116
117   /* Remove from update list */
118   g_hash_table_remove(update_list, GINT_TO_POINTER(ref));
119
120   if (ref != 0)
121     {
122       g_hash_table_remove(ref2ptr, GINT_TO_POINTER(ref));
123       /* Add to removal list */
124       /* 
125        * TODO
126        * Pyatspi client side exceptions have occured indicating
127        * that an object has been removed twice.
128        * This should not be possible and needs investigation.
129        */
130       g_hash_table_insert(remove_list, GINT_TO_POINTER(ref), NULL);
131     }
132
133   atk_tree_cache_needs_update();
134 }
135
136 /*---------------------------------------------------------------------------*/
137
138 /*
139  * Registers a new AtkObject.
140  *
141  * Adds the AtkObject to the reference lookup tables.
142  * Adds the AtkObject to the update list.
143  */
144 static guint
145 register_accessible (AtkObject *accessible)
146 {
147   guint reference;
148
149   g_assert(ATK_IS_OBJECT(accessible));
150
151   reference = assign_reference();
152
153   g_hash_table_insert (ref2ptr, GINT_TO_POINTER(reference), accessible);
154   g_object_set_data (G_OBJECT(accessible), "dbus-id", GINT_TO_POINTER(reference));
155   g_object_weak_ref(G_OBJECT(accessible), deregister_accessible, NULL);
156
157   /* Add to update list */
158   g_hash_table_insert (update_list, GINT_TO_POINTER(reference), accessible);
159
160   atk_tree_cache_needs_update();
161
162   return reference;
163 }
164
165 /*---------------------------------------------------------------------------*/
166
167 /* TODO Turn these into an iterator API - Think about the locking */
168
169 void
170 atk_dbus_foreach_registered(GHFunc func, gpointer data)
171 {
172   g_hash_table_foreach(ref2ptr, func, data);
173 }
174
175 /*---------------------------------------------------------------------------*/
176
177 void
178 atk_dbus_foreach_update_list(GHFunc func, gpointer data)
179 {
180   g_hash_table_foreach(update_list, func, data);
181   g_hash_table_remove_all(update_list);
182 }
183
184 /*---------------------------------------------------------------------------*/
185
186 void
187 atk_dbus_foreach_remove_list(GHFunc func, gpointer data)
188 {
189   g_hash_table_foreach(remove_list, func, data);
190   g_hash_table_remove_all(remove_list);
191 }
192
193 /*---------------------------------------------------------------------------*/
194
195 /* 
196  * Called on an AtkObject when it has changed in such a way
197  * that the client side cache of the object will need updating.
198  */
199 void 
200 atk_dbus_notify_change(AtkObject *accessible)
201 {
202   guint ref;
203   g_assert(ATK_IS_OBJECT(accessible));
204   
205   if (!g_object_get_data (G_OBJECT (accessible), "dbus-id"))
206     {
207       register_accessible(accessible);
208     }
209   else
210     {
211       ref = g_object_get_data (G_OBJECT (accessible), "dbus-id");
212       g_hash_table_insert (update_list, ref, accessible);
213       atk_tree_cache_needs_update();
214     }
215 }
216
217 /*---------------------------------------------------------------------------*/
218
219 /*
220  * Used to lookup an AtkObject from its D-Bus path.
221  */
222 AtkObject *
223 atk_dbus_get_object (const char *path)
224 {
225   guint index;
226   void *data;
227
228   g_assert (path);
229  
230   if (strncmp(path, ATK_BRIDGE_OBJECT_PATH_PREFIX, ATK_BRIDGE_PATH_PREFIX_LENGTH) != 0) 
231     return NULL;
232
233   path += ATK_BRIDGE_PATH_PREFIX_LENGTH; /* Skip over the prefix */
234
235   if (path[0] == '\0')
236      return atk_get_root();
237   if (path[0] != '/')
238      return NULL;
239
240   path++;
241   index = atoi (path);
242   data = g_hash_table_lookup (ref2ptr, GINT_TO_POINTER(index));
243   if (data)
244     return ATK_OBJECT (data);
245   else
246     return NULL;
247 }
248
249 /*---------------------------------------------------------------------------*/
250
251 gchar *
252 atk_dbus_get_path_from_ref(guint ref)
253 {
254   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, ref);
255 }
256
257 /*---------------------------------------------------------------------------*/
258
259 /*
260  * Used to lookup a D-Bus path from the AtkObject.
261  *
262  * Objects without a path are registered and provided with one.
263  */
264 gchar *
265 atk_dbus_get_path (AtkObject *accessible)
266 {
267   guint index;
268
269   g_assert (accessible);
270
271   index = GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
272   if (!index)
273     index = register_accessible(G_OBJECT(accessible));
274
275   return g_strdup_printf(ATK_BRIDGE_OBJECT_REFERENCE_TEMPLATE, index);
276 }
277
278 /*---------------------------------------------------------------------------*/
279
280 /*
281  * Used to recursively register accessibles.
282  *
283  * When children are added to an accessible we need to 
284  * iterate over the new subtree provided to register new accessibles.
285  */
286 guint
287 atk_dbus_register_subtree(AtkObject *accessible)
288 {
289   AtkObject *child;
290   guint i, n_children;
291   guint ref;
292
293   ref = GPOINTER_TO_INT(g_object_get_data (G_OBJECT (accessible), "dbus-id"));
294   if (!ref)
295      ref = register_accessible(accessible);
296
297   n_children = atk_object_get_n_accessible_children(accessible);
298   for (i=0; i < n_children; i++)
299     {
300       child = atk_object_ref_accessible_child(accessible, i);
301       atk_dbus_register_subtree(child);
302     } 
303   return ref;
304
305
306 /*---------------------------------------------------------------------------*/
307
308 /* 
309  * Marshals a single object into a D-Bus message.
310  *
311  * Unrefs the AtkObject if unref is true.
312  */
313 DBusMessage *
314 spi_dbus_return_object (DBusMessage *message, AtkObject *obj, gboolean unref)
315 {
316   DBusMessage *reply;
317   gchar *path;
318   
319   path = atk_dbus_get_path (obj);
320
321   if (unref)
322     g_object_unref (obj);
323
324   reply = dbus_message_new_method_return (message);
325   if (reply)
326     {
327       dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, path,
328                                 DBUS_TYPE_INVALID);
329     }
330   return reply;
331 }
332
333 /*---------------------------------------------------------------------------*/
334
335 /*
336  * Marshals a variant containing the object reference into the message iter
337  * provided.
338  *
339  * Unrefs the object if unref is true.
340  */
341 dbus_bool_t
342 spi_dbus_return_v_object (DBusMessageIter *iter, AtkObject *obj, int unref)
343 {
344   char *path;
345   
346   path = atk_dbus_get_path (obj);
347
348   if (unref)
349     g_object_unref (obj);
350
351   return droute_return_v_object (iter, path);
352 }
353
354 /*---------------------------------------------------------------------------*/
355
356 /*
357  * Initializes required global data. The update and removal lists
358  * and the reference lookup tables.
359  *
360  * Initializes all of the required D-Bus interfaces.
361  */
362 void
363 atk_dbus_initialize (DRouteData * data)
364 {
365   if (!ref2ptr)
366     ref2ptr = g_hash_table_new(g_direct_hash, g_direct_equal);
367   if (!update_list)
368     update_list = g_hash_table_new(g_direct_hash, g_direct_equal);
369   if (!remove_list)
370     remove_list = g_hash_table_new(g_direct_hash, g_direct_equal);
371
372   /* Get the root accessible and add */
373   atk_dbus_register_subtree(atk_get_root());
374
375   spi_initialize_accessible (data);
376   spi_initialize_action(data);
377   spi_initialize_collection (data);
378   spi_initialize_component (data);
379   spi_initialize_document (data);
380   spi_initialize_editabletext (data);
381   spi_initialize_hyperlink (data);
382   spi_initialize_hypertext (data);
383   spi_initialize_image (data);
384   spi_initialize_selection (data);
385   spi_initialize_table (data);
386   spi_initialize_text (data);
387   spi_initialize_value (data);
388   spi_initialize_introspectable(data, (DRouteGetDatumFunction) atk_dbus_get_object);
389 }
390
391 /*END------------------------------------------------------------------------*/
392