2004-03-29 Michael Meeks <michael@ximian.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 2.0
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 "dbus-gutils.h"
28 #include "dbus-gvalue.h"
29 #include <string.h>
30
31 /**
32  * @addtogroup DBusGLibInternals
33  * @{
34  */
35
36 static GStaticMutex info_hash_mutex = G_STATIC_MUTEX_INIT;
37 static GHashTable *info_hash = NULL;
38
39 static char*
40 wincaps_to_uscore (const char *caps)
41 {
42   const char *p;
43   GString *str;
44
45   str = g_string_new (NULL);
46   p = caps;
47   while (*p)
48     {
49       if (g_ascii_isupper (*p))
50         {
51           if (str->len > 0 &&
52               (str->len < 2 || str->str[str->len-2] != '_'))
53             g_string_append_c (str, '_');
54           g_string_append_c (str, g_ascii_tolower (*p));
55         }
56       else
57         {
58           g_string_append_c (str, *p);
59         }
60       ++p;
61     }
62
63   return g_string_free (str, FALSE);
64 }
65
66 static char*
67 uscore_to_wincaps (const char *uscore)
68 {
69   const char *p;
70   GString *str;
71   gboolean last_was_uscore;
72
73   last_was_uscore = TRUE;
74   
75   str = g_string_new (NULL);
76   p = uscore;
77   while (*p)
78     {
79       if (*p == '-' || *p == '_')
80         {
81           last_was_uscore = TRUE;
82         }
83       else
84         {
85           if (last_was_uscore)
86             {
87               g_string_append_c (str, g_ascii_toupper (*p));
88               last_was_uscore = FALSE;
89             }
90           else
91             g_string_append_c (str, *p);
92         }
93       ++p;
94     }
95
96   return g_string_free (str, FALSE);
97 }
98
99 static void
100 gobject_unregister_function (DBusConnection  *connection,
101                              void            *user_data)
102 {
103   GObject *object;
104
105   object = G_OBJECT (user_data);
106
107   /* FIXME */
108
109 }
110
111 static int
112 gtype_to_dbus_type (GType type)
113 {
114   switch (type)
115     {
116     case G_TYPE_CHAR:
117     case G_TYPE_UCHAR:
118       return DBUS_TYPE_BYTE;
119       
120     case G_TYPE_BOOLEAN:
121       return DBUS_TYPE_BOOLEAN;
122
123       /* long gets cut to 32 bits so the remote API is consistent
124        * on all architectures
125        */
126       
127     case G_TYPE_LONG:
128     case G_TYPE_INT:
129       return DBUS_TYPE_INT32;
130     case G_TYPE_ULONG:
131     case G_TYPE_UINT:
132       return DBUS_TYPE_UINT32;
133
134     case G_TYPE_INT64:
135       return DBUS_TYPE_INT64;
136
137     case G_TYPE_UINT64:
138       return DBUS_TYPE_UINT64;
139       
140     case G_TYPE_FLOAT:
141     case G_TYPE_DOUBLE:
142       return DBUS_TYPE_DOUBLE;
143
144     case G_TYPE_STRING:
145       return DBUS_TYPE_STRING;
146
147     default:
148       return DBUS_TYPE_INVALID;
149     }
150 }
151
152 static const char *
153 dbus_type_to_string (int type)
154 {
155   switch (type)
156     {
157     case DBUS_TYPE_INVALID:
158       return "invalid";
159     case DBUS_TYPE_NIL:
160       return "nil";
161     case DBUS_TYPE_BOOLEAN:
162       return "boolean";
163     case DBUS_TYPE_INT32:
164       return "int32";
165     case DBUS_TYPE_UINT32:
166       return "uint32";
167     case DBUS_TYPE_DOUBLE:
168       return "double";
169     case DBUS_TYPE_STRING:
170       return "string";
171     case DBUS_TYPE_CUSTOM:
172       return "custom";
173     case DBUS_TYPE_ARRAY:
174       return "array";
175     case DBUS_TYPE_DICT:
176       return "dict";
177     default:
178       return "unknown";
179     }
180 }
181
182 static DBusHandlerResult
183 handle_introspect (DBusConnection *connection,
184                    DBusMessage    *message,
185                    GObject        *object)
186 {
187   GString *xml;
188   GParamSpec **specs;
189   unsigned int n_specs;
190   unsigned int i;
191   GType last_type;
192   DBusMessage *ret;
193   char **path;
194   char **children;
195   
196   if (!dbus_message_get_path_decomposed (message, &path))
197     g_error ("Out of memory");
198
199   if (!dbus_connection_list_registered (connection, (const char**) path,
200                                         &children))
201     g_error ("Out of memory");
202   
203   xml = g_string_new (NULL);
204
205   g_string_append (xml, "<node>\n");
206
207   last_type = G_TYPE_INVALID;
208
209   specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
210                                           &n_specs);
211
212   i = 0;
213   while (i < n_specs)
214     {
215       GParamSpec *spec = specs[i];
216       gboolean can_set;
217       gboolean can_get;
218       char *s;
219       int dbus_type;
220       
221       dbus_type = gtype_to_dbus_type (G_PARAM_SPEC_VALUE_TYPE (spec));
222       if (dbus_type == DBUS_TYPE_INVALID)
223         goto next;
224       
225       if (spec->owner_type != last_type)
226         {
227           if (last_type != G_TYPE_INVALID)
228             g_string_append (xml, "  </interface>\n");
229
230
231           /* FIXME what should the namespace on the interface be in
232            * general?  should people be able to set it for their
233            * objects?
234            */
235           
236           g_string_append (xml, "  <interface name=\"org.gtk.objects.");
237           g_string_append (xml, g_type_name (spec->owner_type));
238           g_string_append (xml, "\">\n");
239
240           last_type = spec->owner_type;
241         }
242
243       can_set = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
244                     (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
245
246       can_get = (spec->flags & G_PARAM_READABLE) != 0;
247
248       s = uscore_to_wincaps (spec->name);
249       
250       if (can_set)
251         {
252           g_string_append (xml, "    <method name=\"set_");
253           g_string_append (xml, s);
254           g_string_append (xml, "\">\n");
255           
256           g_string_append (xml, "      <arg type=\"");
257           g_string_append (xml, dbus_type_to_string (dbus_type));
258           g_string_append (xml, "\"/>\n");
259         }
260
261       if (can_get)
262         {
263           g_string_append (xml, "    <method name=\"get_");
264           g_string_append (xml, s);
265           g_string_append (xml, "\">\n");
266           
267           g_string_append (xml, "      <arg type=\"");
268           g_string_append (xml, dbus_type_to_string (dbus_type));
269           g_string_append (xml, "\" direction=\"out\"/>\n");
270         }
271
272       g_free (s);
273
274     next:
275       ++i;
276     }
277
278   if (last_type != G_TYPE_INVALID)
279     g_string_append (xml, "  </interface>\n");
280
281   g_free (specs);
282
283   /* Append child nodes */
284   
285   i = 0;
286   while (children[i])
287     {
288       g_string_append_printf (xml, "  <node name=\"%s\"/>\n",
289                               children[i]);
290       ++i;
291     }
292   
293   /* Close the XML, and send it to the requesting app */
294
295   g_string_append (xml, "</node>\n");
296
297   ret = dbus_message_new_method_return (message);
298   if (ret == NULL)
299     g_error ("Out of memory");
300
301   dbus_message_append_args (message,
302                             DBUS_TYPE_STRING, xml->str,
303                             DBUS_TYPE_INVALID);
304
305   dbus_connection_send (connection, message, NULL);
306   dbus_message_unref (message);
307
308   g_string_free (xml, TRUE);
309
310   dbus_free_string_array (path);
311   dbus_free_string_array (children);
312   
313   return DBUS_HANDLER_RESULT_HANDLED;
314 }
315
316 static DBusMessage*
317 set_object_property (DBusConnection *connection,
318                      DBusMessage    *message,
319                      GObject        *object,
320                      GParamSpec     *pspec)
321 {
322   GValue value = { 0, };
323   DBusMessage *ret;
324   DBusMessageIter iter;
325
326   dbus_message_iter_init (message, &iter);
327
328   /* The g_object_set_property() will transform some types, e.g. it
329    * will let you use a uchar to set an int property etc. Note that
330    * any error in value range or value conversion will just
331    * g_warning(). These GObject skels are not for secure applications.
332    */
333   if (dbus_gvalue_demarshal (&iter, &value))
334     {
335       g_object_set_property (object,
336                              pspec->name,
337                              &value);
338
339       g_value_unset (&value);
340
341       ret = dbus_message_new_method_return (message);
342       if (ret == NULL)
343         g_error ("out of memory");
344     }
345   else
346     {
347       ret = dbus_message_new_error (message,
348                                     DBUS_ERROR_INVALID_ARGS,
349                                     "Argument's D-BUS type can't be converted to a GType");
350       if (ret == NULL)
351         g_error ("out of memory");
352     }
353
354   return ret;
355 }
356
357 static DBusMessage*
358 get_object_property (DBusConnection *connection,
359                      DBusMessage    *message,
360                      GObject        *object,
361                      GParamSpec     *pspec)
362 {
363   GType value_type;
364   GValue value;
365   DBusMessage *ret;
366   DBusMessageIter iter;
367
368   value_type = G_PARAM_SPEC_VALUE_TYPE (pspec);
369
370   ret = dbus_message_new_method_return (message);
371   if (ret == NULL)
372     g_error ("out of memory");
373
374   g_value_init (&value, value_type);
375   g_object_get_property (object, pspec->name, &value);
376
377   value_type = G_VALUE_TYPE (&value);
378
379   dbus_message_append_iter_init (message, &iter);
380
381   if (!dbus_gvalue_marshal (&iter, &value))
382     {
383       dbus_message_unref (ret);
384       ret = dbus_message_new_error (message,
385                                     DBUS_ERROR_UNKNOWN_METHOD,
386                                     "Can't convert GType of object property to a D-BUS type");
387     }
388
389   return ret;
390 }
391
392 static DBusHandlerResult
393 gobject_message_function (DBusConnection  *connection,
394                           DBusMessage     *message,
395                           void            *user_data)
396 {
397   const DBusGObjectInfo *info;
398   GParamSpec *pspec;
399   GObject *object;
400   const char *member;
401   gboolean setter;
402   gboolean getter;
403   char *s;
404
405   object = G_OBJECT (user_data);
406
407   if (dbus_message_is_method_call (message,
408                                    DBUS_INTERFACE_ORG_FREEDESKTOP_INTROSPECTABLE,
409                                    "Introspect"))
410     return handle_introspect (connection, message, object);
411
412   member = dbus_message_get_member (message);
413
414   /* Try the metainfo, which lets us invoke methods */
415
416   g_static_mutex_lock (&info_hash_mutex);
417   /* FIXME this needs to walk up the inheritance tree, not
418    * just look at the most-derived class
419    */
420   info = g_hash_table_lookup (info_hash,
421                               G_OBJECT_GET_CLASS (object));
422   g_static_mutex_unlock (&info_hash_mutex);
423
424   if (info != NULL)
425     {
426
427
428
429     }
430
431   /* If no metainfo, we can still do properties and signals
432    * via standard GLib introspection
433    */
434   setter = (member[0] == 's' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
435   getter = (member[0] == 'g' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
436
437   if (!(setter || getter))
438     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
439
440   s = wincaps_to_uscore (&member[4]);
441
442   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),
443                                         s);
444
445   g_free (s);
446
447   if (pspec != NULL)
448     {
449       DBusMessage *ret;
450
451       if (setter)
452         {
453           ret = set_object_property (connection, message,
454                                      object, pspec);
455         }
456       else if (getter)
457         {
458           ret = get_object_property (connection, message,
459                                      object, pspec);
460         }
461       else
462         {
463           g_assert_not_reached ();
464           ret = NULL;
465         }
466
467       g_assert (ret != NULL);
468
469       dbus_connection_send (connection, ret, NULL);
470       dbus_message_unref (ret);
471       return DBUS_HANDLER_RESULT_HANDLED;
472     }
473
474   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
475 }
476
477 static DBusObjectPathVTable gobject_dbus_vtable = {
478   gobject_unregister_function,
479   gobject_message_function,
480   NULL
481 };
482
483 /** @} */ /* end of internals */
484
485 /**
486  * @addtogroup DBusGLib
487  * @{
488  */
489
490 /**
491  * Install introspection information about the given object class
492  * sufficient to allow methods on the object to be invoked by name.
493  * The introspection information is normally generated by
494  * dbus-glib-tool, then this function is called in the
495  * class_init() for the object class.
496  *
497  * Once introspection information has been installed, instances of the
498  * object registered with dbus_connection_register_g_object() can have
499  * their methods invoked remotely.
500  *
501  * @param object_class class struct of the object
502  * @param info introspection data generated by dbus-glib-tool
503  */
504 void
505 dbus_g_object_class_install_info (GObjectClass          *object_class,
506                                   const DBusGObjectInfo *info)
507 {
508   g_return_if_fail (G_IS_OBJECT_CLASS (object_class));
509
510   g_static_mutex_lock (&info_hash_mutex);
511
512   if (info_hash == NULL)
513     {
514       info_hash = g_hash_table_new (NULL, NULL); /* direct hash */
515     }
516
517   g_hash_table_replace (info_hash, object_class, (void*) info);
518
519   g_static_mutex_unlock (&info_hash_mutex);
520 }
521
522 /**
523  * Registers a GObject at the given path. Properties, methods, and signals
524  * of the object can then be accessed remotely. Methods are only available
525  * if method introspection data has been added to the object's class
526  * with g_object_class_install_info().
527  *
528  * The registration will be cancelled if either the DBusConnection or
529  * the GObject gets finalized.
530  *
531  * @param connection the D-BUS connection
532  * @param at_path the path where the object will live (the object's name)
533  * @param object the object
534  */
535 void
536 dbus_connection_register_g_object (DBusConnection        *connection,
537                                    const char            *at_path,
538                                    GObject               *object)
539 {
540   char **split;
541
542   g_return_if_fail (connection != NULL);
543   g_return_if_fail (at_path != NULL);
544   g_return_if_fail (G_IS_OBJECT (object));
545
546   split = _dbus_gutils_split_path (at_path);
547
548   if (!dbus_connection_register_object_path (connection,
549                                              (const char**) split,
550                                              &gobject_dbus_vtable,
551                                              object))
552     g_error ("Failed to register GObject with DBusConnection");
553
554   g_strfreev (split);
555
556   /* FIXME set up memory management (so we break the
557    * registration if object or connection vanishes)
558    */
559 }
560
561 /** @} */ /* end of public API */
562
563 #ifdef DBUS_BUILD_TESTS
564 #include <stdlib.h>
565
566 /**
567  * @ingroup DBusGLibInternals
568  * Unit test for GLib GObject integration ("skeletons")
569  * @returns #TRUE on success.
570  */
571 dbus_bool_t
572 _dbus_gobject_test (const char *test_data_dir)
573 {
574   int i;
575   static struct { const char *wincaps; const char *uscore; } name_pairs[] = {
576     { "SetFoo", "set_foo" },
577     { "Foo", "foo" },
578     { "GetFooBar", "get_foo_bar" },
579     { "Hello", "hello" }
580     
581     /* Impossible-to-handle cases */
582     /* { "FrobateUIHandler", "frobate_ui_handler" } */
583   };
584
585   i = 0;
586   while (i < (int) G_N_ELEMENTS (name_pairs))
587     {
588       char *uscore;
589       char *wincaps;
590
591       uscore = wincaps_to_uscore (name_pairs[i].wincaps);
592       wincaps = uscore_to_wincaps (name_pairs[i].uscore);
593
594       if (strcmp (uscore, name_pairs[i].uscore) != 0)
595         {
596           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
597                       name_pairs[i].wincaps, name_pairs[i].uscore,
598                       uscore);
599           exit (1);
600         }
601       
602       if (strcmp (wincaps, name_pairs[i].wincaps) != 0)
603         {
604           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
605                       name_pairs[i].uscore, name_pairs[i].wincaps,
606                       wincaps);
607           exit (1);
608         }
609       
610       g_free (uscore);
611       g_free (wincaps);
612
613       ++i;
614     }
615   
616   return TRUE;
617 }
618
619 #endif /* DBUS_BUILD_TESTS */