2008-08-18 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / atk-adaptor / tree.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 2001, 2002 Sun Microsystems Inc.,
7  * Copyright 2001, 2002 Ximian, Inc.
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 #include <string.h>
26 #include <droute/introspect-loader.h>
27
28 #include "accessible.h"
29
30 #define get_object(message) spi_dbus_get_object(dbus_message_get_path(message))
31
32 static dbus_bool_t
33 append_update (DBusMessageIter * iter_array, AtkObject * obj,
34                              dbus_bool_t include_children, DRouteData * data)
35 {
36   DBusMessageIter iter_struct, iter_sub_array;
37   char *path = NULL;
38   char *path_parent;
39   const char *name, *desc;
40   int i;
41   dbus_uint32_t role;
42   AtkObject *parent = NULL;
43
44   gint childcount;
45   GSList *l;
46
47   g_assert(data != NULL);
48
49   parent = atk_object_get_parent(obj);
50   if (parent == NULL)
51     {
52       path_parent = g_strdup("/");
53     }
54   else
55     {
56       path_parent = spi_dbus_get_path (parent);
57     }
58
59   dbus_message_iter_open_container (iter_array, DBUS_TYPE_STRUCT, NULL,
60                                     &iter_struct);
61   path = spi_dbus_get_path (obj);
62   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_OBJECT_PATH, &path);
63   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_OBJECT_PATH, &path_parent);
64   g_free(path_parent);
65   dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "o",
66                                     &iter_sub_array);
67   childcount = atk_object_get_n_accessible_children (obj);
68   for (i = 0; i < childcount; i++)
69     {
70       AtkObject *child = atk_object_ref_accessible_child (obj, i);
71       char *child_path = spi_dbus_get_path (child);
72       if (child_path)
73         {
74           dbus_message_iter_append_basic (&iter_sub_array,
75                                           DBUS_TYPE_OBJECT_PATH, &child_path);
76           g_free (child_path);
77         }
78       if (child)
79         g_object_unref (child);
80     }
81   if (!dbus_message_iter_close_container (&iter_struct, &iter_sub_array))
82     goto oom;
83   dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "s",
84                                     &iter_sub_array);
85   if (data) for (l = data->interfaces; l; l = g_slist_next (l))
86     {
87       DRouteInterface *iface_def = (DRouteInterface *) l->data;
88       void *datum = NULL;
89       if (iface_def->get_datum)
90         {
91           datum = (*iface_def->get_datum) (path, data->user_data);
92           if (!datum)
93             continue;
94         }
95       dbus_message_iter_append_basic (&iter_sub_array, DBUS_TYPE_STRING,
96                                       &iface_def->name);
97       if (iface_def->free_datum)
98         (*iface_def->free_datum) (datum);
99     }
100   if (!dbus_message_iter_close_container (&iter_struct, &iter_sub_array))
101     goto oom;
102   name = atk_object_get_name (obj);
103   if (!name)
104     name = "";
105   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &name);
106   role = spi_accessible_role_from_atk_role (atk_object_get_role (obj));
107   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_UINT32, &role);
108   desc = atk_object_get_description (obj);
109   if (!desc)
110     desc = "";
111   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &desc);
112   if (!dbus_message_iter_close_container (iter_array, &iter_struct))
113     goto oom;
114   if (!include_children) childcount = 0;
115   for (i = 0; i < childcount; i++)
116     {
117       AtkObject *child = atk_object_ref_accessible_child (obj, i);
118       dbus_bool_t result;
119       if (!child)
120         continue;
121       result = append_update (iter_array, child, TRUE, data);
122       g_object_unref (child);
123       if (!result)
124         goto oom;
125     }
126   g_free (path);
127   return TRUE;
128 oom:
129   if (path) g_free(path);
130   return FALSE;
131 }
132
133 dbus_bool_t
134 spi_dbus_append_tree (DBusMessage * message, AtkObject * obj,
135                       DRouteData * data)
136 {
137   DBusMessageIter iter, iter_array;
138   dbus_bool_t result;
139
140   g_assert(data != NULL);
141
142   dbus_message_iter_init_append (message, &iter);
143   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
144                                     &iter_array);
145   result = append_update (&iter_array, obj, TRUE, data);
146   if (result)
147     result = dbus_message_iter_close_container (&iter, &iter_array);
148   return result;
149 }
150
151 static DBusMessage *
152 impl_getRoot (DBusConnection * bus, DBusMessage * message, void *user_data)
153 {
154   AtkObject *root = atk_get_root();
155   char *path;
156   DBusMessage *reply;
157
158   if (root) path = spi_dbus_get_path(root);
159   if (!root || !path)
160     return spi_dbus_general_error (message);
161   reply = dbus_message_new_method_return (message);
162   dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &path,
163                             DBUS_TYPE_INVALID);
164   g_free (path);
165   return reply;
166 }
167
168 static DBusMessage *
169 impl_getTree (DBusConnection * bus, DBusMessage * message, void *user_data)
170 {
171   DBusMessage *reply;
172   AtkObject *root = atk_get_root();
173
174   if (!root) return spi_dbus_general_error(message);
175   reply = dbus_message_new_method_return (message);
176   spi_dbus_append_tree (reply, root, (DRouteData *) user_data);
177   return reply;
178 }
179
180 static DBusMessage *
181 impl_introspect (DBusConnection *bus, DBusMessage *message, void *user_data)
182 {
183   const char *path;
184   GString *output;
185   char *final;
186
187   DBusMessage *reply;
188
189   path = dbus_message_get_path(message);
190
191   output = g_string_new(spi_introspection_header);
192   
193   g_string_append_printf(output, spi_introspection_node_element, path);
194
195   spi_append_interface(output, SPI_DBUS_INTERFACE_TREE);
196
197   g_string_append(output, spi_introspection_footer);
198   final = g_string_free(output, FALSE);
199
200   reply = dbus_message_new_method_return (message);
201   g_assert(reply != NULL);
202   dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
203                            DBUS_TYPE_INVALID);
204
205   g_free(final);
206   return reply;
207 }
208
209 static DBusHandlerResult
210 message_handler (DBusConnection *bus, DBusMessage *message, void *user_data)
211 {
212   const char *iface = dbus_message_get_interface (message);
213   const char *member = dbus_message_get_member (message);
214
215   DBusMessage *reply = NULL;
216
217   g_return_val_if_fail(iface != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
218   
219   if (!strcmp(iface, SPI_DBUS_INTERFACE_TREE))
220     {
221       if (!strcmp(member, "getRoot"))
222         {
223           reply = impl_getRoot(bus, message, user_data);
224         }
225
226       if (!strcmp(member, "getTree"))
227         {
228           reply = impl_getTree(bus, message, user_data);
229         }
230     }
231
232   if (!strcmp(iface, "org.freedesktop.DBus.Introspectable"))
233     {
234       if (!strcmp(member, "Introspect"))
235         {
236           reply = impl_introspect(bus, message, user_data);
237         }
238     }
239
240   if (reply)
241     {
242       dbus_connection_send (bus, reply, NULL);
243       dbus_message_unref (reply);
244     }
245
246   return DBUS_HANDLER_RESULT_HANDLED;
247 }
248
249 static DBusObjectPathVTable tree_vtable =
250 {
251   NULL,
252   &message_handler,
253   NULL, NULL, NULL, NULL
254 };
255
256 void
257 spi_register_tree_object(DBusConnection *bus,
258                          DRouteData *data,
259                          const char *path)
260 {
261   dbus_bool_t mem = FALSE;
262   mem = dbus_connection_register_object_path(bus, path, &tree_vtable, data);
263   g_assert(mem == TRUE);
264 }
265
266 static GHashTable *cache_list;
267
268 #define UPDATE_NEW     1
269 #define UPDATE_REFRESH 2
270 #define UPDATE_REMOVE 3
271
272 static int update_pending = 0;
273 static gint update_pending_id;
274
275 typedef struct
276 {
277   DBusMessageIter iter;
278   DRouteData *droute;
279   gboolean removing;
280 } CacheIterData;
281
282 static void handle_cache_item(char *path, guint action, CacheIterData *d)
283 {
284   AtkObject *obj;
285
286   switch (action)
287   {
288   case UPDATE_NEW:
289   case UPDATE_REFRESH:
290   default:
291     if (d->removing) return;
292     obj = spi_dbus_get_object(path);
293 //printf("update %s\n", path);
294     append_update(&d->iter, obj, FALSE, d->droute);
295     break;
296   case UPDATE_REMOVE:
297 //printf("remove: %s\n", path);
298     if (!d->removing) return;
299     dbus_message_iter_append_basic(&d->iter, DBUS_TYPE_OBJECT_PATH, &path);
300     break;
301   }
302   g_hash_table_remove(cache_list, path);
303 }
304
305 gboolean
306 spi_dbus_update_cache(DRouteData *data)
307 {
308   DBusMessage *message;
309   DBusMessageIter iter;
310   CacheIterData d;
311   static gboolean in_update_cache = FALSE;
312
313   if (in_update_cache) return TRUE;
314   g_assert(data != NULL);
315
316   if (update_pending == 0) return FALSE;
317 //printf("Sending cache\n");
318   message = dbus_message_new_signal ("/org/freedesktop/atspi/tree", SPI_DBUS_INTERFACE_TREE, "updateTree");
319   if (!message) goto done;
320   dbus_message_iter_init_append (message, &iter);
321   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
322                                     &d.iter);
323   d.droute = data;
324   d.removing = FALSE;
325   in_update_cache = TRUE;
326   do
327   {
328     /* This loop is needed because appending an item may cause new children
329      * to be registered and consequently added to the hash, so they, too,
330      * will need to be sent with the update */
331     update_pending = 0;
332     g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
333   } while (update_pending);
334   dbus_message_iter_close_container(&iter, &d.iter);
335   dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "o", &d.iter);
336   d.removing = TRUE;
337   g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
338   in_update_cache = FALSE;
339   dbus_message_iter_close_container(&iter, &d.iter);
340   dbus_connection_send(data->bus, message, NULL);
341 done:
342   return FALSE;
343 }
344
345 void spi_dbus_notify_change(AtkObject *obj, gboolean new, DRouteData *data)
346 {
347   guint action = (new? UPDATE_NEW: UPDATE_REFRESH);
348   char *path = spi_dbus_get_path(obj);
349
350   if (!cache_list)
351   {
352     cache_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
353     if (!cache_list)
354     {
355       g_free(path);
356       return;
357     }
358   }
359   if (g_hash_table_lookup(cache_list, path))
360   {
361     g_free(path);
362     return;
363   }
364 //printf("change: %s\n", path);
365   g_hash_table_insert(cache_list, path, (gpointer)action);
366   if (update_pending != 2 && data)
367   {
368     update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
369     update_pending = 2;
370   }
371   else if (!update_pending) update_pending = 1;
372 }
373
374 void spi_dbus_notify_remove(AtkObject *obj, DRouteData *data)
375 {
376   guint action = UPDATE_REMOVE;
377   guint cur_action;
378   gchar *path = spi_dbus_get_path(obj);
379
380 //printf("notify remove: %s\n", path);
381   if (!cache_list)
382   {
383     g_free(path);
384     return;
385   }
386   cur_action = (guint)g_hash_table_lookup(cache_list, path);
387   if (cur_action == UPDATE_NEW)
388   {
389     /* No one knew that this object ever existed, so just remove it */
390 //printf("Removing object from send queue\n");
391     g_hash_table_remove(cache_list, path);
392     g_free(path);
393   }
394   else
395   {
396     g_hash_table_insert(cache_list, path, (gpointer)action);
397     if (update_pending != 2 && data)
398     {
399       update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
400       update_pending = 2;
401     }
402     else if (!update_pending) update_pending = 1;
403   }
404 }
405
406 gboolean spi_dbus_object_is_known(AtkObject *obj)
407 {
408   guint cur_action;
409   char *path = spi_dbus_get_path(obj);
410   if (!path) return FALSE;
411   cur_action = (guint)g_hash_table_lookup(cache_list, path);
412   g_free(path);
413   return (cur_action != UPDATE_NEW);
414 }