Use macros for interface names
[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
43   gint childcount;
44   GSList *l;
45
46   g_assert(data != NULL);
47
48   dbus_message_iter_open_container (iter_array, DBUS_TYPE_STRUCT, NULL,
49                                     &iter_struct);
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   if (data) 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 dbus_bool_t
125 spi_dbus_append_tree (DBusMessage * message, AtkObject * obj,
126                       DRouteData * data)
127 {
128   DBusMessageIter iter, iter_array;
129   dbus_bool_t result;
130
131   g_assert(data != NULL);
132
133   dbus_message_iter_init_append (message, &iter);
134   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
135                                     &iter_array);
136   result = append_update (&iter_array, obj, TRUE, data);
137   if (result)
138     result = dbus_message_iter_close_container (&iter, &iter_array);
139   return result;
140 }
141
142 static DBusMessage *
143 impl_getRoot (DBusConnection * bus, DBusMessage * message, void *user_data)
144 {
145   AtkObject *root = atk_get_root();
146   char *path;
147   DBusMessage *reply;
148
149   if (root) path = spi_dbus_get_path(root);
150   if (!root || !path)
151     return spi_dbus_general_error (message);
152   reply = dbus_message_new_method_return (message);
153   dbus_message_append_args (reply, DBUS_TYPE_OBJECT_PATH, &path,
154                             DBUS_TYPE_INVALID);
155   g_free (path);
156   return reply;
157 }
158
159 static DBusMessage *
160 impl_getTree (DBusConnection * bus, DBusMessage * message, void *user_data)
161 {
162   DBusMessage *reply;
163   AtkObject *root = atk_get_root();
164
165   if (!root) return spi_dbus_general_error(message);
166   reply = dbus_message_new_method_return (message);
167   spi_dbus_append_tree (reply, root, (DRouteData *) user_data);
168   return reply;
169 }
170
171 static DBusMessage *
172 impl_introspect (DBusConnection *bus, DBusMessage *message, void *user_data)
173 {
174   const char *path;
175   GString *output;
176   char *final;
177
178   DBusMessage *reply;
179
180   path = dbus_message_get_path(message);
181
182   output = g_string_new(spi_introspection_header);
183   
184   g_string_append_printf(output, spi_introspection_node_element, path);
185
186   spi_append_interface(output, "org.freedesktop.atspi.Tree");
187
188   g_string_append(output, spi_introspection_footer);
189   final = g_string_free(output, FALSE);
190
191   reply = dbus_message_new_method_return (message);
192   g_assert(reply != NULL);
193   dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
194                            DBUS_TYPE_INVALID);
195
196   g_free(final);
197   return reply;
198 }
199
200 static DBusHandlerResult
201 message_handler (DBusConnection *bus, DBusMessage *message, void *user_data)
202 {
203   const char *iface = dbus_message_get_interface (message);
204   const char *member = dbus_message_get_member (message);
205
206   DBusMessage *reply = NULL;
207
208   g_return_val_if_fail(iface != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
209   
210   if (!strcmp(iface, "org.freedesktop.atspi.Tree"))
211     {
212       if (!strcmp(member, "getRoot"))
213         {
214           reply = impl_getRoot(bus, message, user_data);
215         }
216
217       if (!strcmp(member, "getTree"))
218         {
219           reply = impl_getTree(bus, message, user_data);
220         }
221     }
222
223   if (!strcmp(iface, "org.freedesktop.DBus.Introspectable"))
224     {
225       if (!strcmp(member, "Introspect"))
226         {
227           reply = impl_introspect(bus, message, user_data);
228         }
229     }
230
231   if (reply)
232     {
233       dbus_connection_send (bus, reply, NULL);
234       dbus_message_unref (reply);
235     }
236
237   return DBUS_HANDLER_RESULT_HANDLED;
238 }
239
240 static DBusObjectPathVTable tree_vtable =
241 {
242   NULL,
243   &message_handler,
244   NULL, NULL, NULL, NULL
245 };
246
247 void
248 spi_register_tree_object(DBusConnection *bus,
249                          DRouteData *data,
250                          const char *path)
251 {
252   dbus_bool_t mem = FALSE;
253   mem = dbus_connection_register_object_path(bus, path, &tree_vtable, data);
254   g_assert(mem == TRUE);
255 }
256
257 static GHashTable *cache_list;
258
259 #define UPDATE_NEW     1
260 #define UPDATE_REFRESH 2
261 #define UPDATE_REMOVE 3
262
263 static int update_pending = 0;
264 static gint update_pending_id;
265
266 typedef struct
267 {
268   DBusMessageIter iter;
269   DRouteData *droute;
270   gboolean removing;
271 } CacheIterData;
272
273 static void handle_cache_item(char *path, guint action, CacheIterData *d)
274 {
275   AtkObject *obj;
276
277   switch (action)
278   {
279   case UPDATE_NEW:
280   case UPDATE_REFRESH:
281   default:
282     if (d->removing) return;
283     obj = spi_dbus_get_object(path);
284 //printf("update %s\n", path);
285     append_update(&d->iter, obj, FALSE, d->droute);
286     break;
287   case UPDATE_REMOVE:
288 //printf("remove: %s\n", path);
289     if (!d->removing) return;
290     dbus_message_iter_append_basic(&d->iter, DBUS_TYPE_OBJECT_PATH, &path);
291     break;
292   }
293   g_hash_table_remove(cache_list, path);
294 }
295
296 gboolean spi_dbus_update_cache(DRouteData *data)
297 {
298   DBusMessage *message;
299   DBusMessageIter iter;
300   CacheIterData d;
301
302   g_assert(data != NULL);
303
304   if (update_pending == 0) return FALSE;
305 //printf("Sending cache\n");
306   message = dbus_message_new_signal (SPI_DBUS_INTERFACE_TREE, "org.freedesktop.atspi.Tree", "updateTree");
307   if (!message) goto done;
308   dbus_message_iter_init_append (message, &iter);
309   dbus_message_iter_open_container (&iter, DBUS_TYPE_ARRAY, "(ooaoassus)",
310                                     &d.iter);
311   d.droute = data;
312   d.removing = FALSE;
313   do
314   {
315     /* This loop is needed because appending an item may cause new children
316      * to be registered and consequently added to the hash, so they, too,
317      * will need to be sent with the update */
318     update_pending = 0;
319     g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
320   } while (update_pending);
321   dbus_message_iter_close_container(&iter, &d.iter);
322   dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "o", &d.iter);
323   d.removing = TRUE;
324   g_hash_table_foreach(cache_list, (GHFunc)handle_cache_item, &d);
325   dbus_message_iter_close_container(&iter, &d.iter);
326   dbus_connection_send(data->bus, message, NULL);
327 done:
328   return FALSE;
329 }
330
331 void spi_dbus_notify_change(AtkObject *obj, gboolean new, DRouteData *data)
332 {
333   guint action = (new? UPDATE_NEW: UPDATE_REFRESH);
334   char *path = spi_dbus_get_path(obj);
335
336   if (!cache_list)
337   {
338     cache_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
339     if (!cache_list)
340     {
341       g_free(path);
342       return;
343     }
344   }
345   if (g_hash_table_lookup(cache_list, path))
346   {
347     g_free(path);
348     return;
349   }
350 //printf("change: %s\n", path);
351   g_hash_table_insert(cache_list, path, (gpointer)action);
352   if (update_pending != 2 && data)
353   {
354     update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
355     update_pending = 2;
356   }
357   else if (!update_pending) update_pending = 1;
358 }
359
360 void spi_dbus_notify_remove(AtkObject *obj, DRouteData *data)
361 {
362   guint action = UPDATE_REMOVE;
363   guint cur_action;
364   gchar *path = spi_dbus_get_path(obj);
365
366 //printf("notify remove: %s\n", path);
367   if (!cache_list)
368   {
369     g_free(path);
370     return;
371   }
372   cur_action = (guint)g_hash_table_lookup(cache_list, path);
373   if (cur_action == UPDATE_NEW)
374   {
375     /* No one knew that this object ever existed, so just remove it */
376 //printf("Removing object from send queue\n");
377     g_hash_table_remove(cache_list, path);
378     g_free(path);
379   }
380   else
381   {
382     g_hash_table_insert(cache_list, path, (gpointer)action);
383     if (update_pending != 2 && data)
384     {
385       update_pending_id = g_idle_add((GSourceFunc)spi_dbus_update_cache, data);
386       update_pending = 2;
387     }
388     else if (!update_pending) update_pending = 1;
389   }
390 }
391
392 gboolean spi_dbus_object_is_known(AtkObject *obj)
393 {
394   guint cur_action;
395   char *path = spi_dbus_get_path(obj);
396   if (!path) return FALSE;
397   cur_action = (guint)g_hash_table_lookup(cache_list, path);
398   g_free(path);
399   return (cur_action != UPDATE_NEW);
400 }