Improve introspection
[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 #if defined DROUTE_DEBUG
35     #define _DROUTE_DEBUG(format, args...) g_print (format , ## args)
36 #else
37     #define _DROUTE_DEBUG(format, args...)
38 #endif
39
40 struct _DRouteContext
41 {
42     DBusConnection       *bus;
43     GPtrArray            *registered_paths;
44
45     gchar                *introspect_string;
46 };
47
48 struct _DRoutePath
49 {
50     DRouteContext        *cnx;
51     GStringChunk         *chunks;
52     GPtrArray            *interfaces;
53     GPtrArray            *introspection;
54     GHashTable           *methods;
55     GHashTable           *properties;
56
57     DRouteIntrospectChildrenFunction introspect_children_cb;
58     void *introspect_children_data;
59     void                   *user_data;
60     DRouteGetDatumFunction  get_datum;
61 };
62
63 /*---------------------------------------------------------------------------*/
64
65 typedef struct PropertyPair
66 {
67     DRoutePropertyFunction get;
68     DRoutePropertyFunction set;
69 } PropertyPair;
70
71 /*---------------------------------------------------------------------------*/
72
73 static DBusHandlerResult
74 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data);
75
76 static DBusMessage *
77 droute_object_does_not_exist_error (DBusMessage *message);
78
79 /*---------------------------------------------------------------------------*/
80
81 static DRoutePath *
82 path_new (DRouteContext *cnx,
83           void    *user_data,
84           DRouteIntrospectChildrenFunction introspect_children_cb,
85           void *introspect_children_data,
86           DRouteGetDatumFunction get_datum)
87 {
88     DRoutePath *new_path;
89
90     new_path = g_new0 (DRoutePath, 1);
91     new_path->cnx = cnx;
92     new_path->chunks = g_string_chunk_new (CHUNKS_DEFAULT);
93     new_path->interfaces = g_ptr_array_new ();
94     new_path->introspection = g_ptr_array_new ();
95
96     new_path->methods = g_hash_table_new_full ((GHashFunc)str_pair_hash,
97                                                str_pair_equal,
98                                                g_free,
99                                                NULL);
100
101     new_path->properties = g_hash_table_new_full ((GHashFunc)str_pair_hash,
102                                                   str_pair_equal,
103                                                   g_free,
104                                                   NULL);
105
106     new_path->introspect_children_cb = introspect_children_cb;
107     new_path->introspect_children_data = introspect_children_data;
108     new_path->user_data = user_data;
109     new_path->get_datum = get_datum;
110
111     return new_path;
112 }
113
114 static void
115 path_free (DRoutePath *path, gpointer user_data)
116 {
117     g_string_chunk_free  (path->chunks);
118     g_ptr_array_free     (path->interfaces, TRUE);
119     g_ptr_array_free     (path->introspection, FALSE);
120     g_hash_table_destroy (path->methods);
121     g_hash_table_destroy (path->properties);
122 }
123
124 static void *
125 path_get_datum (DRoutePath *path, const gchar *pathstr)
126 {
127     if (path->get_datum != NULL)
128         return (path->get_datum) (pathstr, path->user_data);
129     else
130         return path->user_data;
131 }
132
133 /*---------------------------------------------------------------------------*/
134
135 DRouteContext *
136 droute_new (DBusConnection *bus)
137 {
138     DRouteContext *cnx;
139
140     cnx = g_new0 (DRouteContext, 1);
141     cnx->bus = bus;
142     cnx->registered_paths = g_ptr_array_new ();
143
144     return cnx;
145 }
146
147 void
148 droute_free (DRouteContext *cnx)
149 {
150     g_ptr_array_foreach (cnx->registered_paths, (GFunc) path_free, NULL);
151     g_free (cnx);
152 }
153
154 /*---------------------------------------------------------------------------*/
155
156 DBusConnection *
157 droute_get_bus (DRouteContext *cnx)
158 {
159     return cnx->bus;
160 }
161
162 /*---------------------------------------------------------------------------*/
163
164 static DBusObjectPathVTable droute_vtable =
165 {
166   NULL,
167   &handle_message,
168   NULL, NULL, NULL, NULL
169 };
170
171 DRoutePath *
172 droute_add_one (DRouteContext *cnx,
173                 const char    *path,
174                 const void    *data)
175 {
176     DRoutePath *new_path;
177     gboolean registered;
178
179     new_path = path_new (cnx, NULL, NULL, (void *) data, NULL);
180
181     registered = dbus_connection_register_object_path (cnx->bus, path, &droute_vtable, new_path);
182     if (!registered)
183       {
184         path_free (new_path, NULL);
185         return NULL;
186       }
187
188     g_ptr_array_add (cnx->registered_paths, new_path);
189     return new_path;
190 }
191
192 DRoutePath *
193 droute_add_many (DRouteContext *cnx,
194                  const char    *path,
195                  const void    *data,
196                  DRouteIntrospectChildrenFunction introspect_children_cb,
197                  void *introspect_children_data,
198                  const DRouteGetDatumFunction get_datum)
199 {
200     DRoutePath *new_path;
201
202     new_path = path_new (cnx, (void *) data, introspect_children_cb, introspect_children_data, get_datum);
203
204     if (!dbus_connection_register_fallback (cnx->bus, path, &droute_vtable, new_path))
205         oom();
206
207     g_ptr_array_add (cnx->registered_paths, new_path);
208     return new_path;
209 }
210
211 /*---------------------------------------------------------------------------*/
212
213 void
214 droute_path_add_interface(DRoutePath *path,
215                           const char *name,
216                           const char *introspect,
217                           const DRouteMethod   *methods,
218                           const DRouteProperty *properties)
219 {
220     gchar *itf;
221
222     g_return_if_fail (name != NULL);
223
224     itf = g_string_chunk_insert (path->chunks, name);
225     g_ptr_array_add (path->interfaces, itf);
226     g_ptr_array_add (path->introspection, introspect);
227
228     for (; methods != NULL && methods->name != NULL; methods++)
229       {
230         gchar *meth;
231
232         meth = g_string_chunk_insert (path->chunks, methods->name);
233         g_hash_table_insert (path->methods, str_pair_new (itf, meth), methods->func);
234       }
235
236     for (; properties != NULL && properties->name != NULL; properties++)
237       {
238         gchar *prop;
239         PropertyPair *pair;
240
241         prop = g_string_chunk_insert (path->chunks, properties->name);
242         pair = g_new (PropertyPair, 1);
243         pair->get = properties->get;
244         pair->set = properties->set;
245         g_hash_table_insert (path->properties, str_pair_new (itf, prop), pair);
246       }
247 }
248
249 /*---------------------------------------------------------------------------*/
250
251 /* The data structures don't support an efficient implementation of GetAll
252  * and I don't really care.
253  */
254 static DBusMessage *
255 impl_prop_GetAll (DBusMessage *message,
256                   DRoutePath  *path,
257                   const char  *pathstr)
258 {
259     DBusMessageIter iter, iter_dict, iter_dict_entry;
260     DBusMessage *reply;
261     DBusError error;
262     GHashTableIter prop_iter;
263
264     StrPair *key;
265     PropertyPair *value;
266     gchar *iface;
267
268     void  *datum = path_get_datum (path, pathstr);
269     if (!datum)
270         return droute_object_does_not_exist_error (message);
271
272     dbus_error_init (&error);
273     if (!dbus_message_get_args
274                 (message, &error, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID))
275         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
276
277     reply = dbus_message_new_method_return (message);
278     if (!reply)
279         oom ();
280
281     dbus_message_iter_init_append (reply, &iter);
282     if (!dbus_message_iter_open_container
283                 (&iter, DBUS_TYPE_ARRAY, "{sv}", &iter_dict))
284         oom ();
285
286     g_hash_table_iter_init (&prop_iter, path->properties);
287     while (g_hash_table_iter_next (&prop_iter, (gpointer*)&key, (gpointer*)&value))
288       {
289         if (!g_strcmp0 (key->one, iface))
290          {
291            if (!value->get)
292               continue;
293            if (!dbus_message_iter_open_container
294                         (&iter_dict, DBUS_TYPE_DICT_ENTRY, NULL, &iter_dict_entry))
295               oom ();
296            dbus_message_iter_append_basic (&iter_dict_entry, DBUS_TYPE_STRING,
297                                            &key->two);
298            (value->get) (&iter_dict_entry, datum);
299            if (!dbus_message_iter_close_container (&iter_dict, &iter_dict_entry))
300                oom ();
301          }
302       }
303
304     if (!dbus_message_iter_close_container (&iter, &iter_dict))
305         oom ();
306     return reply;
307 }
308
309 static DBusMessage *
310 impl_prop_GetSet (DBusMessage *message,
311                   DRoutePath  *path,
312                   const char  *pathstr,
313                   gboolean     get)
314 {
315     DBusMessage *reply = NULL;
316     DBusError error;
317
318     StrPair pair;
319     PropertyPair *prop_funcs = NULL;
320
321     void *datum;
322
323     dbus_error_init (&error);
324     if (!dbus_message_get_args (message,
325                                 &error,
326                                 DBUS_TYPE_STRING,
327                                 &(pair.one),
328                                 DBUS_TYPE_STRING,
329                                 &(pair.two),
330                                 DBUS_TYPE_INVALID))
331         return dbus_message_new_error (message, DBUS_ERROR_FAILED, error.message);
332
333     _DROUTE_DEBUG ("DRoute (handle prop): %s|%s on %s\n", pair.one, pair.two, pathstr);
334
335     prop_funcs = (PropertyPair *) g_hash_table_lookup (path->properties, &pair);
336     if (!prop_funcs)
337         return dbus_message_new_error (message, DBUS_ERROR_FAILED, "Property unavailable");
338
339     datum = path_get_datum (path, pathstr);
340     if (!datum)
341         return droute_object_does_not_exist_error (message);
342
343     if (get && prop_funcs->get)
344       {
345         
346         DBusMessageIter iter;
347
348         _DROUTE_DEBUG ("DRoute (handle prop Get): %s|%s on %s\n", pair.one, pair.two, pathstr);
349
350         reply = dbus_message_new_method_return (message);
351         dbus_message_iter_init_append (reply, &iter);
352         if (!(prop_funcs->get) (&iter, datum))
353           {
354             dbus_message_unref (reply);
355             reply = dbus_message_new_error (message, DBUS_ERROR_FAILED, "Get failed");
356           }
357       }
358     else if (!get && prop_funcs->set)
359       {
360         DBusMessageIter iter;
361
362         _DROUTE_DEBUG ("DRoute (handle prop Get): %s|%s on %s\n", pair.one, pair.two, pathstr);
363
364         dbus_message_iter_init (message, &iter);
365         /* Skip the interface and property name */
366         dbus_message_iter_next(&iter);
367         dbus_message_iter_next(&iter);
368         (prop_funcs->set) (&iter, datum);
369
370         reply = dbus_message_new_method_return (message);
371       }
372     else
373       {
374         reply = dbus_message_new_error (message, DBUS_ERROR_FAILED, "Getter or setter unavailable");
375       }
376
377     return reply;
378 }
379
380 static DBusHandlerResult
381 handle_properties (DBusConnection *bus,
382                    DBusMessage    *message,
383                    DRoutePath     *path,
384                    const gchar    *iface,
385                    const gchar    *member,
386                    const gchar    *pathstr)
387 {
388     DBusMessage *reply;
389     DBusHandlerResult result = DBUS_HANDLER_RESULT_HANDLED;
390
391     if (!g_strcmp0(member, "GetAll"))
392        reply = impl_prop_GetAll (message, path, pathstr);
393     else if (!g_strcmp0 (member, "Get"))
394        reply = impl_prop_GetSet (message, path, pathstr, TRUE);
395     else if (!g_strcmp0 (member, "Set"))
396        reply = impl_prop_GetSet (message, path, pathstr, FALSE);
397     else
398        result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
399
400     if (reply)
401       {
402         dbus_connection_send (bus, reply, NULL);
403         dbus_message_unref (reply);
404       }
405
406     return result;
407 }
408
409 /*---------------------------------------------------------------------------*/
410
411 static const char *introspection_header =
412 "<?xml version=\"1.0\"?>\n";
413
414 static const char *introspection_node_element =
415 "<node name=\"%s\">\n";
416
417 static const char *introspection_footer =
418 "</node>";
419
420 static DBusHandlerResult
421 handle_introspection (DBusConnection *bus,
422                       DBusMessage    *message,
423                       DRoutePath     *path,
424                       const gchar    *iface,
425                       const gchar    *member,
426                       const gchar    *pathstr)
427 {
428     GString *output;
429     gchar *final;
430     gint i;
431
432     DBusMessage *reply;
433
434     _DROUTE_DEBUG ("DRoute (handle introspection): %s\n", pathstr);
435
436     if (g_strcmp0 (member, "Introspect"))
437         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
438
439     output = g_string_new(introspection_header);
440
441     g_string_append_printf(output, introspection_node_element, pathstr);
442
443     if (!path->get_datum || path_get_datum (path, pathstr))
444       {
445         for (i=0; i < path->introspection->len; i++)
446           {
447             gchar *introspect = (gchar *) g_ptr_array_index (path->introspection, i);
448             g_string_append (output, introspect);
449           }
450       }
451
452     if (path->introspect_children_cb)
453       {
454         gchar *children = (*path->introspect_children_cb) (pathstr, path->introspect_children_data);
455         if (children)
456           {
457             g_string_append (output, children);
458             g_free (children);
459           }
460       }
461
462     g_string_append(output, introspection_footer);
463     final = g_string_free(output, FALSE);
464
465     reply = dbus_message_new_method_return (message);
466     if (!reply)
467         oom ();
468     dbus_message_append_args(reply, DBUS_TYPE_STRING, &final,
469                              DBUS_TYPE_INVALID);
470     dbus_connection_send (bus, reply, NULL);
471
472     dbus_message_unref (reply);
473     g_free(final);
474     return DBUS_HANDLER_RESULT_HANDLED;
475 }
476
477 /*---------------------------------------------------------------------------*/
478
479 static DBusHandlerResult
480 handle_other (DBusConnection *bus,
481               DBusMessage    *message,
482               DRoutePath     *path,
483               const gchar    *iface,
484               const gchar    *member,
485               const gchar    *pathstr)
486 {
487     gint result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
488
489     StrPair pair;
490     DRouteFunction func;
491     DBusMessage *reply = NULL;
492
493     void *datum;
494
495     pair.one = iface;
496     pair.two = member;
497
498     _DROUTE_DEBUG ("DRoute (handle other): %s|%s on %s\n", member, iface, pathstr);
499
500     func = (DRouteFunction) g_hash_table_lookup (path->methods, &pair);
501     if (func != NULL)
502       {
503         datum = path_get_datum (path, pathstr);
504         if (!datum)
505             reply = droute_object_does_not_exist_error (message);
506         else
507             reply = (func) (bus, message, datum);
508
509         if (!reply)
510           {
511             /* All D-Bus method calls must have a reply.
512              * If one is not provided presume that the call has a void
513              * return and no error has occured.
514              */
515             reply = dbus_message_new_method_return (message);
516           }
517         dbus_connection_send (bus, reply, NULL);
518         dbus_message_unref (reply);
519         result = DBUS_HANDLER_RESULT_HANDLED;
520       }
521
522     _DROUTE_DEBUG ("DRoute (handle other) (reply): type %d\n",
523                    dbus_message_get_type(reply));
524     return result;
525 }
526
527 /*---------------------------------------------------------------------------*/
528
529 static DBusHandlerResult
530 handle_message (DBusConnection *bus, DBusMessage *message, void *user_data)
531 {
532     DRoutePath *path = (DRoutePath *) user_data;
533     const gchar *iface   = dbus_message_get_interface (message);
534     const gchar *member  = dbus_message_get_member (message);
535     const gint   type    = dbus_message_get_type (message);
536     const gchar *pathstr = dbus_message_get_path (message);
537
538     DBusHandlerResult result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
539
540     _DROUTE_DEBUG ("DRoute (handle message): %s|%s of type %d on %s\n", member, iface, type, pathstr);
541
542     /* Check for basic reasons not to handle */
543     if (type   != DBUS_MESSAGE_TYPE_METHOD_CALL ||
544         member == NULL ||
545         iface  == NULL)
546         return result;
547
548     if (!strcmp (iface, "org.freedesktop.DBus.Properties"))
549         result = handle_properties (bus, message, path, iface, member, pathstr);
550     else if (!strcmp (iface, "org.freedesktop.DBus.Introspectable"))
551         result = handle_introspection (bus, message, path, iface, member, pathstr);
552     else
553         result = handle_other (bus, message, path, iface, member, pathstr);
554 #if 0
555     if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED)
556         g_print ("DRoute | Unhandled message: %s|%s of type %d on %s\n", member, iface, type, pathstr);
557 #endif
558       
559     return result;
560 }
561
562 /*---------------------------------------------------------------------------*/
563
564 static DBusMessage *
565 droute_object_does_not_exist_error (DBusMessage *message)
566 {
567     DBusMessage *reply;
568     gchar       *errmsg;
569
570     errmsg= g_strdup_printf (
571             "Method \"%s\" with signature \"%s\" on interface \"%s\" could not be processed as object %s does not exist\n",
572             dbus_message_get_member (message),
573             dbus_message_get_signature (message),
574             dbus_message_get_interface (message),
575             dbus_message_get_path (message));
576     reply = dbus_message_new_error (message,
577                                     DBUS_ERROR_FAILED,
578                                     errmsg);
579     g_free (errmsg);
580     return reply;
581 }
582
583 /*---------------------------------------------------------------------------*/
584
585 DBusMessage *
586 droute_not_yet_handled_error (DBusMessage *message)
587 {
588     DBusMessage *reply;
589     gchar       *errmsg;
590
591     errmsg= g_strdup_printf (
592             "Method \"%s\" with signature \"%s\" on interface \"%s\" doesn't exist\n",
593             dbus_message_get_member (message),
594             dbus_message_get_signature (message),
595             dbus_message_get_interface (message));
596     reply = dbus_message_new_error (message,
597                                     DBUS_ERROR_UNKNOWN_METHOD,
598                                     errmsg);
599     g_free (errmsg);
600     return reply;
601 }
602
603 DBusMessage *
604 droute_out_of_memory_error (DBusMessage *message)
605 {
606     DBusMessage *reply;
607     gchar       *errmsg;
608
609     errmsg= g_strdup_printf (
610             "Method \"%s\" with signature \"%s\" on interface \"%s\" could not be processed due to lack of memory\n",
611             dbus_message_get_member (message),
612             dbus_message_get_signature (message),
613             dbus_message_get_interface (message));
614     reply = dbus_message_new_error (message,
615                                     DBUS_ERROR_NO_MEMORY,
616                                     errmsg);
617     g_free (errmsg);
618     return reply;
619 }
620
621 DBusMessage *
622 droute_invalid_arguments_error (DBusMessage *message)
623 {
624     DBusMessage *reply;
625     gchar       *errmsg;
626
627     errmsg= g_strdup_printf (
628             "Method \"%s\" with signature \"%s\" on interface \"%s\" was supplied with invalid arguments\n",
629             dbus_message_get_member (message),
630             dbus_message_get_signature (message),
631             dbus_message_get_interface (message));
632     reply = dbus_message_new_error (message,
633                                     DBUS_ERROR_INVALID_ARGS,
634                                     errmsg);
635     g_free (errmsg);
636     return reply;
637 }
638
639 /*END------------------------------------------------------------------------*/