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