diff options
Diffstat (limited to 'glib/dbus-gobject.c')
-rw-r--r-- | glib/dbus-gobject.c | 596 |
1 files changed, 545 insertions, 51 deletions
diff --git a/glib/dbus-gobject.c b/glib/dbus-gobject.c index abaf2e0e..3bcd90d5 100644 --- a/glib/dbus-gobject.c +++ b/glib/dbus-gobject.c @@ -1,7 +1,7 @@ /* -*- mode: C; c-file-style: "gnu" -*- */ /* dbus-gobject.c Exporting a GObject remotely * - * Copyright (C) 2003, 2004 Red Hat, Inc. + * Copyright (C) 2003, 2004, 2005 Red Hat, Inc. * * Licensed under the Academic Free License version 2.1 * @@ -26,6 +26,7 @@ #include <dbus/dbus-glib-lowlevel.h> #include "dbus-gtest.h" #include "dbus-gutils.h" +#include "dbus-gobject.h" #include "dbus-gvalue.h" #include <string.h> @@ -34,37 +35,10 @@ * @{ */ -static GStaticMutex info_hash_mutex = G_STATIC_MUTEX_INIT; +static GStaticRWLock info_hash_lock = G_STATIC_RW_LOCK_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; @@ -97,6 +71,164 @@ uscore_to_wincaps (const char *uscore) return g_string_free (str, FALSE); } +static const char * +string_table_next (const char *table) +{ + return (table + (strlen (table) + 1)); +} + +static const char * +string_table_lookup (const char *table, int index) +{ + const char *ret; + + ret = table; + + while (index--) + ret = string_table_next (ret); + + return ret; +} + +static const char * +get_method_data (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return object->data + method->data_offset; +} + +static char * +object_error_domain_prefix_from_object_info (const DBusGObjectInfo *info) +{ + /* FIXME */ + return NULL; +} + +static char * +object_error_code_from_object_info (const DBusGObjectInfo *info, GQuark domain, gint code) +{ + /* FIXME */ + return NULL; +} + +static const char * +method_interface_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return string_table_lookup (get_method_data (object, method), 0); +} + +static const char * +method_name_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return string_table_lookup (get_method_data (object, method), 1); +} + +static const char * +method_arg_info_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return string_table_lookup (get_method_data (object, method), 2); +} + +static const char * +arg_iterate (const char *data, const char **name, gboolean *in, + const char **type) +{ + *name = data; + + data = string_table_next (data); + switch (*data) + { + case 'I': + *in = TRUE; + break; + case 'O': + *in = FALSE; + break; + default: + g_warning ("invalid arg direction"); + break; + } + + data = string_table_next (data); + *type = data; + + return string_table_next (data); +} + +static char * +method_dir_signature_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method, + gboolean in) +{ + const char *arg; + GString *ret; + + arg = method_arg_info_from_object_info (object, method); + + ret = g_string_new (NULL); + + while (*arg) + { + const char *name; + gboolean arg_in; + const char *type; + + arg = arg_iterate (arg, &name, &arg_in, &type); + + if (arg_in == in) + g_string_append (ret, type); + } + + return g_string_free (ret, FALSE); +} + +static char * +method_input_signature_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return method_dir_signature_from_object_info (object, method, TRUE); +} + +static char * +method_output_signature_from_object_info (const DBusGObjectInfo *object, + const DBusGMethodInfo *method) +{ + return method_dir_signature_from_object_info (object, method, FALSE); +} + +GValueArray * +dbus_glib_marshal_dbus_message_to_gvalue_array (DBusMessage *message) +{ + GValueArray *ret; + DBusMessageIter iter; + int dtype; + + ret = g_value_array_new (6); /* 6 is a typical maximum for arguments */ + dbus_message_iter_init (message, &iter); + + while ((dtype = dbus_message_iter_get_arg_type (&iter)) != DBUS_TYPE_INVALID) + { + GValue value = { 0, }; + + if (!dbus_gvalue_demarshal (&iter, &value)) + { + g_warning ("Unable to convert arg type %d to GValue", dtype); + g_value_array_free (ret); + ret = NULL; + goto out; + } + g_value_array_append (ret, &value); + + dbus_message_iter_next (&iter); + } + + out: + return ret; +} + static void gobject_unregister_function (DBusConnection *connection, void *user_data) @@ -272,6 +404,115 @@ introspect_signals (GType type, GString *xml) g_string_append (xml, " </interface>\n"); } +typedef struct +{ + GString *xml; + const DBusGObjectInfo *object_info; +} DBusGlibWriteIterfaceData; + +static void +write_interface (gpointer key, gpointer val, gpointer user_data) +{ + const char *name; + GSList *methods; + GString *xml; + const DBusGObjectInfo *object_info; + DBusGlibWriteIterfaceData *data; + + name = key; + methods = val; + data = user_data; + xml = data->xml; + object_info = data->object_info; + + g_string_append_printf (xml, " <interface name=\"%s\">\n", name); + + /* FIXME: recurse to parent types ? */ + for (; methods; methods = methods->next) + { + DBusGMethodInfo *method; + method = methods->data; + const char *args; + + g_string_append_printf (xml, " <method name=\"%s\">\n", + method_name_from_object_info (object_info, method)); + + args = method_arg_info_from_object_info (object_info, method); + + while (*args) + { + const char *name; + gboolean arg_in; + const char *type; + + args = arg_iterate (args, &name, &arg_in, &type); + + /* FIXME - handle container types */ + g_string_append_printf (xml, " <arg name=\"%s\" type=\"%s\" direction=\"%s\"/>\n", + name, _dbus_gutils_type_to_string (type[0]), arg_in ? "in" : "out"); + + } + g_string_append (xml, " </method>\n"); + } + + g_string_append (xml, " </interface>\n"); +} + +static void +introspect_interfaces (GObject *object, GString *xml) +{ + GType classtype; + + g_static_rw_lock_reader_lock (&info_hash_lock); + + for (classtype = G_TYPE_FROM_INSTANCE (object); classtype != 0; classtype = g_type_parent (classtype)) + { + const DBusGObjectInfo *info; + DBusGlibWriteIterfaceData data; + + info = g_hash_table_lookup (info_hash, + g_type_class_peek (classtype)); + + if (info != NULL && info->format_version == 0) + { + int i; + GHashTable *interfaces; + + /* Gather a list of all interfaces, indexed into their methods */ + interfaces = g_hash_table_new (g_str_hash, g_str_equal); + for (i = 0; i < info->n_infos; i++) + { + const char *method_name; + const char *method_interface; + const char *method_args; + const DBusGMethodInfo *method; + GSList *methods; + + method = &(info->infos[i]); + + method_interface = method_interface_from_object_info (info, method); + method_name = method_name_from_object_info (info, method); + method_args = method_arg_info_from_object_info (info, method); + + if ((methods = g_hash_table_lookup (interfaces, method_interface)) == NULL) + methods = g_slist_prepend (NULL, (gpointer) method); + else + methods = g_slist_prepend (methods, (gpointer) method); + g_hash_table_insert (interfaces, (gpointer) method_interface, methods); + } + + memset (&data, 0, sizeof (data)); + data.xml = xml; + data.object_info = info; + g_hash_table_foreach (interfaces, write_interface, &data); + + g_hash_table_destroy (interfaces); + } + } + + g_static_rw_lock_reader_lock (&info_hash_lock); +} + static DBusHandlerResult handle_introspect (DBusConnection *connection, DBusMessage *message, @@ -316,6 +557,7 @@ handle_introspect (DBusConnection *connection, introspect_signals (G_OBJECT_TYPE (object), xml); introspect_properties (object, xml); + introspect_interfaces (object, xml); /* Append child nodes */ for (i = 0; children[i]; i++) @@ -392,7 +634,7 @@ get_object_property (DBusConnection *connection, GParamSpec *pspec) { GType value_type; - GValue value; + GValue value = {0, }; DBusMessage *ret; DBusMessageIter iter; @@ -420,12 +662,275 @@ get_object_property (DBusConnection *connection, return ret; } +static gboolean +lookup_object_and_method (GObject *object, + DBusMessage *message, + const DBusGObjectInfo **object_ret, + const DBusGMethodInfo **method_ret) +{ + GType classtype; + const char *interface; + const char *member; + const char *signature; + gboolean ret; + + interface = dbus_message_get_interface (message); + member = dbus_message_get_member (message); + signature = dbus_message_get_signature (message); + ret = FALSE; + + g_static_rw_lock_reader_lock (&info_hash_lock); + + if (!info_hash) + goto out; + + for (classtype = G_TYPE_FROM_INSTANCE (object); classtype != 0; classtype = g_type_parent (classtype)) + { + const DBusGObjectInfo *info; + + info = g_hash_table_lookup (info_hash, + g_type_class_peek (classtype)); + + *object_ret = info; + + if (info != NULL && info->format_version == 0) + { + int i; + for (i = 0; i < info->n_infos; i++) + { + const char *expected_member; + const char *expected_interface; + char *expected_signature; + const DBusGMethodInfo *method; + + method = &(info->infos[i]); + + /* Check method interface/name and input signature */ + expected_interface = method_interface_from_object_info (*object_ret, method); + expected_member = method_name_from_object_info (*object_ret, method); + expected_signature = method_input_signature_from_object_info (*object_ret, method); + if ((interface == NULL + || strcmp (expected_interface, interface) == 0) + && strcmp (expected_member, member) == 0 + && strcmp (expected_signature, signature) == 0) + { + g_free (expected_signature); + *method_ret = method; + ret = TRUE; + goto out; + } + g_free (expected_signature); + } + } + } + out: + g_static_rw_lock_reader_lock (&info_hash_lock); + return ret; +} + +static char * +gerror_domaincode_to_dbus_error_name (const DBusGObjectInfo *object_info, + GQuark domain, gint code) +{ + const char *domain_str; + const char *code_str; + GString *dbus_error_name; + + domain_str = object_error_domain_prefix_from_object_info (object_info); + code_str = object_error_code_from_object_info (object_info, domain, code); + + if (!domain_str || !code_str) + { + /* If we can't map it sensibly, make up an error name */ + char *domain_from_quark; + + dbus_error_name = g_string_new ("org.freedesktop.DBus.GLib.UnmappedError."); + + domain_from_quark = uscore_to_wincaps (g_quark_to_string (domain)); + g_string_append (dbus_error_name, domain_from_quark); + g_free (domain_from_quark); + + g_string_append_printf (dbus_error_name, ".Code%d", code); + } + else + { + dbus_error_name = g_string_new (domain_str); + g_string_append_c (dbus_error_name, '.'); + g_string_append (dbus_error_name, code_str); + } + + return g_string_free (dbus_error_name, FALSE); +} + +static DBusMessage * +gerror_to_dbus_error_message (const DBusGObjectInfo *object_info, + DBusMessage *message, + GError *error) +{ + DBusMessage *reply; + + if (!error) + { + char *error_msg; + + error_msg = g_strdup_printf ("Method invoked for %s returned FALSE but did not set error", dbus_message_get_member (message)); + reply = dbus_message_new_error (message, "org.freedesktop.DBus.GLib.ErrorError", error_msg); + g_free (error_msg); + } + else + { + char *error_name; + error_name = gerror_domaincode_to_dbus_error_name (object_info, error->domain, error->code); + reply = dbus_message_new_error (message, error_name, error->message); + g_free (error_name); + } + return reply; +} + +static DBusHandlerResult +invoke_object_method (GObject *object, + const DBusGObjectInfo *object_info, + const DBusGMethodInfo *method, + DBusConnection *connection, + DBusMessage *message) +{ + gboolean had_error; + GError *gerror; + GValueArray *value_array; + GValue object_value = {0,}; + GValue error_value = {0,}; + GValue return_value = {0,}; + GClosure closure; + char *out_signature; + int out_signature_len; + GArray *out_param_values; + int i; + DBusHandlerResult result; + DBusMessage *reply; + + gerror = NULL; + + /* This is evil. We do this to work around the fact that + * the generated glib marshallers check a flag in the closure object + * which we don't care about. We don't need/want to create + * a new closure for each invocation. + */ + memset (&closure, 0, sizeof (closure)); + + /* Convert method IN parameters to GValueArray */ + value_array = dbus_glib_marshal_dbus_message_to_gvalue_array (message); + + g_return_val_if_fail (value_array != NULL, DBUS_HANDLER_RESULT_NOT_YET_HANDLED); + + /* Prepend object as first argument */ + g_value_init (&object_value, G_TYPE_OBJECT); + g_value_set_object (&object_value, object); + g_value_array_prepend (value_array, &object_value); + + out_signature = method_output_signature_from_object_info (object_info, method); + out_signature_len = strlen (out_signature); + + /* Create an array to store the actual values of OUT + * parameters. Then, create a GValue boxed POINTER + * to each of those values, and append to the invocation, + * so the method can return the OUT parameters. + */ + out_param_values = g_array_new (FALSE, TRUE, sizeof (DBusBasicGValue)); + for (i = 0; i < out_signature_len; i++) + { + GValue value = {0, }; + DBusBasicGValue basic; + + memset (&basic, 0, sizeof (basic)); + + /* FIXME - broken for container types */ + + g_array_append_val (out_param_values, basic); + g_value_init (&value, G_TYPE_POINTER); + g_value_set_pointer (&value, &(g_array_index (out_param_values, DBusBasicGValue, i))); + g_value_array_append (value_array, &value); + } + + /* Append GError as final argument */ + g_value_init (&error_value, G_TYPE_POINTER); + g_value_set_pointer (&error_value, &gerror); + g_value_array_append (value_array, &error_value); + + /* Actually invoke method */ + g_value_init (&return_value, G_TYPE_BOOLEAN); + method->marshaller (&closure, &return_value, + value_array->n_values, + value_array->values, + NULL, method->function); + had_error = !g_value_get_boolean (&return_value); + + if (!had_error) + { + DBusMessageIter iter; + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto nomem; + + /* Append OUT arguments to reply */ + dbus_message_iter_init_append (reply, &iter); + for (i = 0; i < out_signature_len; i++) + { + DBusBasicGValue *value; + + /* FIXME - broken for container types */ + + value = &(g_array_index (out_param_values, DBusBasicGValue, i)); + if (!dbus_message_iter_append_basic (&iter, out_signature[i], value)) + goto nomem; + + } + } + else + reply = gerror_to_dbus_error_message (object_info, message, gerror); + + if (reply) + { + dbus_connection_send (connection, reply, NULL); + dbus_message_unref (reply); + } + + /* Assume that if there was an error, no return values are + * set */ + if (!had_error) + { + /* Be sure to free all returned STRING arguments for now; + * later this should be specified via method info parameter + * annotation; probably we want to support custom free funcs too */ + for (i = 0; i < out_signature_len; i++) + { + DBusBasicGValue *value; + + value = &(g_array_index (out_param_values, DBusBasicGValue, i)); + if (out_signature[i] == DBUS_TYPE_STRING) + g_free (value->gpointer_val); + } + } + + result = DBUS_HANDLER_RESULT_HANDLED; + done: + g_free (out_signature); + g_array_free (out_param_values, TRUE); + g_value_array_free (value_array); + g_value_unset (&object_value); + g_value_unset (&error_value); + g_value_unset (&return_value); + return result; + nomem: + result = DBUS_HANDLER_RESULT_NEED_MEMORY; + goto done; +} + static DBusHandlerResult gobject_message_function (DBusConnection *connection, DBusMessage *message, void *user_data) { - const DBusGObjectInfo *info; GParamSpec *pspec; GObject *object; gboolean setter; @@ -434,6 +939,8 @@ gobject_message_function (DBusConnection *connection, const char *wincaps_propname; /* const char *wincaps_propiface; */ DBusMessageIter iter; + const DBusGMethodInfo *method; + const DBusGObjectInfo *object_info; object = G_OBJECT (user_data); @@ -441,23 +948,10 @@ gobject_message_function (DBusConnection *connection, 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 (lookup_object_and_method (object, message, &object_info, &method)) + return invoke_object_method (object, object_info, method, connection, message); /* If no metainfo, we can still do properties and signals * via standard GLib introspection @@ -497,7 +991,7 @@ gobject_message_function (DBusConnection *connection, dbus_message_iter_get_basic (&iter, &wincaps_propname); dbus_message_iter_next (&iter); - s = wincaps_to_uscore (wincaps_propname); + s = _dbus_gutils_wincaps_to_uscore (wincaps_propname); pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), s); @@ -577,7 +1071,7 @@ dbus_g_object_class_install_info (GObjectClass *object_class, { g_return_if_fail (G_IS_OBJECT_CLASS (object_class)); - g_static_mutex_lock (&info_hash_mutex); + g_static_rw_lock_writer_lock (&info_hash_lock); if (info_hash == NULL) { @@ -586,7 +1080,7 @@ dbus_g_object_class_install_info (GObjectClass *object_class, g_hash_table_replace (info_hash, object_class, (void*) info); - g_static_mutex_unlock (&info_hash_mutex); + g_static_rw_lock_writer_unlock (&info_hash_lock); } /** @@ -652,7 +1146,7 @@ _dbus_gobject_test (const char *test_data_dir) char *uscore; char *wincaps; - uscore = wincaps_to_uscore (name_pairs[i].wincaps); + uscore = _dbus_gutils_wincaps_to_uscore (name_pairs[i].wincaps); wincaps = uscore_to_wincaps (name_pairs[i].uscore); if (strcmp (uscore, name_pairs[i].uscore) != 0) |