Merge branch 'mgorse' of git+ssh://git.codethink.co.uk/git/atspi-dbus into rework
[platform/upstream/at-spi2-core.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
43   gint childcount;
44   GSList *l;
45
46   dbus_message_iter_open_container (iter_array, DBUS_TYPE_STRUCT, NULL,
47                                     &iter_struct);
48   path = spi_dbus_get_path (obj);
49   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_OBJECT_PATH, &path);
50   path_parent = spi_dbus_get_path (atk_object_get_parent(obj));
51   if (!path_parent) path_parent = g_strdup("/");
52   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_OBJECT_PATH, &path_parent);
53   g_free(path_parent);
54   dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "o",
55                                     &iter_sub_array);
56   childcount = atk_object_get_n_accessible_children (obj);
57   for (i = 0; i < childcount; i++)
58     {
59       AtkObject *child = atk_object_ref_accessible_child (obj, i);
60       char *child_path = spi_dbus_get_path (child);
61       if (child_path)
62         {
63           dbus_message_iter_append_basic (&iter_sub_array,
64                                           DBUS_TYPE_OBJECT_PATH, &child_path);
65           g_free (child_path);
66         }
67       if (child)
68         g_object_unref (child);
69     }
70   if (!dbus_message_iter_close_container (&iter_struct, &iter_sub_array))
71     goto oom;
72   dbus_message_iter_open_container (&iter_struct, DBUS_TYPE_ARRAY, "s",
73                                     &iter_sub_array);
74   if (data) for (l = data->interfaces; l; l = g_slist_next (l))
75     {
76       DRouteInterface *iface_def = (DRouteInterface *) l->data;
77       void *datum = NULL;
78       if (iface_def->get_datum)
79         {
80           datum = (*iface_def->get_datum) (path, data->user_data);
81           if (!datum)
82             continue;
83         }
84       dbus_message_iter_append_basic (&iter_sub_array, DBUS_TYPE_STRING,
85                                       &iface_def->name);
86       if (iface_def->free_datum)
87         (*iface_def->free_datum) (datum);
88     }
89   if (!dbus_message_iter_close_container (&iter_struct, &iter_sub_array))
90     goto oom;
91   name = atk_object_get_name (obj);
92   if (!name)
93     name = "";
94   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &name);
95   role = spi_accessible_role_from_atk_role (atk_object_get_role (obj));
96   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_UINT32, &role);
97   desc = atk_object_get_description (obj);
98   if (!desc)
99     desc = "";
100   dbus_message_iter_append_basic (&iter_struct, DBUS_TYPE_STRING, &desc);
101   if (!dbus_message_iter_close_container (iter_array, &iter_struct))
102     goto oom;
103   if (!include_children) childcount = 0;
104   for (i = 0; i < childcount; i++)
105     {
106       AtkObject *child = atk_object_ref_accessible_child (obj, i);
107       dbus_bool_t result;
108       if (!child)
109         continue;
110       result = append_update (iter_array, child, TRUE, data);
111       g_object_unref (child);
112       if (!result)
113         goto oom;
114     }
115   g_free (path);
116   return TRUE;
117 oom:
118   if (path) g_free(path);
119   return FALSE;
120 }
121
122 dbus_bool_t
123 spi_dbus_append_tree (DBusMessage * message, AtkObject * obj,
124                       DRouteData * data)
125 {
126   DBusMessageIter iter, iter_array;
127   dbus_bool_t result;
128
129   dbus_message_iter_init_append (message, &iter);
130   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
131                                     &iter_array);
132   result = append_update (&iter_array, obj, TRUE, data);
133   if (result)
134     result = dbus_message_iter_close_container (&iter, &iter_array);
135   return result;
136 }
137
138 static DBusMessage *
139 impl_getRoot (DBusConnection * bus, DBusMessage * message, void *user_data)
140 {
141   AtkObject *root = atk_get_root();
142   char *path;
143   DBusMessage *reply;
144
145   if (root) path = spi_dbus_get_path(root);
146   if (!root || !path)
147     return spi_dbus_general_error (message);
148   reply = dbus_message_new_method_return (message);
149   dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &path,
150                             DBUS_TYPE_INVALID);
151   g_free (path);
152   return reply;
153 }
154
155 static DBusMessage *
156 impl_getTree (DBusConnection * bus, DBusMessage * message, void *user_data)
157 {
158   DBusMessage *reply;
159   AtkObject *root = atk_get_root();
160
161   if (!root) return spi_dbus_general_error(message);
162   reply = dbus_message_new_method_return (message);
163   spi_dbus_append_tree (reply, root, (DRouteData *) user_data);
164   return reply;
165 }
166
167 static DBusMessage *
168 impl_introspect (DBusConnection *bus, DBusMessage *message, void *user_data)
169 {
170   const char *path;
171   GString *output;
172   char *final;
173
174   DBusMessage *reply;
175
176   path = dbus_message_get_path(message);
177
178   output = g_string_new(spi_introspection_header);
179   
180   g_string_append_printf(output, spi_introspection_node_element, path);
181
182   spi_append_interface(output, "org.freedesktop.atspi.Tree");
183
184   g_string_append(output, spi_introspection_footer);
185   final = g_string_free(output, FALSE);
186
187   reply = dbus_message_new_method_return (message);
188   g_assert(reply != NULL);
189   dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
190                            DBUS_TYPE_INVALID);
191
192   g_free(final);
193   return reply;
194 }
195
196 static DBusHandlerResult
197 message_handler (DBusConnection *bus, DBusMessage *message, void *user_data)
198 {
199   const char *iface = dbus_message_get_interface (message);
200   const char *member = dbus_message_get_member (message);
201
202   DBusMessage *reply = NULL;
203   
204   if (!strcmp(iface, "org.freedesktop.atspi.Tree"))
205     {
206       if (!strcmp(member, "getRoot"))
207         {
208           reply = impl_getRoot(bus, message, user_data);
209         }
210
211       if (!strcmp(member, "getTree"))
212         {
213           reply = impl_getTree(bus, message, user_data);
214         }
215     }
216
217   if (!strcmp(iface, "org.freedesktop.DBus.Introspectable"))
218     {
219       if (!strcmp(member, "Introspect"))
220         {
221           reply = impl_introspect(bus, message, user_data);
222         }
223     }
224
225   if (reply)
226     {
227       dbus_connection_send (bus, reply, NULL);
228       dbus_message_unref (reply);
229     }
230
231   return DBUS_HANDLER_RESULT_HANDLED;
232 }
233
234 static DBusObjectPathVTable tree_vtable =
235 {
236   NULL,
237   &message_handler,
238   NULL, NULL, NULL, NULL
239 };
240
241 void
242 spi_register_tree_object(DBusConnection *bus,
243                          const char *path)
244 {
245   dbus_bool_t mem = FALSE;
246   mem = dbus_connection_register_object_path(bus, path, &tree_vtable, NULL);
247   g_assert(mem == TRUE);
248 }
249
250 static GHashTable *cache_list;
251
252 #define UPDATE_NEW     1
253 #define UPDATE_REFRESH 2
254 #define UPDATE_REMOVE 3
255
256 static int update_pending = 0;
257 static gint update_pending_id;
258
259 typedef struct
260 {
261   DBusMessageIter iter;
262   DRouteData *droute;
263   gboolean removing;
264 } CacheIterData;
265
266 static void handle_cache_item(char *path, guint action, CacheIterData *d)
267 {
268   AtkObject *obj;
269
270   switch (action)
271   {
272   case UPDATE_NEW:
273   case UPDATE_REFRESH:
274   default:
275     if (d->removing) return;
276     obj = spi_dbus_get_object(path);
277 //printf("update %s\n", path);
278     append_update(&d->iter, obj, FALSE, d->droute);
279     break;
280   case UPDATE_REMOVE:
281 //printf("remove: %s\n", path);
282     if (!d->removing) return;
283     dbus_message_iter_append_basic(&d->iter, DBUS_TYPE_OBJECT_PATH, &path);
284     break;
285   }
286   g_hash_table_remove(cache_list, path);
287   }
288
289 gboolean spi_dbus_update_cache(DRouteData *data)
290 {
291   DBusMessage *message;
292   DBusMessageIter iter;
293   CacheIterData d;
294
295   if (update_pending == 0) return FALSE;
296 //printf("Sending cache\n");
297   message = dbus_message_new_signal("/org/freedesktop/atspi/tree", "org.freedesktop.atspi.Tree", "UpdateTree");
298   if (!message) goto done;
299   dbus_message_iter_init_append (message, &iter);
300   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
301                                     &d.iter);
302   d.droute = data;
303   d.removing = FALSE;
304   do
305   {
306     /* This loop is needed because appending an item may cause new children
307      * to be registered and consequently added to the hash, so they, too,
308      * will need to be sent with the update */
309     update_pending = 0;
310     g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
311   } while (update_pending);
312   dbus_message_iter_close_container(&iter, &d.iter);
313   dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "o", &d.iter);
314   d.removing = TRUE;
315   g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
316   dbus_message_iter_close_container(&iter, &d.iter);
317   dbus_connection_send(data->bus, message, NULL);
318 done:
319   return FALSE;
320 }
321
322 void spi_dbus_notify_change(AtkObject *obj, gboolean new, DRouteData *data)
323 {
324   guint action = (new? UPDATE_NEW: UPDATE_REFRESH);
325   char *path = spi_dbus_get_path(obj);
326
327   if (!cache_list)
328   {
329     cache_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
330     if (!cache_list)
331     {
332       g_free(path);
333       return;
334     }
335   }
336   if (g_hash_table_lookup(cache_list, path))
337   {
338     g_free(path);
339     return;
340   }
341 //printf("change: %s\n", path);
342   g_hash_table_insert(cache_list, path, (gpointer)action);
343   if (update_pending != 2 && data)
344   {
345     update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
346     update_pending = 2;
347   }
348   else if (!update_pending) update_pending = 1;
349 }
350
351 void spi_dbus_notify_remove(AtkObject *obj, DRouteData *data)
352 {
353   guint action = UPDATE_REMOVE;
354   guint cur_action;
355   gchar *path = spi_dbus_get_path(obj);
356
357 //printf("notify remove: %s\n", path);
358   if (!cache_list)
359   {
360     g_free(path);
361     return;
362   }
363   cur_action = (guint)g_hash_table_lookup(cache_list, path);
364   if (cur_action == UPDATE_NEW)
365   {
366     /* No one knew that this object ever existed, so just remove it */
367 //printf("Removing object from send queue\n");
368     g_hash_table_remove(cache_list, path);
369     g_free(path);
370   }
371   else
372   {
373     g_hash_table_insert(cache_list, path, (gpointer)action);
374     if (update_pending != 2 && data)
375     {
376       update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
377       update_pending = 2;
378     }
379     else if (!update_pending) update_pending = 1;
380   }
381 }
382
383 gboolean spi_dbus_object_is_known(AtkObject *obj)
384 {
385   guint cur_action;
386   char *path = spi_dbus_get_path(obj);
387   if (!path) return FALSE;
388   cur_action = (guint)g_hash_table_lookup(cache_list, path);
389   g_free(path);
390   return (cur_action != UPDATE_NEW);
391 }