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