2003-09-03 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / glib / dbus-gobject.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-gobject.c Exporting a GObject remotely
3  *
4  * Copyright (C) 2003 Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 1.2
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program 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
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include <config.h>
25 #include "dbus-glib.h"
26 #include "dbus-gtest.h"
27 #include <string.h>
28
29 /**
30  * @addtogroup DBusGLibInternals
31  * @{
32  */
33
34 static GStaticMutex info_hash_mutex = G_STATIC_MUTEX_INIT;
35 static GHashTable *info_hash = NULL;
36
37 static char*
38 wincaps_to_uscore (const char *caps)
39 {
40   const char *p;
41   GString *str;
42
43   str = g_string_new (NULL);
44   p = caps;
45   while (*p)
46     {
47       if (g_ascii_isupper (*p))
48         {
49           if (str->len > 0 &&
50               (str->len < 2 || str->str[str->len-2] != '_'))
51             g_string_append_c (str, '_');
52           g_string_append_c (str, g_ascii_tolower (*p));
53         }
54       else
55         {
56           g_string_append_c (str, *p);
57         }
58       ++p;
59     }
60
61   return g_string_free (str, FALSE);
62 }
63
64 static char*
65 uscore_to_wincaps (const char *uscore)
66 {
67   const char *p;
68   GString *str;
69   gboolean last_was_uscore;
70
71   last_was_uscore = TRUE;
72   
73   str = g_string_new (NULL);
74   p = uscore;
75   while (*p)
76     {
77       if (*p == '-' || *p == '_')
78         {
79           last_was_uscore = TRUE;
80         }
81       else
82         {
83           if (last_was_uscore)
84             {
85               g_string_append_c (str, g_ascii_toupper (*p));
86               last_was_uscore = FALSE;
87             }
88           else
89             g_string_append_c (str, *p);
90         }
91       ++p;
92     }
93
94   return g_string_free (str, FALSE);
95 }
96
97 static void
98 gobject_unregister_function (DBusConnection  *connection,
99                              void            *user_data)
100 {
101   GObject *object;
102
103   object = G_OBJECT (user_data);
104
105
106 }
107
108 static int
109 gtype_to_dbus_type (GType type)
110 {
111   switch (type)
112     {
113     case G_TYPE_CHAR:
114     case G_TYPE_UCHAR:
115       return DBUS_TYPE_BYTE;
116       
117     case G_TYPE_BOOLEAN:
118       return DBUS_TYPE_BOOLEAN;
119
120       /* long gets cut to 32 bits so the remote API is consistent
121        * on all architectures
122        */
123       
124     case G_TYPE_LONG:
125     case G_TYPE_INT:
126       return DBUS_TYPE_INT32;
127     case G_TYPE_ULONG:
128     case G_TYPE_UINT:
129       return DBUS_TYPE_UINT32;
130
131     case G_TYPE_INT64:
132       return DBUS_TYPE_INT64;
133
134     case G_TYPE_UINT64:
135       return DBUS_TYPE_UINT64;
136       
137     case G_TYPE_FLOAT:
138     case G_TYPE_DOUBLE:
139       return DBUS_TYPE_DOUBLE;
140
141     case G_TYPE_STRING:
142       return DBUS_TYPE_STRING;
143
144     default:
145       return DBUS_TYPE_INVALID;
146     }
147 }
148
149 static const char *
150 dbus_type_to_string (int type)
151 {
152   switch (type)
153     {
154     case DBUS_TYPE_INVALID:
155       return "invalid";
156     case DBUS_TYPE_NIL:
157       return "nil";
158     case DBUS_TYPE_BOOLEAN:
159       return "boolean";
160     case DBUS_TYPE_INT32:
161       return "int32";
162     case DBUS_TYPE_UINT32:
163       return "uint32";
164     case DBUS_TYPE_DOUBLE:
165       return "double";
166     case DBUS_TYPE_STRING:
167       return "string";
168     case DBUS_TYPE_NAMED:
169       return "named";
170     case DBUS_TYPE_ARRAY:
171       return "array";
172     case DBUS_TYPE_DICT:
173       return "dict";
174     default:
175       return "unknown";
176     }
177 }
178
179 static DBusHandlerResult
180 handle_introspect (DBusConnection *connection,
181                    DBusMessage    *message,
182                    GObject        *object)
183 {
184   GString *xml;
185   GParamSpec **specs;
186   unsigned int n_specs;
187   unsigned int i;
188   GType last_type;
189   DBusMessage *ret;
190   
191   xml = g_string_new (NULL);
192
193   g_string_append (xml, "<node>\n");
194
195   last_type = G_TYPE_INVALID;
196
197   specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
198                                           &n_specs);
199
200   i = 0;
201   while (i < n_specs)
202     {
203       GParamSpec *spec = specs[i];
204       gboolean can_set;
205       gboolean can_get;
206       char *s;
207       int dbus_type;
208       
209       dbus_type = gtype_to_dbus_type (G_PARAM_SPEC_VALUE_TYPE (spec));
210       if (dbus_type == DBUS_TYPE_INVALID)
211         goto next;
212       
213       if (spec->owner_type != last_type)
214         {
215           if (last_type != G_TYPE_INVALID)
216             g_string_append (xml, "  </interface>\n");
217
218
219           /* FIXME what should the namespace on the interface be in
220            * general?  should people be able to set it for their
221            * objects?
222            */
223           
224           g_string_append (xml, "  <interface name=\"org.gtk.objects.");
225           g_string_append (xml, g_type_name (spec->owner_type));
226           g_string_append (xml, "\">\n");
227
228           last_type = spec->owner_type;
229         }
230
231       can_set = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
232                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
233
234       can_get = (spec->flags & G_PARAM_READABLE) != 0;
235
236       s = uscore_to_wincaps (spec->name);
237       
238       if (can_set)
239         {
240           g_string_append (xml, "    <method name=\"set_");
241           g_string_append (xml, s);
242           g_string_append (xml, "\">\n");
243           
244           g_string_append (xml, "      <arg type=\"");
245           g_string_append (xml, dbus_type_to_string (dbus_type));
246           g_string_append (xml, "\"/>\n");
247         }
248
249       if (can_get)
250         {
251           g_string_append (xml, "    <method name=\"get_");
252           g_string_append (xml, s);
253           g_string_append (xml, "\">\n");
254           
255           g_string_append (xml, "      <arg type=\"");
256           g_string_append (xml, dbus_type_to_string (dbus_type));
257           g_string_append (xml, "\" direction=\"out\"/>\n");
258         }
259
260       g_free (s);
261
262     next:
263       ++i;
264     }
265
266   if (last_type != G_TYPE_INVALID)
267     g_string_append (xml, "  </interface>\n");
268
269   g_free (specs);
270
271   /* Close the XML, and send it to the requesting app */
272
273   g_string_append (xml, "</node>\n");
274
275   ret = dbus_message_new_method_return (message);
276   if (ret == NULL)
277     g_error ("out of memory");
278
279   dbus_message_append_args (message,
280                             DBUS_TYPE_STRING, xml->str,
281                             DBUS_TYPE_INVALID);
282
283   dbus_connection_send (connection, message, NULL);
284   dbus_message_unref (message);
285
286   g_string_free (xml, TRUE);
287
288   return DBUS_HANDLER_RESULT_HANDLED;
289 }
290
291 static DBusMessage*
292 set_object_property (DBusConnection *connection,
293                      DBusMessage    *message,
294                      GObject        *object,
295                      GParamSpec     *pspec)
296 {
297   GValue value;
298   DBusMessageIter iter;
299   int type;
300   gboolean can_set;
301   DBusMessage *ret;
302
303   dbus_message_iter_init (message, &iter);
304   type = dbus_message_get_type (message);
305
306   can_set = TRUE;
307   switch (type)
308     {
309     case DBUS_TYPE_BYTE:
310       {
311         unsigned char b;
312
313         b = dbus_message_iter_get_byte (&iter);
314
315         g_value_init (&value, G_TYPE_UCHAR);
316
317         g_value_set_uchar (&value, b);
318       }
319       break;
320     case DBUS_TYPE_BOOLEAN:
321       {
322         gboolean b;
323
324         b = dbus_message_iter_get_boolean (&iter);
325
326         g_value_init (&value, G_TYPE_BOOLEAN);
327
328         g_value_set_boolean (&value, b);
329       }
330       break;
331     case DBUS_TYPE_INT32:
332       {
333         gint32 i;
334
335         i = dbus_message_iter_get_int32 (&iter);
336
337         g_value_init (&value, G_TYPE_INT);
338
339         g_value_set_int (&value, i);
340       }
341       break;
342     case DBUS_TYPE_UINT32:
343       {
344         guint32 i;
345
346         i = dbus_message_iter_get_uint32 (&iter);
347
348         g_value_init (&value, G_TYPE_UINT);
349
350         g_value_set_uint (&value, i);
351       }
352       break;
353     case DBUS_TYPE_INT64:
354       {
355         gint64 i;
356
357         i = dbus_message_iter_get_int64 (&iter);
358
359         g_value_init (&value, G_TYPE_INT64);
360
361         g_value_set_int64 (&value, i);
362       }
363       break;
364     case DBUS_TYPE_UINT64:
365       {
366         guint64 i;
367
368         i = dbus_message_iter_get_uint64 (&iter);
369
370         g_value_init (&value, G_TYPE_UINT64);
371
372         g_value_set_uint64 (&value, i);
373       }
374       break;
375     case DBUS_TYPE_DOUBLE:
376       {
377         double d;
378
379         d = dbus_message_iter_get_double (&iter);
380
381         g_value_init (&value, G_TYPE_DOUBLE);
382
383         g_value_set_double (&value, d);
384       }
385       break;
386     case DBUS_TYPE_STRING:
387       {
388         char *s;
389
390         /* FIXME use a const string accessor */
391
392         s = dbus_message_iter_get_string (&iter);
393
394         g_value_init (&value, G_TYPE_STRING);
395
396         g_value_set_string (&value, s);
397
398         g_free (s);
399       }
400       break;
401
402       /* FIXME array and other types, especially byte array
403        * converted to G_TYPE_STRING
404        */
405
406     default:
407       can_set = FALSE;
408       break;
409     }
410
411   /* The g_object_set_property() will transform some types, e.g. it
412    * will let you use a uchar to set an int property etc. Note that
413    * any error in value range or value conversion will just
414    * g_warning(). These GObject skels are not for secure applications.
415    */
416
417   if (can_set)
418     {
419       g_object_set_property (object,
420                              pspec->name,
421                              &value);
422
423       ret = dbus_message_new_method_return (message);
424       if (ret == NULL)
425         g_error ("out of memory");
426
427       g_value_unset (&value);
428     }
429   else
430     {
431       ret = dbus_message_new_error (message,
432                                     DBUS_ERROR_INVALID_ARGS,
433                                     "Argument's D-BUS type can't be converted to a GType");
434       if (ret == NULL)
435         g_error ("out of memory");
436     }
437
438   return ret;
439 }
440
441 static DBusMessage*
442 get_object_property (DBusConnection *connection,
443                      DBusMessage    *message,
444                      GObject        *object,
445                      GParamSpec     *pspec)
446 {
447   GType value_type;
448   gboolean can_get;
449   DBusMessage *ret;
450   GValue value;
451   DBusMessageIter iter;
452
453   value_type = G_PARAM_SPEC_VALUE_TYPE (pspec);
454
455   ret = dbus_message_new_method_return (message);
456   if (ret == NULL)
457     g_error ("out of memory");
458
459   can_get = TRUE;
460   g_value_init (&value, value_type);
461   g_object_get_property (object, pspec->name, &value);
462
463   value_type = G_VALUE_TYPE (&value);
464
465   dbus_message_append_iter_init (message, &iter);
466   
467   switch (value_type)
468     {
469     case G_TYPE_CHAR:
470       dbus_message_iter_append_byte (&iter,
471                                      g_value_get_char (&value));
472       break;
473     case G_TYPE_UCHAR:
474       dbus_message_iter_append_byte (&iter,
475                                      g_value_get_uchar (&value));
476       break;
477     case G_TYPE_BOOLEAN:
478       dbus_message_iter_append_boolean (&iter,
479                                         g_value_get_boolean (&value));
480       break;
481     case G_TYPE_INT:
482       dbus_message_iter_append_int32 (&iter,
483                                       g_value_get_int (&value));
484       break;
485     case G_TYPE_UINT:
486       dbus_message_iter_append_uint32 (&iter,
487                                        g_value_get_uint (&value));
488       break;
489       /* long gets cut to 32 bits so the remote API is consistent
490        * on all architectures
491        */
492     case G_TYPE_LONG:
493       dbus_message_iter_append_int32 (&iter,
494                                       g_value_get_long (&value));
495       break;
496     case G_TYPE_ULONG:
497       dbus_message_iter_append_uint32 (&iter,
498                                        g_value_get_ulong (&value));
499       break;
500     case G_TYPE_INT64:
501       dbus_message_iter_append_int64 (&iter,
502                                       g_value_get_int64 (&value));
503       break;
504     case G_TYPE_UINT64:
505       dbus_message_iter_append_uint64 (&iter,
506                                        g_value_get_uint64 (&value));
507       break;
508     case G_TYPE_FLOAT:
509       dbus_message_iter_append_double (&iter,
510                                        g_value_get_float (&value));
511       break;
512     case G_TYPE_DOUBLE:
513       dbus_message_iter_append_double (&iter,
514                                        g_value_get_double (&value));
515       break;
516     case G_TYPE_STRING:
517       /* FIXME, the GValue string may not be valid UTF-8 */
518       dbus_message_iter_append_string (&iter,
519                                        g_value_get_string (&value));
520       break;
521     default:
522       can_get = FALSE;
523       break;
524     }
525
526   g_value_unset (&value);
527
528   if (!can_get)
529     {
530       dbus_message_unref (ret);
531       ret = dbus_message_new_error (message,
532                                     DBUS_ERROR_UNKNOWN_METHOD,
533                                     "Can't convert GType of object property to a D-BUS type");
534     }
535
536   return ret;
537 }
538
539 static DBusHandlerResult
540 gobject_message_function (DBusConnection  *connection,
541                           DBusMessage     *message,
542                           void            *user_data)
543 {
544   const DBusGObjectInfo *info;
545   GParamSpec *pspec;
546   GObject *object;
547   const char *member;
548   gboolean setter;
549   gboolean getter;
550   char *s;
551
552   object = G_OBJECT (user_data);
553
554   if (dbus_message_is_method_call (message,
555                                    DBUS_INTERFACE_ORG_FREEDESKTOP_INTROSPECTABLE,
556                                    "Introspect"))
557     return handle_introspect (connection, message, object);
558
559   member = dbus_message_get_member (message);
560
561   /* Try the metainfo, which lets us invoke methods */
562
563   g_static_mutex_lock (&info_hash_mutex);
564   /* FIXME this needs to walk up the inheritance tree, not
565    * just look at the most-derived class
566    */
567   info = g_hash_table_lookup (info_hash,
568                               G_OBJECT_GET_CLASS (object));
569   g_static_mutex_unlock (&info_hash_mutex);
570
571   if (info != NULL)
572     {
573
574
575
576     }
577
578   /* If no metainfo, we can still do properties and signals
579    * via standard GLib introspection
580    */
581   setter = (member[0] == 's' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
582   getter = (member[0] == 'g' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
583
584   if (!(setter || getter))
585     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
586
587   s = wincaps_to_uscore (&member[4]);
588
589   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),
590                                         s);
591
592   g_free (s);
593
594   if (pspec != NULL)
595     {
596       DBusMessage *ret;
597
598       if (setter)
599         {
600           ret = set_object_property (connection, message,
601                                      object, pspec);
602         }
603       else if (getter)
604         {
605           ret = get_object_property (connection, message,
606                                      object, pspec);
607         }
608       else
609         {
610           g_assert_not_reached ();
611           ret = NULL;
612         }
613
614       g_assert (ret != NULL);
615
616       dbus_connection_send (connection, ret, NULL);
617       dbus_message_unref (ret);
618       return DBUS_HANDLER_RESULT_HANDLED;
619     }
620
621   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
622 }
623
624 static DBusObjectPathVTable gobject_dbus_vtable = {
625   gobject_unregister_function,
626   gobject_message_function,
627   NULL
628 };
629
630 /** @} */ /* end of internals */
631
632 /**
633  * @addtogroup DBusGLib
634  * @{
635  */
636
637 /**
638  * Install introspection information about the given object class
639  * sufficient to allow methods on the object to be invoked by name.
640  * The introspection information is normally generated by
641  * dbus-glib-tool, then this function is called in the
642  * class_init() for the object class.
643  *
644  * Once introspection information has been installed, instances of the
645  * object registered with dbus_connection_register_gobject() can have
646  * their methods invoked remotely.
647  *
648  * @param object_class class struct of the object
649  * @param info introspection data generated by dbus-glib-tool
650  */
651 void
652 dbus_gobject_class_install_info (GObjectClass          *object_class,
653                                  const DBusGObjectInfo *info)
654 {
655   g_return_if_fail (G_IS_OBJECT_CLASS (object_class));
656
657   g_static_mutex_lock (&info_hash_mutex);
658
659   if (info_hash == NULL)
660     {
661       info_hash = g_hash_table_new (NULL, NULL); /* direct hash */
662     }
663
664   g_hash_table_replace (info_hash, object_class, (void*) info);
665
666   g_static_mutex_unlock (&info_hash_mutex);
667 }
668
669 static char**
670 split_path (const char *path)
671 {
672   int len;
673   char **split;
674   int n_components;
675   int i, j, comp;
676
677   len = strlen (path);
678
679   n_components = 0;
680   i = 0;
681   while (i < len)
682     {
683       if (path[i] == '/')
684         n_components += 1;
685       ++i;
686     }
687
688   split = g_new0 (char*, n_components + 1);
689
690   comp = 0;
691   i = 0;
692   while (i < len)
693     {
694       if (path[i] == '/')
695         ++i;
696       j = i;
697
698       while (j < len && path[j] != '/')
699         ++j;
700
701       /* Now [i, j) is the path component */
702       g_assert (i < j);
703       g_assert (path[i] != '/');
704       g_assert (j == len || path[j] == '/');
705
706       split[comp] = g_strndup (&path[i], j - i + 1);
707
708       split[comp][j-i] = '\0';
709
710       ++comp;
711       i = j;
712     }
713   g_assert (i == len);
714
715   return split;
716 }
717
718 /**
719  * Registers a GObject at the given path. Properties, methods, and signals
720  * of the object can then be accessed remotely. Methods are only available
721  * if method introspection data has been added to the object's class
722  * with g_object_class_install_info().
723  *
724  * The registration will be cancelled if either the DBusConnection or
725  * the GObject gets finalized.
726  *
727  * @param connection the D-BUS connection
728  * @param at_path the path where the object will live (the object's name)
729  * @param object the object
730  */
731 void
732 dbus_connection_register_gobject (DBusConnection        *connection,
733                                   const char            *at_path,
734                                   GObject               *object)
735 {
736   char **split;
737
738   g_return_if_fail (connection != NULL);
739   g_return_if_fail (at_path != NULL);
740   g_return_if_fail (G_IS_OBJECT (object));
741
742   split = split_path (at_path);
743
744   if (!dbus_connection_register_object_path (connection,
745                                              (const char**) split,
746                                              &gobject_dbus_vtable,
747                                              object))
748     g_error ("Failed to register GObject with DBusConnection");
749
750   g_strfreev (split);
751
752   /* FIXME set up memory management (so we break the
753    * registration if object or connection vanishes)
754    */
755 }
756
757 /** @} */ /* end of public API */
758
759 #ifdef DBUS_BUILD_TESTS
760 #include <stdlib.h>
761
762 /**
763  * @ingroup DBusGLibInternals
764  * Unit test for GLib GObject integration ("skeletons")
765  * @returns #TRUE on success.
766  */
767 dbus_bool_t
768 _dbus_gobject_test (const char *test_data_dir)
769 {
770   int i;
771   static struct { const char *wincaps; const char *uscore; } name_pairs[] = {
772     { "SetFoo", "set_foo" },
773     { "Foo", "foo" },
774     { "GetFooBar", "get_foo_bar" },
775     { "Hello", "hello" }
776     
777     /* Impossible-to-handle cases */
778     /* { "FrobateUIHandler", "frobate_ui_handler" } */
779   };
780
781   i = 0;
782   while (i < (int) G_N_ELEMENTS (name_pairs))
783     {
784       char *uscore;
785       char *wincaps;
786
787       uscore = wincaps_to_uscore (name_pairs[i].wincaps);
788       wincaps = uscore_to_wincaps (name_pairs[i].uscore);
789
790       if (strcmp (uscore, name_pairs[i].uscore) != 0)
791         {
792           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
793                       name_pairs[i].wincaps, name_pairs[i].uscore,
794                       uscore);
795           exit (1);
796         }
797       
798       if (strcmp (wincaps, name_pairs[i].wincaps) != 0)
799         {
800           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
801                       name_pairs[i].uscore, name_pairs[i].wincaps,
802                       wincaps);
803           exit (1);
804         }
805       
806       g_free (uscore);
807       g_free (wincaps);
808
809       ++i;
810     }
811   
812   return TRUE;
813 }
814
815 #endif /* DBUS_BUILD_TESTS */