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