/* -*- mode: C; c-file-style: "gnu" -*- */ /* dbus-gobject.c Exporting a GObject remotely * * Copyright (C) 2003, 2004 Red Hat, Inc. * * Licensed under the Academic Free License version 2.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include "dbus-gtest.h" #include "dbus-gutils.h" #include "dbus-gvalue.h" #include /** * @addtogroup DBusGLibInternals * @{ */ static GStaticMutex info_hash_mutex = G_STATIC_MUTEX_INIT; static GHashTable *info_hash = NULL; static char* wincaps_to_uscore (const char *caps) { const char *p; GString *str; str = g_string_new (NULL); p = caps; while (*p) { if (g_ascii_isupper (*p)) { if (str->len > 0 && (str->len < 2 || str->str[str->len-2] != '_')) g_string_append_c (str, '_'); g_string_append_c (str, g_ascii_tolower (*p)); } else { g_string_append_c (str, *p); } ++p; } return g_string_free (str, FALSE); } static char* uscore_to_wincaps (const char *uscore) { const char *p; GString *str; gboolean last_was_uscore; last_was_uscore = TRUE; str = g_string_new (NULL); p = uscore; while (*p) { if (*p == '-' || *p == '_') { last_was_uscore = TRUE; } else { if (last_was_uscore) { g_string_append_c (str, g_ascii_toupper (*p)); last_was_uscore = FALSE; } else g_string_append_c (str, *p); } ++p; } return g_string_free (str, FALSE); } static void gobject_unregister_function (DBusConnection *connection, void *user_data) { GObject *object; object = G_OBJECT (user_data); /* FIXME */ } static int gtype_to_dbus_type (GType type) { switch (type) { case G_TYPE_CHAR: case G_TYPE_UCHAR: return DBUS_TYPE_BYTE; case G_TYPE_BOOLEAN: return DBUS_TYPE_BOOLEAN; /* long gets cut to 32 bits so the remote API is consistent * on all architectures */ case G_TYPE_LONG: case G_TYPE_INT: return DBUS_TYPE_INT32; case G_TYPE_ULONG: case G_TYPE_UINT: return DBUS_TYPE_UINT32; case G_TYPE_INT64: return DBUS_TYPE_INT64; case G_TYPE_UINT64: return DBUS_TYPE_UINT64; case G_TYPE_FLOAT: case G_TYPE_DOUBLE: return DBUS_TYPE_DOUBLE; case G_TYPE_STRING: return DBUS_TYPE_STRING; default: return DBUS_TYPE_INVALID; } } static void introspect_properties (GObject *object, GString *xml) { unsigned int i; unsigned int n_specs; GType last_type; GParamSpec **specs; last_type = G_TYPE_INVALID; specs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &n_specs); for (i = 0; i < n_specs; i++ ) { char *s; int dbus_type; gboolean can_set; gboolean can_get; GParamSpec *spec = specs[i]; dbus_type = gtype_to_dbus_type (G_PARAM_SPEC_VALUE_TYPE (spec)); if (dbus_type == DBUS_TYPE_INVALID) continue; if (spec->owner_type != last_type) { if (last_type != G_TYPE_INVALID) g_string_append (xml, " \n"); /* FIXME what should the namespace on the interface be in * general? should people be able to set it for their * objects? */ g_string_append (xml, " owner_type)); g_string_append (xml, "\">\n"); last_type = spec->owner_type; } can_set = ((spec->flags & G_PARAM_WRITABLE) != 0 && (spec->flags & G_PARAM_CONSTRUCT_ONLY) == 0); can_get = (spec->flags & G_PARAM_READABLE) != 0; s = uscore_to_wincaps (spec->name); if (can_set || can_get) { g_string_append (xml, " \n"); } g_free (s); } if (last_type != G_TYPE_INVALID) g_string_append (xml, " \n"); g_free (specs); } static void introspect_signals (GType type, GString *xml) { guint i; guint *ids, n_ids; ids = g_signal_list_ids (type, &n_ids); if (!n_ids) return; g_string_append (xml, " \n"); /* FIXME: recurse to parent types ? */ for (i = 0; i < n_ids; i++) { guint arg; GSignalQuery query; g_signal_query (ids[i], &query); if (query.return_type) continue; /* FIXME: these could be listed as methods ? */ g_string_append (xml, " \n"); for (arg = 0; arg < query.n_params; arg++) { int dbus_type = gtype_to_dbus_type (query.param_types[arg]); g_string_append (xml, " \n"); } g_string_append (xml, " \n"); } g_string_append (xml, " \n"); } static DBusHandlerResult handle_introspect (DBusConnection *connection, DBusMessage *message, GObject *object) { GString *xml; unsigned int i; DBusMessage *ret; char **children; if (!dbus_connection_list_registered (connection, dbus_message_get_path (message), &children)) g_error ("Out of memory"); xml = g_string_new (NULL); g_string_append (xml, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); g_string_append (xml, "\n"); /* We are introspectable, though I guess that was pretty obvious */ g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); /* We support get/set properties */ g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); g_string_append (xml, " \n"); introspect_signals (G_OBJECT_TYPE (object), xml); introspect_properties (object, xml); /* Append child nodes */ for (i = 0; children[i]; i++) g_string_append_printf (xml, " \n", children[i]); /* Close the XML, and send it to the requesting app */ g_string_append (xml, "\n"); ret = dbus_message_new_method_return (message); if (ret == NULL) g_error ("Out of memory"); dbus_message_append_args (ret, DBUS_TYPE_STRING, &xml->str, DBUS_TYPE_INVALID); dbus_connection_send (connection, ret, NULL); dbus_message_unref (ret); g_string_free (xml, TRUE); dbus_free_string_array (children); return DBUS_HANDLER_RESULT_HANDLED; } static DBusMessage* set_object_property (DBusConnection *connection, DBusMessage *message, DBusMessageIter *iter, GObject *object, GParamSpec *pspec) { GValue value = { 0, }; DBusMessage *ret; DBusMessageIter sub; dbus_message_iter_recurse (iter, &sub); /* The g_object_set_property() will transform some types, e.g. it * will let you use a uchar to set an int property etc. Note that * any error in value range or value conversion will just * g_warning(). These GObject skels are not for secure applications. */ if (dbus_gvalue_demarshal (&sub, &value)) { g_object_set_property (object, pspec->name, &value); g_value_unset (&value); ret = dbus_message_new_method_return (message); if (ret == NULL) g_error ("out of memory"); } else { ret = dbus_message_new_error (message, DBUS_ERROR_INVALID_ARGS, "Argument's D-BUS type can't be converted to a GType"); if (ret == NULL) g_error ("out of memory"); } return ret; } static DBusMessage* get_object_property (DBusConnection *connection, DBusMessage *message, GObject *object, GParamSpec *pspec) { GType value_type; GValue value; DBusMessage *ret; DBusMessageIter iter; value_type = G_PARAM_SPEC_VALUE_TYPE (pspec); ret = dbus_message_new_method_return (message); if (ret == NULL) g_error ("out of memory"); g_value_init (&value, value_type); g_object_get_property (object, pspec->name, &value); value_type = G_VALUE_TYPE (&value); dbus_message_iter_init_append (message, &iter); if (!dbus_gvalue_marshal (&iter, &value)) { dbus_message_unref (ret); ret = dbus_message_new_error (message, DBUS_ERROR_UNKNOWN_METHOD, "Can't convert GType of object property to a D-BUS type"); } return ret; } static DBusHandlerResult gobject_message_function (DBusConnection *connection, DBusMessage *message, void *user_data) { const DBusGObjectInfo *info; GParamSpec *pspec; GObject *object; gboolean setter; gboolean getter; char *s; const char *wincaps_propname; /* const char *wincaps_propiface; */ DBusMessageIter iter; object = G_OBJECT (user_data); if (dbus_message_is_method_call (message, DBUS_INTERFACE_ORG_FREEDESKTOP_INTROSPECTABLE, "Introspect")) return handle_introspect (connection, message, object); /* Try the metainfo, which lets us invoke methods */ g_static_mutex_lock (&info_hash_mutex); /* FIXME this needs to walk up the inheritance tree, not * just look at the most-derived class */ info = g_hash_table_lookup (info_hash, G_OBJECT_GET_CLASS (object)); g_static_mutex_unlock (&info_hash_mutex); if (info != NULL) { } /* If no metainfo, we can still do properties and signals * via standard GLib introspection */ getter = FALSE; setter = FALSE; if (dbus_message_is_method_call (message, DBUS_INTERFACE_ORG_FREEDESKTOP_PROPERTIES, "Get")) getter = TRUE; else if (dbus_message_is_method_call (message, DBUS_INTERFACE_ORG_FREEDESKTOP_PROPERTIES, "Set")) setter = TRUE; if (!(setter || getter)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; dbus_message_iter_init (message, &iter); if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) { g_warning ("Property get or set does not have an interface string as first arg\n"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* We never use the interface name; if we did, we'd need to * remember that it can be empty string for "pick one for me" */ /* dbus_message_iter_get_basic (&iter, &wincaps_propiface); */ dbus_message_iter_next (&iter); if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_STRING) { g_warning ("Property get or set does not have a property name string as second arg\n"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_get_basic (&iter, &wincaps_propname); dbus_message_iter_next (&iter); s = wincaps_to_uscore (wincaps_propname); pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), s); g_free (s); if (pspec != NULL) { DBusMessage *ret; if (setter) { if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_VARIANT) { g_warning ("Property set does not have a variant value as third arg\n"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } ret = set_object_property (connection, message, &iter, object, pspec); dbus_message_iter_next (&iter); } else if (getter) { ret = get_object_property (connection, message, object, pspec); } else { g_assert_not_reached (); ret = NULL; } g_assert (ret != NULL); if (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID) g_warning ("Property get or set had too many arguments\n"); dbus_connection_send (connection, ret, NULL); dbus_message_unref (ret); return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static DBusObjectPathVTable gobject_dbus_vtable = { gobject_unregister_function, gobject_message_function, NULL }; /** @} */ /* end of internals */ /** * @addtogroup DBusGLib * @{ */ /** * Install introspection information about the given object class * sufficient to allow methods on the object to be invoked by name. * The introspection information is normally generated by * dbus-glib-tool, then this function is called in the * class_init() for the object class. * * Once introspection information has been installed, instances of the * object registered with dbus_g_connection_register_g_object() can have * their methods invoked remotely. * * @param object_class class struct of the object * @param info introspection data generated by dbus-glib-tool */ void dbus_g_object_class_install_info (GObjectClass *object_class, const DBusGObjectInfo *info) { g_return_if_fail (G_IS_OBJECT_CLASS (object_class)); g_static_mutex_lock (&info_hash_mutex); if (info_hash == NULL) { info_hash = g_hash_table_new (NULL, NULL); /* direct hash */ } g_hash_table_replace (info_hash, object_class, (void*) info); g_static_mutex_unlock (&info_hash_mutex); } /** * Registers a GObject at the given path. Properties, methods, and signals * of the object can then be accessed remotely. Methods are only available * if method introspection data has been added to the object's class * with g_object_class_install_info(). * * The registration will be cancelled if either the DBusConnection or * the GObject gets finalized. * * @param connection the D-BUS connection * @param at_path the path where the object will live (the object's name) * @param object the object */ void dbus_g_connection_register_g_object (DBusGConnection *connection, const char *at_path, GObject *object) { g_return_if_fail (connection != NULL); g_return_if_fail (at_path != NULL); g_return_if_fail (G_IS_OBJECT (object)); if (!dbus_connection_register_object_path (DBUS_CONNECTION_FROM_G_CONNECTION (connection), at_path, &gobject_dbus_vtable, object)) g_error ("Failed to register GObject with DBusConnection"); /* FIXME set up memory management (so we break the * registration if object or connection vanishes) */ } /** @} */ /* end of public API */ #ifdef DBUS_BUILD_TESTS #include /** * @ingroup DBusGLibInternals * Unit test for GLib GObject integration ("skeletons") * @returns #TRUE on success. */ gboolean _dbus_gobject_test (const char *test_data_dir) { int i; static struct { const char *wincaps; const char *uscore; } name_pairs[] = { { "SetFoo", "set_foo" }, { "Foo", "foo" }, { "GetFooBar", "get_foo_bar" }, { "Hello", "hello" } /* Impossible-to-handle cases */ /* { "FrobateUIHandler", "frobate_ui_handler" } */ }; i = 0; while (i < (int) G_N_ELEMENTS (name_pairs)) { char *uscore; char *wincaps; uscore = wincaps_to_uscore (name_pairs[i].wincaps); wincaps = uscore_to_wincaps (name_pairs[i].uscore); if (strcmp (uscore, name_pairs[i].uscore) != 0) { g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n", name_pairs[i].wincaps, name_pairs[i].uscore, uscore); exit (1); } if (strcmp (wincaps, name_pairs[i].wincaps) != 0) { g_printerr ("\"%s\" should have been converted to \"%s\" not \"%s\"\n", name_pairs[i].uscore, name_pairs[i].wincaps, wincaps); exit (1); } g_free (uscore); g_free (wincaps); ++i; } return TRUE; } #endif /* DBUS_BUILD_TESTS */