diff --git a/gio/gdbusconnection.c b/gio/gdbusconnection.c index a745be48f..8ea5c4385 100644 --- a/gio/gdbusconnection.c +++ b/gio/gdbusconnection.c @@ -4272,17 +4272,8 @@ validate_and_maybe_schedule_property_getset (GDBusConnection *connect &property_name, NULL); - - if (is_get) - { - if (vtable == NULL || vtable->get_property == NULL) - goto out; - } - else - { - if (vtable == NULL || vtable->set_property == NULL) - goto out; - } + if (vtable == NULL) + goto out; /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs */ @@ -4350,6 +4341,32 @@ validate_and_maybe_schedule_property_getset (GDBusConnection *connect g_variant_unref (value); } + /* If the vtable pointer for get_property() resp. set_property() is + * NULL then dispatch the call via the method_call() handler. + */ + if (is_get) + { + if (vtable->get_property == NULL) + { + schedule_method_call (connection, message, registration_id, subtree_registration_id, + interface_info, NULL, property_info, g_dbus_message_get_body (message), + vtable, main_context, user_data); + handled = TRUE; + goto out; + } + } + else + { + if (vtable->set_property == NULL) + { + schedule_method_call (connection, message, registration_id, subtree_registration_id, + interface_info, NULL, property_info, g_dbus_message_get_body (message), + vtable, main_context, user_data); + handled = TRUE; + goto out; + } + } + /* ok, got the property info - call user code in an idle handler */ property_data = g_new0 (PropertyData, 1); property_data->connection = g_object_ref (connection); @@ -4538,9 +4555,21 @@ validate_and_maybe_schedule_property_get_all (GDBusConnection *connec handled = FALSE; - if (vtable == NULL || vtable->get_property == NULL) + if (vtable == NULL) goto out; + /* If the vtable pointer for get_property() is NULL, then dispatch the + * call via the method_call() handler. + */ + if (vtable->get_property == NULL) + { + schedule_method_call (connection, message, registration_id, subtree_registration_id, + interface_info, NULL, NULL, g_dbus_message_get_body (message), + vtable, main_context, user_data); + handled = TRUE; + goto out; + } + /* ok, got the property info - call user in an idle handler */ property_get_all_data = g_new0 (PropertyGetAllData, 1); property_get_all_data->connection = g_object_ref (connection); diff --git a/gio/gdbusconnection.h b/gio/gdbusconnection.h index 1325e3714..02caedc98 100644 --- a/gio/gdbusconnection.h +++ b/gio/gdbusconnection.h @@ -340,9 +340,38 @@ typedef gboolean (*GDBusInterfaceSetPropertyFunc) (GDBusConnection *conne * Virtual table for handling properties and method calls for a D-Bus * interface. * - * If you want to handle getting/setting D-Bus properties asynchronously, simply - * register an object with the org.freedesktop.DBus.Properties - * D-Bus interface using g_dbus_connection_register_object(). + * Since 2.38, if you want to handle getting/setting D-Bus properties + * asynchronously, give %NULL as your get_property() or set_property() + * function. The D-Bus call will be directed to your @method_call + * function, with the provided @interface_name set to + * "org.freedesktop.DBus.Properties". + * + * The usual checks on the validity of the calls is performed. For + * 'Get' calls, an error is automatically returned if + * the property does not exist or the permissions do not allow access. + * The same checks are performed for 'Set' calls, and + * the provided value is also checked for being the correct type. + * + * For both 'Get' and 'Set' calls, + * the #GDBusMethodInvocation passed to the method_call handler can be + * queried with g_dbus_method_invocation_get_property_info() to get a + * pointer to the #GDBusPropertyInfo of the property. + * + * If you have readable properties specified in your interface info, you + * must ensure that you either provide a non-%NULL @get_property() + * function or provide implementations of both the + * 'Get' and 'GetAll' methods on + * the 'org.freedesktop.DBus.Properties' interface in + * your @method_call function. Note that the required return type of + * the 'Get' call is (v), not the + * type of the property. 'GetAll' expects a return + * value of type a{sv}. + * + * If you have writable properties specified in your interface info, you + * must ensure that you either provide a non-%NULL @set_property() + * function or provide an implementation of the 'Set' + * call. If implementing the call, you must return the value of type + * %G_VARIANT_TYPE_UNIT. * * Since: 2.26 */ diff --git a/gio/gdbusmethodinvocation.c b/gio/gdbusmethodinvocation.c index 6ff05564b..ad19d9d09 100644 --- a/gio/gdbusmethodinvocation.c +++ b/gio/gdbusmethodinvocation.c @@ -166,6 +166,11 @@ g_dbus_method_invocation_get_object_path (GDBusMethodInvocation *invocation) * * Gets the name of the D-Bus interface the method was invoked on. * + * If this method call is a property Get, Set or GetAll call that has + * been redirected to the method call handler then + * "org.freedesktop.DBus.Properties" will be returned. See + * #GDBusInterfaceVTable for more information. + * * Returns: A string. Do not free, it is owned by @invocation. * * Since: 2.26 @@ -183,6 +188,11 @@ g_dbus_method_invocation_get_interface_name (GDBusMethodInvocation *invocation) * * Gets information about the method call, if any. * + * If this method invocation is a property Get, Set or GetAll call that + * has been redirected to the method call handler then %NULL will be + * returned. See g_dbus_method_invocation_get_property_info() and + * #GDBusInterfaceVTable for more information. + * * Returns: A #GDBusMethodInfo or %NULL. Do not free, it is owned by @invocation. * * Since: 2.26 @@ -201,6 +211,15 @@ g_dbus_method_invocation_get_method_info (GDBusMethodInvocation *invocation) * Gets information about the property that this method call is for, if * any. * + * This will only be set in the case of an invocation in response to a + * property Get or Set call that has been directed to the method call + * handler for an object on account of its property_get() or + * property_set() vtable pointers being unset. + * + * See #GDBusInterfaceVTable for more information. + * + * If the call was GetAll, %NULL will be returned. + * * Returns: (transfer none): a #GDBusPropertyInfo or %NULL * * Since: 2.38 diff --git a/gio/tests/gdbus-export.c b/gio/tests/gdbus-export.c index f4ce6cebc..03e6c6cb9 100644 --- a/gio/tests/gdbus-export.c +++ b/gio/tests/gdbus-export.c @@ -1524,6 +1524,181 @@ test_registered_interfaces (void) } +/* ---------------------------------------------------------------------------------------------------- */ + +static void +test_async_method_call (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + const GDBusPropertyInfo *property; + + /* Strictly speaking, this function should also expect to receive + * method calls not on the org.freedesktop.DBus.Properties interface, + * but we don't do any during this testcase, so assert that. + */ + g_assert_cmpstr (interface_name, ==, "org.freedesktop.DBus.Properties"); + g_assert (g_dbus_method_invocation_get_method_info (invocation) == NULL); + + property = g_dbus_method_invocation_get_property_info (invocation); + + /* Do a whole lot of asserts to make sure that invalid calls are still + * getting properly rejected by GDBusConnection and that our + * environment is as we expect it to be. + */ + if (g_str_equal (method_name, "Get")) + { + const gchar *iface_name, *prop_name; + + g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + g_assert (property != NULL); + g_assert_cmpstr (prop_name, ==, property->name); + g_assert (property->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_string (prop_name))); + } + + else if (g_str_equal (method_name, "Set")) + { + const gchar *iface_name, *prop_name; + GVariant *value; + + g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + g_assert (property != NULL); + g_assert_cmpstr (prop_name, ==, property->name); + g_assert (property->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE); + g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE (property->signature))); + g_dbus_method_invocation_return_value (invocation, g_variant_new ("()")); + g_variant_unref (value); + } + + else if (g_str_equal (method_name, "GetAll")) + { + const gchar *iface_name; + + g_variant_get (parameters, "(&s)", &iface_name); + g_assert_cmpstr (iface_name, ==, "org.example.Foo"); + g_assert (property == NULL); + g_dbus_method_invocation_return_value (invocation, + g_variant_new_parsed ("({ 'PropertyUno': < 'uno' >," + " 'NotWritable': < 'notwrite' > },)")); + } + + else + g_assert_not_reached (); +} + +static gint outstanding_cases; + +static void +ensure_result_cb (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (source); + GVariant *reply; + + reply = g_dbus_connection_call_finish (connection, result, NULL); + + if (user_data == NULL) + { + /* Expected an error */ + g_assert (reply == NULL); + } + else + { + /* Expected a reply of a particular format. */ + gchar *str; + + g_assert (reply != NULL); + str = g_variant_print (reply, TRUE); + g_assert_cmpstr (str, ==, (const gchar *) user_data); + g_free (str); + + g_variant_unref (reply); + } + + g_assert_cmpint (outstanding_cases, >, 0); + outstanding_cases--; +} + +static void +test_async_case (GDBusConnection *connection, + const gchar *expected_reply, + const gchar *method, + const gchar *format_string, + ...) +{ + va_list ap; + + va_start (ap, format_string); + + g_dbus_connection_call (connection, g_dbus_connection_get_unique_name (connection), "/foo", + "org.freedesktop.DBus.Properties", method, g_variant_new_va (format_string, NULL, &ap), + NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, ensure_result_cb, (gpointer) expected_reply); + + va_end (ap); + + outstanding_cases++; +} + +static void +test_async_properties (void) +{ + GError *error = NULL; + guint registration_id; + static const GDBusInterfaceVTable vtable = { + test_async_method_call, NULL, NULL + }; + + c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert (c != NULL); + + registration_id = g_dbus_connection_register_object (c, + "/foo", + (GDBusInterfaceInfo *) &foo_interface_info, + &vtable, NULL, NULL, &error); + g_assert_no_error (error); + g_assert (registration_id); + + test_async_case (c, NULL, "random", "()"); + + /* Test a variety of error cases */ + test_async_case (c, NULL, "Get", "(si)", "wrong signature", 5); + test_async_case (c, NULL, "Get", "(ss)", "org.example.WrongInterface", "zzz"); + test_async_case (c, NULL, "Get", "(ss)", "org.example.Foo", "NoSuchProperty"); + test_async_case (c, NULL, "Get", "(ss)", "org.example.Foo", "NotReadable"); + + test_async_case (c, NULL, "Set", "(si)", "wrong signature", 5); + test_async_case (c, NULL, "Set", "(ssv)", "org.example.WrongInterface", "zzz", g_variant_new_string ("")); + test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "NoSuchProperty", g_variant_new_string ("")); + test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "NotWritable", g_variant_new_string ("")); + test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "PropertyUno", g_variant_new_object_path ("/wrong")); + + test_async_case (c, NULL, "GetAll", "(si)", "wrong signature", 5); + test_async_case (c, NULL, "GetAll", "(s)", "org.example.WrongInterface"); + + /* Now do the proper things */ + test_async_case (c, "(<'PropertyUno'>,)", "Get", "(ss)", "org.example.Foo", "PropertyUno"); + test_async_case (c, "(<'NotWritable'>,)", "Get", "(ss)", "org.example.Foo", "NotWritable"); + test_async_case (c, "()", "Set", "(ssv)", "org.example.Foo", "PropertyUno", g_variant_new_string ("")); + test_async_case (c, "()", "Set", "(ssv)", "org.example.Foo", "NotReadable", g_variant_new_string ("")); + test_async_case (c, "({'PropertyUno': <'uno'>, 'NotWritable': <'notwrite'>},)", "GetAll", "(s)", "org.example.Foo"); + + while (outstanding_cases) + g_main_context_iteration (NULL, TRUE); + + g_dbus_connection_unregister_object (c, registration_id); + g_object_unref (c); +} + /* ---------------------------------------------------------------------------------------------------- */ int @@ -1541,6 +1716,7 @@ main (int argc, g_test_add_func ("/gdbus/object-registration", test_object_registration); g_test_add_func ("/gdbus/registered-interfaces", test_registered_interfaces); + g_test_add_func ("/gdbus/async-properties", test_async_properties); /* TODO: check that we spit out correct introspection data */ /* TODO: check that registering a whole subtree works */