0168697e96799f9f193370bba6ef9a75b82d6d72
[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 void
153 introspect_properties (GObject *object, GString *xml)
154 {
155   unsigned int i;
156   unsigned int n_specs;
157   GType last_type;
158   GParamSpec **specs;
159
160   last_type = G_TYPE_INVALID;
161   specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object),
162                                           &n_specs);
163
164   for (i = 0; i < n_specs; i++ )
165     {
166       char *s;
167       int dbus_type;
168       gboolean can_set;
169       gboolean can_get;
170       GParamSpec *spec = specs[i];
171       
172       dbus_type = gtype_to_dbus_type (G_PARAM_SPEC_VALUE_TYPE (spec));
173       if (dbus_type == DBUS_TYPE_INVALID)
174         continue;
175       
176       if (spec->owner_type != last_type)
177         {
178           if (last_type != G_TYPE_INVALID)
179             g_string_append (xml, "  </interface>\n");
180
181
182           /* FIXME what should the namespace on the interface be in
183            * general?  should people be able to set it for their
184            * objects?
185            */
186           g_string_append (xml, "  <interface name=\"org.gtk.objects.");
187           g_string_append (xml, g_type_name (spec->owner_type));
188           g_string_append (xml, "\">\n");
189
190           last_type = spec->owner_type;
191         }
192
193       can_set = ((spec->flags & G_PARAM_WRITABLE) != 0 &&
194                  (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0);
195       
196       can_get = (spec->flags & G_PARAM_READABLE) != 0;
197
198       s = uscore_to_wincaps (spec->name);
199       
200       if (can_set)
201         {
202           g_string_append (xml, "    <method name=\"set_");
203           g_string_append (xml, s);
204           g_string_append (xml, "\">\n");
205           
206           g_string_append (xml, "      <arg type=\"");
207           g_string_append (xml, _dbus_gutils_type_to_string (dbus_type));
208           g_string_append (xml, "\"/>\n");
209         }
210
211       if (can_get)
212         {
213           g_string_append (xml, "    <method name=\"get_");
214           g_string_append (xml, s);
215           g_string_append (xml, "\">\n");
216           
217           g_string_append (xml, "      <arg type=\"");
218           g_string_append (xml, _dbus_gutils_type_to_string (dbus_type));
219           g_string_append (xml, "\" direction=\"out\"/>\n");
220         }
221
222       g_free (s);
223     }
224
225   if (last_type != G_TYPE_INVALID)
226     g_string_append (xml, "  </interface>\n");
227
228   g_free (specs);
229 }
230
231 static void
232 introspect_signals (GType type, GString *xml)
233 {
234   guint i;
235   guint *ids, n_ids;
236
237   ids = g_signal_list_ids (type, &n_ids);
238   if (!n_ids)
239     return;
240
241   g_string_append (xml, "  <interface name=\"org.gtk.objects.");
242   g_string_append (xml, g_type_name (type));
243   g_string_append (xml, "\">\n");
244
245   /* FIXME: recurse to parent types ? */
246   for (i = 0; i < n_ids; i++)
247     {
248       guint arg;
249       GSignalQuery query;
250       
251       g_signal_query (ids[i], &query);
252
253       if (query.return_type)
254         continue; /* FIXME: these could be listed as methods ? */
255
256       g_string_append (xml, "    <signal name=\"");
257       g_string_append (xml, query.signal_name);
258       g_string_append (xml, "\">\n");
259
260       for (arg = 0; arg < query.n_params; arg++)
261         {
262           int dbus_type = gtype_to_dbus_type (query.param_types[arg]);
263
264           g_string_append (xml, "      <arg type=\"");
265           g_string_append (xml, _dbus_gutils_type_to_string (dbus_type));
266           g_string_append (xml, "\"/>\n");
267         }
268
269       g_string_append (xml, "    </signal>\n");
270     }
271
272   g_string_append (xml, "  </interface>\n");
273 }
274
275 static DBusHandlerResult
276 handle_introspect (DBusConnection *connection,
277                    DBusMessage    *message,
278                    GObject        *object)
279 {
280   GString *xml;
281   unsigned int i;
282   DBusMessage *ret;
283   char **path;
284   char **children;
285   
286   if (!dbus_message_get_path_decomposed (message, &path))
287     g_error ("Out of memory");
288
289   if (!dbus_connection_list_registered (connection, (const char**) path,
290                                         &children))
291     g_error ("Out of memory");
292   
293   xml = g_string_new (NULL);
294
295   introspect_signals (G_OBJECT_TYPE (object), xml);
296   introspect_properties (object, xml);
297           
298   g_string_append (xml, "<node>\n");
299
300   /* Append child nodes */
301   for (i = 0; children[i]; i++)
302       g_string_append_printf (xml, "  <node name=\"%s\"/>\n",
303                               children[i]);
304   
305   /* Close the XML, and send it to the requesting app */
306   g_string_append (xml, "</node>\n");
307
308   ret = dbus_message_new_method_return (message);
309   if (ret == NULL)
310     g_error ("Out of memory");
311
312   dbus_message_append_args (message,
313                             DBUS_TYPE_STRING, xml->str,
314                             DBUS_TYPE_INVALID);
315
316   dbus_connection_send (connection, message, NULL);
317   dbus_message_unref (message);
318
319   g_string_free (xml, TRUE);
320
321   dbus_free_string_array (path);
322   dbus_free_string_array (children);
323   
324   return DBUS_HANDLER_RESULT_HANDLED;
325 }
326
327 static DBusMessage*
328 set_object_property (DBusConnection *connection,
329                      DBusMessage    *message,
330                      GObject        *object,
331                      GParamSpec     *pspec)
332 {
333   GValue value = { 0, };
334   DBusMessage *ret;
335   DBusMessageIter iter;
336
337   dbus_message_iter_init (message, &iter);
338
339   /* The g_object_set_property() will transform some types, e.g. it
340    * will let you use a uchar to set an int property etc. Note that
341    * any error in value range or value conversion will just
342    * g_warning(). These GObject skels are not for secure applications.
343    */
344   if (dbus_gvalue_demarshal (&iter, &value))
345     {
346       g_object_set_property (object,
347                              pspec->name,
348                              &value);
349
350       g_value_unset (&value);
351
352       ret = dbus_message_new_method_return (message);
353       if (ret == NULL)
354         g_error ("out of memory");
355     }
356   else
357     {
358       ret = dbus_message_new_error (message,
359                                     DBUS_ERROR_INVALID_ARGS,
360                                     "Argument's D-BUS type can't be converted to a GType");
361       if (ret == NULL)
362         g_error ("out of memory");
363     }
364
365   return ret;
366 }
367
368 static DBusMessage*
369 get_object_property (DBusConnection *connection,
370                      DBusMessage    *message,
371                      GObject        *object,
372                      GParamSpec     *pspec)
373 {
374   GType value_type;
375   GValue value;
376   DBusMessage *ret;
377   DBusMessageIter iter;
378
379   value_type = G_PARAM_SPEC_VALUE_TYPE (pspec);
380
381   ret = dbus_message_new_method_return (message);
382   if (ret == NULL)
383     g_error ("out of memory");
384
385   g_value_init (&value, value_type);
386   g_object_get_property (object, pspec->name, &value);
387
388   value_type = G_VALUE_TYPE (&value);
389
390   dbus_message_append_iter_init (message, &iter);
391
392   if (!dbus_gvalue_marshal (&iter, &value))
393     {
394       dbus_message_unref (ret);
395       ret = dbus_message_new_error (message,
396                                     DBUS_ERROR_UNKNOWN_METHOD,
397                                     "Can't convert GType of object property to a D-BUS type");
398     }
399
400   return ret;
401 }
402
403 static DBusHandlerResult
404 gobject_message_function (DBusConnection  *connection,
405                           DBusMessage     *message,
406                           void            *user_data)
407 {
408   const DBusGObjectInfo *info;
409   GParamSpec *pspec;
410   GObject *object;
411   const char *member;
412   gboolean setter;
413   gboolean getter;
414   char *s;
415
416   object = G_OBJECT (user_data);
417
418   if (dbus_message_is_method_call (message,
419                                    DBUS_INTERFACE_ORG_FREEDESKTOP_INTROSPECTABLE,
420                                    "Introspect"))
421     return handle_introspect (connection, message, object);
422
423   member = dbus_message_get_member (message);
424
425   /* Try the metainfo, which lets us invoke methods */
426
427   g_static_mutex_lock (&info_hash_mutex);
428   /* FIXME this needs to walk up the inheritance tree, not
429    * just look at the most-derived class
430    */
431   info = g_hash_table_lookup (info_hash,
432                               G_OBJECT_GET_CLASS (object));
433   g_static_mutex_unlock (&info_hash_mutex);
434
435   if (info != NULL)
436     {
437
438
439
440     }
441
442   /* If no metainfo, we can still do properties and signals
443    * via standard GLib introspection
444    */
445   setter = (member[0] == 's' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
446   getter = (member[0] == 'g' && member[1] == 'e' && member[2] == 't' && member[3] == '_');
447
448   if (!(setter || getter))
449     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
450
451   s = wincaps_to_uscore (&member[4]);
452
453   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object),
454                                         s);
455
456   g_free (s);
457
458   if (pspec != NULL)
459     {
460       DBusMessage *ret;
461
462       if (setter)
463         {
464           ret = set_object_property (connection, message,
465                                      object, pspec);
466         }
467       else if (getter)
468         {
469           ret = get_object_property (connection, message,
470                                      object, pspec);
471         }
472       else
473         {
474           g_assert_not_reached ();
475           ret = NULL;
476         }
477
478       g_assert (ret != NULL);
479
480       dbus_connection_send (connection, ret, NULL);
481       dbus_message_unref (ret);
482       return DBUS_HANDLER_RESULT_HANDLED;
483     }
484
485   return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
486 }
487
488 static DBusObjectPathVTable gobject_dbus_vtable = {
489   gobject_unregister_function,
490   gobject_message_function,
491   NULL
492 };
493
494 /** @} */ /* end of internals */
495
496 /**
497  * @addtogroup DBusGLib
498  * @{
499  */
500
501 /**
502  * Install introspection information about the given object class
503  * sufficient to allow methods on the object to be invoked by name.
504  * The introspection information is normally generated by
505  * dbus-glib-tool, then this function is called in the
506  * class_init() for the object class.
507  *
508  * Once introspection information has been installed, instances of the
509  * object registered with dbus_connection_register_g_object() can have
510  * their methods invoked remotely.
511  *
512  * @param object_class class struct of the object
513  * @param info introspection data generated by dbus-glib-tool
514  */
515 void
516 dbus_g_object_class_install_info (GObjectClass          *object_class,
517                                   const DBusGObjectInfo *info)
518 {
519   g_return_if_fail (G_IS_OBJECT_CLASS (object_class));
520
521   g_static_mutex_lock (&info_hash_mutex);
522
523   if (info_hash == NULL)
524     {
525       info_hash = g_hash_table_new (NULL, NULL); /* direct hash */
526     }
527
528   g_hash_table_replace (info_hash, object_class, (void*) info);
529
530   g_static_mutex_unlock (&info_hash_mutex);
531 }
532
533 /**
534  * Registers a GObject at the given path. Properties, methods, and signals
535  * of the object can then be accessed remotely. Methods are only available
536  * if method introspection data has been added to the object's class
537  * with g_object_class_install_info().
538  *
539  * The registration will be cancelled if either the DBusConnection or
540  * the GObject gets finalized.
541  *
542  * @param connection the D-BUS connection
543  * @param at_path the path where the object will live (the object's name)
544  * @param object the object
545  */
546 void
547 dbus_connection_register_g_object (DBusConnection        *connection,
548                                    const char            *at_path,
549                                    GObject               *object)
550 {
551   char **split;
552
553   g_return_if_fail (connection != NULL);
554   g_return_if_fail (at_path != NULL);
555   g_return_if_fail (G_IS_OBJECT (object));
556
557   split = _dbus_gutils_split_path (at_path);
558
559   if (!dbus_connection_register_object_path (connection,
560                                              (const char**) split,
561                                              &gobject_dbus_vtable,
562                                              object))
563     g_error ("Failed to register GObject with DBusConnection");
564
565   g_strfreev (split);
566
567   /* FIXME set up memory management (so we break the
568    * registration if object or connection vanishes)
569    */
570 }
571
572 /** @} */ /* end of public API */
573
574 #ifdef DBUS_BUILD_TESTS
575 #include <stdlib.h>
576
577 /**
578  * @ingroup DBusGLibInternals
579  * Unit test for GLib GObject integration ("skeletons")
580  * @returns #TRUE on success.
581  */
582 dbus_bool_t
583 _dbus_gobject_test (const char *test_data_dir)
584 {
585   int i;
586   static struct { const char *wincaps; const char *uscore; } name_pairs[] = {
587     { "SetFoo", "set_foo" },
588     { "Foo", "foo" },
589     { "GetFooBar", "get_foo_bar" },
590     { "Hello", "hello" }
591     
592     /* Impossible-to-handle cases */
593     /* { "FrobateUIHandler", "frobate_ui_handler" } */
594   };
595
596   i = 0;
597   while (i < (int) G_N_ELEMENTS (name_pairs))
598     {
599       char *uscore;
600       char *wincaps;
601
602       uscore = wincaps_to_uscore (name_pairs[i].wincaps);
603       wincaps = uscore_to_wincaps (name_pairs[i].uscore);
604
605       if (strcmp (uscore, name_pairs[i].uscore) != 0)
606         {
607           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
608                       name_pairs[i].wincaps, name_pairs[i].uscore,
609                       uscore);
610           exit (1);
611         }
612       
613       if (strcmp (wincaps, name_pairs[i].wincaps) != 0)
614         {
615           g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n",
616                       name_pairs[i].uscore, name_pairs[i].wincaps,
617                       wincaps);
618           exit (1);
619         }
620       
621       g_free (uscore);
622       g_free (wincaps);
623
624       ++i;
625     }
626   
627   return TRUE;
628 }
629
630 #endif /* DBUS_BUILD_TESTS */