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