2008-12-07 Mark Doffman <mark.doffman@codethink.co.uk>
[platform/core/uifw/at-spi2-atk.git] / droute / droute.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 2008 Codethink Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "droute.h"
28 #include "droute-pairhash.h"
29
30 #define CHUNKS_DEFAULT (512)
31
32 #define oom() g_error ("D-Bus out of memory, this message will fail anyway")
33
34 struct _DRouteContext
35 {
36     DBusConnection       *bus;
37     GPtrArray            *registered_paths;
38
39     gchar                *introspect_dir;
40 };
41
42 struct _DRoutePath
43 {
44     DRouteContext        *cnx;
45     GStringChunk         *chunks;
46     GPtrArray            *interfaces;
47     GHashTable           *methods;
48     GHashTable           *properties;
49
50     void                   *user_data;
51     DRouteGetDatumFunction  get_datum;
52 };
53
54 /*---------------------------------------------------------------------------*/
55
56 typedef struct PropertyPair
57 {
58     DRoutePropertyFunction get;
59     DRoutePropertyFunction set;
60 } PropertyPair;
61
62 /*---------------------------------------------------------------------------*/
63
64 static DBusHandlerResult
65 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data);
66
67 /*---------------------------------------------------------------------------*/
68
69 static DRoutePath *
70 path_new (DRouteContext *cnx,
71           void    *user_data,
72           DRouteGetDatumFunction get_datum)
73 {
74     DRoutePath *new_path;
75
76     new_path = g_new0 (DRoutePath, 0);
77     new_path->cnx = cnx;
78     new_path->chunks = g_string_chunk_new (CHUNKS_DEFAULT);
79     new_path->interfaces = g_ptr_array_new ();
80
81     new_path->methods = g_hash_table_new_full ((GHashFunc)str_pair_hash,
82                                                str_pair_equal,
83                                                g_free,
84                                                NULL);
85
86     new_path->properties = g_hash_table_new_full ((GHashFunc)str_pair_hash,
87                                                   str_pair_equal,
88                                                   g_free,
89                                                   NULL);
90
91     new_path->user_data = user_data;
92     new_path->get_datum = get_datum;
93
94     return new_path;
95 }
96
97 static void
98 path_free (DRoutePath *path, gpointer user_data)
99 {
100     g_string_chunk_free  (path->chunks);
101     g_ptr_array_free     (path->interfaces, TRUE);
102     g_hash_table_destroy (path->methods);
103     g_hash_table_destroy (path->properties);
104 }
105
106 static void *
107 path_get_datum (DRoutePath *path, const gchar *pathstr)
108 {
109     if (path->get_datum != NULL)
110         return (path->get_datum) (pathstr, path->user_data);
111     else
112         return path->user_data;
113 }
114
115 /*---------------------------------------------------------------------------*/
116
117 DRouteContext *
118 droute_new (DBusConnection *bus, const char *introspect_dir)
119 {
120     DRouteContext *cnx;
121
122     cnx = g_new0 (DRouteContext, 1);
123     cnx->bus = bus;
124     cnx->registered_paths = g_ptr_array_new ();
125     cnx->introspect_dir = g_strdup(introspect_dir);
126 }
127
128 void
129 droute_free (DRouteContext *cnx)
130 {
131     g_pointer_array_foreach ((GFunc) path_free, cnx->registered_paths, NULL);
132     g_free (cnx->introspect_dir);
133     g_free (cnx);
134 }
135
136 /*---------------------------------------------------------------------------*/
137
138 static DBusObjectPathVTable droute_vtable =
139 {
140   NULL,
141   &handle_message,
142   NULL, NULL, NULL, NULL
143 };
144
145 DRoutePath *
146 droute_add_one (DRouteContext *cnx,
147                 const char    *path,
148                 const void    *data)
149 {
150     DRoutePath *new_path;
151
152     new_path = path_new (cnx, (void *) data, NULL);
153
154     if (!dbus_connection_register_object_path (cnx->bus, path, &droute_vtable, new_path))
155         oom();
156
157     g_ptr_array_add (cnx->registered_paths, new_path);
158     return new_path;
159 }
160
161 DRoutePath *
162 droute_add_many (DRouteContext *cnx,
163                  const char    *path,
164                  const void    *data,
165                  const DRouteGetDatumFunction get_datum)
166 {
167     DRoutePath *new_path;
168
169     new_path = path_new (cnx, (void *) data, get_datum);
170
171     if (!dbus_connection_register_fallback (cnx->bus, path, &droute_vtable, new_path))
172         oom();
173
174     g_ptr_array_add (cnx->registered_paths, new_path);
175     return new_path;
176 }
177
178 /*---------------------------------------------------------------------------*/
179
180 void
181 droute_path_add_interface(DRoutePath *path,
182                           const char *name,
183                           const DRouteMethod   *methods,
184                           const DRouteProperty *properties)
185 {
186     gchar *itf;
187
188     g_return_if_fail (name == NULL);
189
190     itf = g_string_chunk_insert (path->chunks, name);
191     g_ptr_array_add (path->interfaces, itf);
192
193     for (; methods->name != NULL; methods++)
194       {
195         gchar *meth;
196
197         meth = g_string_chunk_insert (path->chunks, methods->name);
198         g_hash_table_insert (path->methods, str_pair_new (itf, meth), methods->func);
199       }
200
201     for (; properties->name != NULL; properties++)
202       {
203         gchar *prop;
204         PropertyPair *pair;
205
206         prop = g_string_chunk_insert (path->chunks, properties->name);
207         pair = g_new (PropertyPair, 1);
208         pair->get = properties->get;
209         pair->set = properties->set;
210         g_hash_table_insert (path->properties, str_pair_new (itf, prop), pair);
211       }
212 }
213
214 /*---------------------------------------------------------------------------*/
215
216 /* The data structures don't support an efficient implementation of GetAll
217  * and I don't really care.
218  */
219 static DBusMessage *
220 impl_prop_GetAll (DBusMessage *message,
221                   DRoutePath  *path,
222                   const char  *pathstr)
223 {
224     DBusMessageIter iter, iter_dict, iter_dict_entry;
225     DBusMessage *reply;
226     DBusError error;
227     GHashTableIter prop_iter;
228
229     StrPair *key;
230     PropertyPair *value;
231     gchar *iface;
232
233     void  *datum = path_get_datum (path, pathstr);
234
235     dbus_error_init (&error);
236
237     if (!dbus_message_get_args
238                 (message, &error, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID))
239         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
240
241     reply = dbus_message_new_method_return (message);
242     if (!reply)
243         oom ();
244
245     dbus_message_iter_init_append (reply, &iter);
246     if (!dbus_message_iter_open_container
247                 (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_dict))
248         oom ();
249
250     g_hash_table_iter_init (&prop_iter, path->properties);
251     while (g_hash_table_iter_next (&prop_iter, (gpointer*)&key, (gpointer*)&value))
252       {
253         if (!g_strcmp (key->one, iface))
254          {
255            if (!value->get)
256               continue;
257            if (!dbus_message_iter_open_container
258                         (&iter_dict, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict_entry))
259               oom ();
260            dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING,
261                                            key->two);
262            (value->get) (&iter_dict_entry, datum);
263            if (!dbus_message_iter_close_container (&iter_dict, &iter_dict_entry))
264                oom ();
265          }
266       }
267
268     if (!dbus_message_iter_close_container (&iter, &iter_dict))
269         oom ();
270     return reply;
271 }
272
273 static DBusMessage *
274 impl_prop_GetSet (DBusMessage *message,
275                   DRoutePath  *path,
276                   const char  *pathstr,
277                   gboolean     get)
278 {
279     DBusMessage *reply = NULL;
280     DBusError error;
281
282     StrPair pair;
283     PropertyPair *prop_funcs;
284
285     if (!dbus_message_get_args (message,
286                                 &error,
287                                 DBUS_TYPE_STRING,
288                                 &(pair.one),
289                                 DBUS_TYPE_STRING,
290                                 &(pair.two),
291                                 DBUS_TYPE_INVALID))
292         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
293
294     prop_funcs = (PropertyPair *) g_hash_table_lookup (path->properties, &pair);
295     if (!prop_funcs)
296         return dbus_message_new_error (message, DBUS_ERROR_FAILED, "Property unavailable");
297
298     if (get && prop_funcs->get)
299       {
300         void *datum = path_get_datum (path, pathstr);
301         DBusMessageIter iter;
302
303         reply = dbus_message_new_method_return (message);
304         dbus_message_iter_init_append (reply, &iter);
305         (prop_funcs->get) (&iter, datum);
306       }
307     else if (!get && prop_funcs->set)
308       {
309         void *datum = path_get_datum (path, pathstr);
310         DBusMessageIter iter;
311
312         dbus_message_iter_init_append (message, &iter);
313         /* Skip the interface and property name */
314         dbus_message_iter_next(&iter);
315         dbus_message_iter_next(&iter);
316         (prop_funcs->get) (&iter, datum);
317       }
318     return reply;
319 }
320
321 static DBusHandlerResult
322 handle_properties (DBusConnection *bus,
323                    DBusMessage    *message,
324                    DRoutePath     *path,
325                    const gchar    *iface,
326                    const gchar    *member,
327                    const gchar    *pathstr)
328 {
329     DBusMessage *reply;
330     DBusHandlerResult result = DBUS_HANDLER_RESULT_HANDLED;
331
332     if (!g_strcmp0(member, "GetAll"))
333        reply = impl_prop_GetAll (message, path, pathstr);
334     else if (!g_strcmp0 (member, "Get"))
335        reply = impl_prop_GetSet (message, path, pathstr, TRUE);
336     else if (!g_strcmp0 (member, "Set"))
337        reply = impl_prop_GetSet (message, path, pathstr, FALSE);
338     else
339        result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
340
341     return result;
342 }
343
344 /*---------------------------------------------------------------------------*/
345
346 static const char *introspection_header =
347 "<?xml version=\"1.0\"?>\n";
348
349 static const char *introspection_node_element =
350 "<node name=\"%s\">\n";
351
352 static const char *introspection_footer =
353 "</node>";
354
355 static void
356 append_interface (GString     *str,
357                   const gchar *interface,
358                   const gchar *directory)
359 {
360   gchar *filename;
361   gchar *contents;
362   gsize len;
363
364   GError *err = NULL;
365
366   filename = g_build_filename (directory, interface, NULL);
367
368   if (g_file_get_contents (filename, &contents, &len, &err))
369     {
370       g_string_append_len (str, contents, len);
371     }
372   else
373     {
374       g_warning ("AT-SPI: Cannot find introspection XML file %s - %s",
375                  filename, err->message);
376       g_error_free (err);
377     }
378
379   g_string_append (str, "\n");
380   g_free (filename);
381   g_free (contents);
382 }
383
384 static DBusHandlerResult
385 handle_intropsection (DBusConnection *bus,
386                       DBusMessage    *message,
387                       DRoutePath     *path,
388                       const gchar    *iface,
389                       const gchar    *member,
390                       const gchar    *pathstr)
391 {
392     GString *output;
393     gchar *final;
394     gint i;
395
396     DBusMessage *reply;
397
398     if (g_strcmp (member, "Introspect"))
399         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
400
401     output = g_string_new(introspection_header);
402
403     g_string_append_printf(output, introspection_node_element, pathstr);
404
405     for (i=0; i < path->interfaces->len; i++)
406       {
407         gchar *interface = (gchar *) g_ptr_array_index (path->interfaces, i);
408         append_interface(output, interface, path->cnx->introspect_dir);
409       }
410
411     g_string_append(output, introspection_footer);
412     final = g_string_free(output, FALSE);
413
414     reply = dbus_message_new_method_return (message);
415     if (!reply)
416         oom ();
417     dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
418                              DBUS_TYPE_INVALID);
419     dbus_connection_send (bus, reply, NULL);
420
421     dbus_message_unref (reply);
422     g_free(final);
423     return DBUS_HANDLER_RESULT_HANDLED;
424 }
425
426 /*---------------------------------------------------------------------------*/
427
428 static DBusHandlerResult
429 handle_other (DBusConnection *bus,
430               DBusMessage    *message,
431               DRoutePath     *path,
432               const gchar    *iface,
433               const gchar    *member,
434               const gchar    *pathstr)
435 {
436     gint result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
437
438     StrPair pair;
439     DRouteFunction func;
440     DBusMessage *reply;
441
442     pair.one = iface;
443     pair.two = member;
444
445     func = (DRouteFunction) g_hash_table_lookup (path->methods, &pair);
446     if (func != NULL)
447       {
448         void *datum = path_get_datum (path, pathstr);
449
450         reply = (func) (bus, message, datum);
451
452         if (reply)
453           {
454             dbus_connection_send (bus, reply, NULL);
455             dbus_message_unref (reply);
456           }
457         result = DBUS_HANDLER_RESULT_HANDLED;
458       }
459     return result;
460 }
461
462 /*---------------------------------------------------------------------------*/
463
464 static DBusHandlerResult
465 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data)
466 {
467     DRoutePath *path = (DRoutePath *) user_data;
468     const gchar *iface   = dbus_message_get_interface (message);
469     const gchar *member  = dbus_message_get_member (message);
470     const gint   type    = dbus_message_get_type (message);
471     const gchar *pathstr = dbus_message_get_path (message);
472
473     /* Check for basic reasons not to handle */
474     if (type   != DBUS_MESSAGE_TYPE_METHOD_CALL ||
475         member == NULL ||
476         iface  == NULL)
477         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
478
479     if (!strcmp (iface, "org.freedesktop.DBus.Properties"))
480         return handle_properties (bus, message, path, iface, member, pathstr);
481
482     if (!strcmp (iface, "org.freedesktop.DBus.Introspectable"))
483         return handle_introspection (bus, message, path, iface, member, pathstr);
484
485     return handle_other (bus, message, path, iface, member, pathstr);
486 }
487
488 /*END------------------------------------------------------------------------*/