From 141e338e3d11c64fba76741d9bdfe69a4b9ebc58 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Mon, 3 Nov 2025 20:35:51 +0100 Subject: [PATCH 01/10] ci: Drop the fedora based test stage in favor of the GNOME OS tests --- .gitlab-ci.yml | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 223bd93..160103c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,30 +9,4 @@ include: - component: "gitlab.gnome.org/GNOME/citemplates/release-service@master" inputs: dist-job-name: "build-gnomeos" - tarball-artifact-path: "_builddir/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz" - -test: - image: 'registry.fedoraproject.org/fedora:41' - stage: test - variables: - DEPS: >- - gcc - gcc-c++ - gettext - libatomic - liburing-devel - meson - ninja-build - redhat-rpm-config - glib2-devel - gobject-introspection-devel - git - before_script: - - 'cat /proc/cpuinfo' - - "dnf install -y $DEPS" - script: - - 'meson setup _build . -Ddocs=false -Dexamples=false -Dvapi=false -Dintrospection=enabled -Dsysprof=false -Dtests=true -Dliburing=enabled -Deventfd=enabled' - - 'cd _build' - - 'ninja test' - - 'meson configure -Dliburing=disabled -Deventfd=disabled' - - 'ninja test' + tarball-artifact-path: "_builddir/meson-dist/$CI_PROJECT_NAME-$CI_COMMIT_TAG.tar.xz" \ No newline at end of file -- GitLab From ac49429effc5ff41fd2be0205173128601f87728 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Tue, 16 Sep 2025 23:27:27 +0200 Subject: [PATCH 02/10] gdbus: split out the dbus functionality from gio --- src/dex-gdbus.c | 502 ++++++++++++++++++++++++++++++++++++++++++++++++ src/dex-gdbus.h | 74 +++++++ src/dex-gio.c | 462 -------------------------------------------- src/dex-gio.h | 44 ----- src/libdex.h | 1 + src/meson.build | 2 + 6 files changed, 579 insertions(+), 506 deletions(-) create mode 100644 src/dex-gdbus.c create mode 100644 src/dex-gdbus.h diff --git a/src/dex-gdbus.c b/src/dex-gdbus.c new file mode 100644 index 0000000..8a406be --- /dev/null +++ b/src/dex-gdbus.c @@ -0,0 +1,502 @@ +/* + * dex-gdbus.c + * + * Copyright 2025 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "dex-async-pair-private.h" +#include "dex-future-private.h" +#include "dex-future-set.h" +#include "dex-promise.h" + +#include "dex-gdbus.h" + +static inline DexAsyncPair * +create_async_pair (const char *name) +{ + DexAsyncPair *async_pair; + + async_pair = (DexAsyncPair *)dex_object_create_instance (DEX_TYPE_ASYNC_PAIR); + dex_future_set_static_name (DEX_FUTURE (async_pair), name); + + return async_pair; +} + +static void +dex_dbus_connection_send_message_with_reply_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + DexAsyncPair *async_pair = user_data; + GDBusMessage *message = NULL; + GError *error = NULL; + + message = g_dbus_connection_send_message_with_reply_finish (G_DBUS_CONNECTION (object), result, &error); + + if (error == NULL) + dex_async_pair_return_object (async_pair, message); + else + dex_async_pair_return_error (async_pair, error); + + dex_unref (async_pair); +} + +/** + * dex_dbus_connection_send_message_with_reply: + * @connection: a [class@Gio.DBusConnection] + * @message: a [class@Gio.DBusMessage] + * @flags: a set of [flags@Gio.DBusSendMessageFlags] + * @timeout_msec: timeout in milliseconds, or -1 for default, or %G_MAXINT + * for no timeout. + * @out_serial: (out) (optional): a location for the message serial number + * + * Wrapper for [method@Gio.DBusConnection.send_message_with_reply]. + * + * Returns: (transfer full): a [class@Dex.Future] that will resolve to a + * [class@Gio.DBusMessage] or reject with failure. + * + * Since: 0.4 + */ +DexFuture * +dex_dbus_connection_send_message_with_reply (GDBusConnection *connection, + GDBusMessage *message, + GDBusSendMessageFlags flags, + int timeout_msec, + guint32 *out_serial) +{ + DexAsyncPair *async_pair; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); + + async_pair = create_async_pair (G_STRFUNC); + + g_dbus_connection_send_message_with_reply (connection, + message, + flags, + timeout_msec, + out_serial, + async_pair->cancellable, + dex_dbus_connection_send_message_with_reply_cb, + dex_ref (async_pair)); + + return DEX_FUTURE (async_pair); +} + +static void +dex_dbus_connection_call_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + DexAsyncPair *async_pair = user_data; + GVariant *reply = NULL; + GError *error = NULL; + + reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); + + if (error == NULL) + dex_async_pair_return_variant (async_pair, reply); + else + dex_async_pair_return_error (async_pair, error); + + dex_unref (async_pair); +} + +/** + * dex_dbus_connection_call: + * @connection: a [class@Gio.DBusConnection] + * @bus_name: (nullable): a unique or well-known bus name or %NULL if + * @connection is not a message bus connection + * @object_path: path of remote object + * @interface_name: D-Bus interface to invoke method on + * @method_name: the name of the method to invoke + * @parameters: (nullable): a [struct@GLib.Variant] tuple with parameters for + * the method or %NULL if not passing parameters + * @reply_type: (nullable): the expected type of the reply (which will be a + * tuple), or %NULL + * @flags: flags from the [flags@Gio.DBusCallFlags] enumeration + * @timeout_msec: the timeout in milliseconds, -1 to use the default + * timeout or %G_MAXINT for no timeout + * + * Wrapper for [method@Gio.DBusConnection.call]. + * + * Returns: (transfer full): a [class@Dex.Future] that resolves to a + * [struct@GLib.Variant] or rejects with error. + * + * Since: 0.4 + */ +DexFuture * +dex_dbus_connection_call (GDBusConnection *connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + int timeout_msec) +{ + DexAsyncPair *async_pair; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + + async_pair = create_async_pair (G_STRFUNC); + + g_dbus_connection_call (connection, + bus_name, + object_path, + interface_name, + method_name, + parameters, + reply_type, + flags, + timeout_msec, + async_pair->cancellable, + dex_dbus_connection_call_cb, + dex_ref (async_pair)); + + return DEX_FUTURE (async_pair); +} + +#ifdef G_OS_UNIX +static void +dex_dbus_connection_call_with_unix_fd_list_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + DexFutureSet *future_set = user_data; + DexAsyncPair *async_pair; + DexPromise *promise; + GUnixFDList *fd_list = NULL; + GVariant *reply = NULL; + GError *error = NULL; + + g_assert (G_IS_DBUS_CONNECTION (object)); + g_assert (DEX_IS_FUTURE_SET (future_set)); + + async_pair = DEX_ASYNC_PAIR (dex_future_set_get_future_at (future_set, 0)); + promise = DEX_PROMISE (dex_future_set_get_future_at (future_set, 1)); + + g_assert (DEX_IS_ASYNC_PAIR (async_pair)); + g_assert (DEX_IS_PROMISE (promise)); + + reply = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), &fd_list, result, &error); + + g_assert (!fd_list || G_IS_UNIX_FD_LIST (fd_list)); + g_assert (reply != NULL || error != NULL); + + if (error == NULL) + { + dex_promise_resolve_object (promise, fd_list); + dex_async_pair_return_variant (async_pair, reply); + } + else + { + dex_promise_reject (promise, g_error_copy (error)); + dex_async_pair_return_error (async_pair, error); + } + + dex_unref (future_set); +} + +/** + * dex_dbus_connection_call_with_unix_fd_list: + * @connection: a [class@Gio.DBusConnection] + * @bus_name: (nullable): a unique or well-known bus name or %NULL if + * @connection is not a message bus connection + * @object_path: path of remote object + * @interface_name: D-Bus interface to invoke method on + * @method_name: the name of the method to invoke + * @parameters: (nullable): a [struct@GLib.Variant] tuple with parameters for + * the method or %NULL if not passing parameters + * @reply_type: (nullable): the expected type of the reply (which will be a + * tuple), or %NULL + * @flags: flags from the [flags@Gio.DBusCallFlags] enumeration + * @timeout_msec: the timeout in milliseconds, -1 to use the default + * timeout or %G_MAXINT for no timeout + * @fd_list: (nullable): a [class@Gio.UnixFDList] + * + * Wrapper for [method@Gio.DBusConnection.call_with_unix_fd_list]. + * + * Returns: (transfer full): a [class@Dex.FutureSet] that resolves to a + * [struct@GLib.Variant]. + * + * The [class@Dex.Future] containing the resulting [class@Gio.UnixFDList] can + * be retrieved with [method@Dex.FutureSet.get_future_at] with an index of 1. + * + * Since: 0.4 + */ +DexFuture * +dex_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + int timeout_msec, + GUnixFDList *fd_list) +{ + DexAsyncPair *async_pair; + DexPromise *promise; + DexFuture *ret; + + g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); + g_return_val_if_fail (!fd_list || G_IS_UNIX_FD_LIST (fd_list), NULL); + + /* Will hold our GVariant result */ + async_pair = create_async_pair (G_STRFUNC); + + /* Will hold our GUnixFDList result */ + promise = dex_promise_new (); + + /* Sent to user. Resolving will contain variant. */ + ret = dex_future_all (DEX_FUTURE (async_pair), DEX_FUTURE (promise), NULL); + + g_dbus_connection_call_with_unix_fd_list (connection, + bus_name, + object_path, + interface_name, + method_name, + parameters, + reply_type, + flags, + timeout_msec, + fd_list, + async_pair->cancellable, + dex_dbus_connection_call_with_unix_fd_list_cb, + dex_ref (ret)); + + return ret; +} +#endif + +static void +dex_bus_get_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + DexAsyncPair *async_pair = user_data; + GDBusConnection *bus; + GError *error = NULL; + + bus = g_bus_get_finish (result, &error); + + if (error == NULL) + dex_async_pair_return_object (async_pair, bus); + else + dex_async_pair_return_error (async_pair, error); + + dex_unref (async_pair); +} + +/** + * dex_bus_get: + * @bus_type: the [enum@Gio.BusType] + * + * Wrapper for [func@Gio.bus_get]. + * + * Returns: (transfer full): a [class@Dex.Future] that resolves to a + * [class@Gio.DBusConnection] or rejects with error. + * + * Since: 0.4 + */ +DexFuture * +dex_bus_get (GBusType bus_type) +{ + DexAsyncPair *async_pair; + + async_pair = create_async_pair (G_STRFUNC); + + g_bus_get (bus_type, + async_pair->cancellable, + dex_bus_get_cb, + dex_ref (async_pair)); + + return DEX_FUTURE (async_pair); +} + +typedef struct +{ + DexPromise *name_acquired; + gulong acquired_cancelled_id; + DexPromise *name_lost; + gulong lost_cancelled_id; + guint own_name_id; +} BusOwnNameData; + +static void +dex_bus_own_name_data_free (BusOwnNameData *data) +{ + + g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_acquired), + data->acquired_cancelled_id); + g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_lost), + data->lost_cancelled_id); + + if (dex_future_is_pending (DEX_FUTURE (data->name_acquired))) + { + dex_promise_reject (data->name_acquired, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to acquire dbus name")); + } + + if (dex_future_is_pending (DEX_FUTURE (data->name_lost))) + { + dex_promise_reject (data->name_lost, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + "Lost dbus name")); + } + + g_clear_pointer (&data->name_acquired, dex_unref); + g_clear_pointer (&data->name_lost, dex_unref); + free (data); +} + +static void +dex_bus_name_acquired_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + BusOwnNameData *data = user_data; + + dex_promise_resolve_boolean (data->name_acquired, TRUE); +} + +static void +dex_bus_name_lost_cb (GDBusConnection *connection, + const char *name, + gpointer user_data) +{ + BusOwnNameData *data = user_data; + + g_clear_handle_id (&data->own_name_id, g_bus_unown_name); +} + +static void +dex_bus_name_cancelled_cb (GCancellable *cancellable, + gpointer user_data) +{ + BusOwnNameData *data = user_data; + + g_clear_handle_id (&data->own_name_id, g_bus_unown_name); +} + +/** + * dex_bus_own_name_on_connection: + * @connection: The [class@Gio.DBusConnection] to own a name on. + * @name: The well-known name to own. + * @flags: a set of flags with ownership options. + * @out_name_acquired_future: (out) (optional): a location for the name acquired future + * @out_name_lost_future: (out) (optional): a location for the name lost future + * + * Wrapper for [func@Gio.bus_own_name]. + * + * Asks the D-Bus broker to own the well-known name @name on the connection @connection. + * + * @out_name_acquired_future is a future that awaits owning the name and either + * resolves to true, or rejects with an error. + * + * @out_name_lost_future is a future that rejects when the name was lost. + * + * If either future is canceled, the name will be unowned. + * + * Since: 1.1 + */ +void +dex_bus_own_name_on_connection (GDBusConnection *connection, + const char *name, + GBusNameOwnerFlags flags, + DexFuture **out_name_acquired_future, + DexFuture **out_name_lost_future) +{ + BusOwnNameData *data = g_new0 (BusOwnNameData, 1); + + data->name_acquired = dex_promise_new_cancellable (); + data->name_lost = dex_promise_new_cancellable (); + + data->acquired_cancelled_id = + g_signal_connect_swapped (dex_promise_get_cancellable (data->name_acquired), + "cancelled", + G_CALLBACK (dex_bus_name_cancelled_cb), + data); + + data->lost_cancelled_id = + g_signal_connect_swapped (dex_promise_get_cancellable (data->name_lost), + "cancelled", + G_CALLBACK (dex_bus_name_cancelled_cb), + data); + data->own_name_id = + g_bus_own_name_on_connection (connection, + name, + flags, + dex_bus_name_acquired_cb, + dex_bus_name_lost_cb, + data, + (GDestroyNotify) dex_bus_own_name_data_free); + + if (out_name_acquired_future) + *out_name_acquired_future = DEX_FUTURE (dex_ref (data->name_acquired)); + if (out_name_lost_future) + *out_name_lost_future = DEX_FUTURE (dex_ref (data->name_lost)); +} + +static void +dex_dbus_connection_close_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + DexPromise *promise = user_data; + GError *error = NULL; + + if (!g_dbus_connection_close_finish (G_DBUS_CONNECTION (object), result, &error)) + dex_promise_reject (promise, g_steal_pointer (&error)); + else + dex_promise_resolve_boolean (promise, TRUE); + + dex_unref (promise); +} + +/** + * dex_dbus_connection_close: + * @connection: a [class@Gio.DBusConnection] + * + * Asynchronously closes a connection. + * + * Returns: (transfer full): a [class@Dex.Future] that resolves + * to `true` or rejects with error. + * + * Since: 1.0 + */ +DexFuture * +dex_dbus_connection_close (GDBusConnection *connection) +{ + DexPromise *promise; + + dex_return_error_if_fail (G_IS_DBUS_CONNECTION (connection)); + + promise = dex_promise_new_cancellable (); + g_dbus_connection_close (connection, + dex_promise_get_cancellable (promise), + dex_dbus_connection_close_cb, + dex_ref (promise)); + return DEX_FUTURE (promise); +} diff --git a/src/dex-gdbus.h b/src/dex-gdbus.h new file mode 100644 index 0000000..dff27db --- /dev/null +++ b/src/dex-gdbus.h @@ -0,0 +1,74 @@ +/* + * dex-gdbus.h + * + * Copyright 2025 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include + +#ifdef G_OS_UNIX +# include +#endif + +#include "dex-future.h" + +G_BEGIN_DECLS + +DEX_AVAILABLE_IN_ALL +DexFuture *dex_bus_get (GBusType bus_type) G_GNUC_WARN_UNUSED_RESULT; +DEX_AVAILABLE_IN_1_1 +void dex_bus_own_name_on_connection (GDBusConnection *connection, + const char *name, + GBusNameOwnerFlags flags, + DexFuture **out_name_acquired_future, + DexFuture **out_name_lost_future); +DEX_AVAILABLE_IN_ALL +DexFuture *dex_dbus_connection_call (GDBusConnection *connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + int timeout_msec) G_GNUC_WARN_UNUSED_RESULT; +DEX_AVAILABLE_IN_ALL +DexFuture *dex_dbus_connection_close (GDBusConnection *connection) G_GNUC_WARN_UNUSED_RESULT; +DEX_AVAILABLE_IN_ALL +DexFuture *dex_dbus_connection_send_message_with_reply (GDBusConnection *connection, + GDBusMessage *message, + GDBusSendMessageFlags flags, + int timeout_msec, + guint32 *out_serial) G_GNUC_WARN_UNUSED_RESULT; +#ifdef G_OS_UNIX +DEX_AVAILABLE_IN_ALL +DexFuture *dex_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, + const char *bus_name, + const char *object_path, + const char *interface_name, + const char *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + int timeout_msec, + GUnixFDList *fd_list) G_GNUC_WARN_UNUSED_RESULT; +#endif + +G_END_DECLS diff --git a/src/dex-gio.c b/src/dex-gio.c index 82fd9a9..df215c9 100644 --- a/src/dex-gio.c +++ b/src/dex-gio.c @@ -1428,426 +1428,6 @@ dex_file_load_contents_bytes (GFile *file) return DEX_FUTURE (async_pair); } -static void -dex_dbus_connection_send_message_with_reply_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - DexAsyncPair *async_pair = user_data; - GDBusMessage *message = NULL; - GError *error = NULL; - - message = g_dbus_connection_send_message_with_reply_finish (G_DBUS_CONNECTION (object), result, &error); - - if (error == NULL) - dex_async_pair_return_object (async_pair, message); - else - dex_async_pair_return_error (async_pair, error); - - dex_unref (async_pair); -} - -/** - * dex_dbus_connection_send_message_with_reply: - * @connection: a [class@Gio.DBusConnection] - * @message: a [class@Gio.DBusMessage] - * @flags: a set of [flags@Gio.DBusSendMessageFlags] - * @timeout_msec: timeout in milliseconds, or -1 for default, or %G_MAXINT - * for no timeout. - * @out_serial: (out) (optional): a location for the message serial number - * - * Wrapper for [method@Gio.DBusConnection.send_message_with_reply]. - * - * Returns: (transfer full): a [class@Dex.Future] that will resolve to a - * [class@Gio.DBusMessage] or reject with failure. - * - * Since: 0.4 - */ -DexFuture * -dex_dbus_connection_send_message_with_reply (GDBusConnection *connection, - GDBusMessage *message, - GDBusSendMessageFlags flags, - int timeout_msec, - guint32 *out_serial) -{ - DexAsyncPair *async_pair; - - g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); - g_return_val_if_fail (G_IS_DBUS_MESSAGE (message), NULL); - - async_pair = create_async_pair (G_STRFUNC); - - g_dbus_connection_send_message_with_reply (connection, - message, - flags, - timeout_msec, - out_serial, - async_pair->cancellable, - dex_dbus_connection_send_message_with_reply_cb, - dex_ref (async_pair)); - - return DEX_FUTURE (async_pair); -} - -static void -dex_dbus_connection_call_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - DexAsyncPair *async_pair = user_data; - GVariant *reply = NULL; - GError *error = NULL; - - reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error); - - if (error == NULL) - dex_async_pair_return_variant (async_pair, reply); - else - dex_async_pair_return_error (async_pair, error); - - dex_unref (async_pair); -} - -/** - * dex_dbus_connection_call: - * @connection: a [class@Gio.DBusConnection] - * @bus_name: (nullable): a unique or well-known bus name or %NULL if - * @connection is not a message bus connection - * @object_path: path of remote object - * @interface_name: D-Bus interface to invoke method on - * @method_name: the name of the method to invoke - * @parameters: (nullable): a [struct@GLib.Variant] tuple with parameters for - * the method or %NULL if not passing parameters - * @reply_type: (nullable): the expected type of the reply (which will be a - * tuple), or %NULL - * @flags: flags from the [flags@Gio.DBusCallFlags] enumeration - * @timeout_msec: the timeout in milliseconds, -1 to use the default - * timeout or %G_MAXINT for no timeout - * - * Wrapper for [method@Gio.DBusConnection.call]. - * - * Returns: (transfer full): a [class@Dex.Future] that resolves to a - * [struct@GLib.Variant] or rejects with error. - * - * Since: 0.4 - */ -DexFuture * -dex_dbus_connection_call (GDBusConnection *connection, - const char *bus_name, - const char *object_path, - const char *interface_name, - const char *method_name, - GVariant *parameters, - const GVariantType *reply_type, - GDBusCallFlags flags, - int timeout_msec) -{ - DexAsyncPair *async_pair; - - g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); - - async_pair = create_async_pair (G_STRFUNC); - - g_dbus_connection_call (connection, - bus_name, - object_path, - interface_name, - method_name, - parameters, - reply_type, - flags, - timeout_msec, - async_pair->cancellable, - dex_dbus_connection_call_cb, - dex_ref (async_pair)); - - return DEX_FUTURE (async_pair); -} - -#ifdef G_OS_UNIX -static void -dex_dbus_connection_call_with_unix_fd_list_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - DexFutureSet *future_set = user_data; - DexAsyncPair *async_pair; - DexPromise *promise; - GUnixFDList *fd_list = NULL; - GVariant *reply = NULL; - GError *error = NULL; - - g_assert (G_IS_DBUS_CONNECTION (object)); - g_assert (DEX_IS_FUTURE_SET (future_set)); - - async_pair = DEX_ASYNC_PAIR (dex_future_set_get_future_at (future_set, 0)); - promise = DEX_PROMISE (dex_future_set_get_future_at (future_set, 1)); - - g_assert (DEX_IS_ASYNC_PAIR (async_pair)); - g_assert (DEX_IS_PROMISE (promise)); - - reply = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), &fd_list, result, &error); - - g_assert (!fd_list || G_IS_UNIX_FD_LIST (fd_list)); - g_assert (reply != NULL || error != NULL); - - if (error == NULL) - { - dex_promise_resolve_object (promise, fd_list); - dex_async_pair_return_variant (async_pair, reply); - } - else - { - dex_promise_reject (promise, g_error_copy (error)); - dex_async_pair_return_error (async_pair, error); - } - - dex_unref (future_set); -} - -/** - * dex_dbus_connection_call_with_unix_fd_list: - * @connection: a [class@Gio.DBusConnection] - * @bus_name: (nullable): a unique or well-known bus name or %NULL if - * @connection is not a message bus connection - * @object_path: path of remote object - * @interface_name: D-Bus interface to invoke method on - * @method_name: the name of the method to invoke - * @parameters: (nullable): a [struct@GLib.Variant] tuple with parameters for - * the method or %NULL if not passing parameters - * @reply_type: (nullable): the expected type of the reply (which will be a - * tuple), or %NULL - * @flags: flags from the [flags@Gio.DBusCallFlags] enumeration - * @timeout_msec: the timeout in milliseconds, -1 to use the default - * timeout or %G_MAXINT for no timeout - * @fd_list: (nullable): a [class@Gio.UnixFDList] - * - * Wrapper for [method@Gio.DBusConnection.call_with_unix_fd_list]. - * - * Returns: (transfer full): a [class@Dex.FutureSet] that resolves to a - * [struct@GLib.Variant]. - * - * The [class@Dex.Future] containing the resulting [class@Gio.UnixFDList] can - * be retrieved with [method@Dex.FutureSet.get_future_at] with an index of 1. - * - * Since: 0.4 - */ -DexFuture * -dex_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, - const char *bus_name, - const char *object_path, - const char *interface_name, - const char *method_name, - GVariant *parameters, - const GVariantType *reply_type, - GDBusCallFlags flags, - int timeout_msec, - GUnixFDList *fd_list) -{ - DexAsyncPair *async_pair; - DexPromise *promise; - DexFuture *ret; - - g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL); - g_return_val_if_fail (!fd_list || G_IS_UNIX_FD_LIST (fd_list), NULL); - - /* Will hold our GVariant result */ - async_pair = create_async_pair (G_STRFUNC); - - /* Will hold our GUnixFDList result */ - promise = dex_promise_new (); - - /* Sent to user. Resolving will contain variant. */ - ret = dex_future_all (DEX_FUTURE (async_pair), DEX_FUTURE (promise), NULL); - - g_dbus_connection_call_with_unix_fd_list (connection, - bus_name, - object_path, - interface_name, - method_name, - parameters, - reply_type, - flags, - timeout_msec, - fd_list, - async_pair->cancellable, - dex_dbus_connection_call_with_unix_fd_list_cb, - dex_ref (ret)); - - return ret; -} -#endif - -static void -dex_bus_get_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - DexAsyncPair *async_pair = user_data; - GDBusConnection *bus; - GError *error = NULL; - - bus = g_bus_get_finish (result, &error); - - if (error == NULL) - dex_async_pair_return_object (async_pair, bus); - else - dex_async_pair_return_error (async_pair, error); - - dex_unref (async_pair); -} - -/** - * dex_bus_get: - * @bus_type: the [enum@Gio.BusType] - * - * Wrapper for [func@Gio.bus_get]. - * - * Returns: (transfer full): a [class@Dex.Future] that resolves to a - * [class@Gio.DBusConnection] or rejects with error. - * - * Since: 0.4 - */ -DexFuture * -dex_bus_get (GBusType bus_type) -{ - DexAsyncPair *async_pair; - - async_pair = create_async_pair (G_STRFUNC); - - g_bus_get (bus_type, - async_pair->cancellable, - dex_bus_get_cb, - dex_ref (async_pair)); - - return DEX_FUTURE (async_pair); -} - -typedef struct -{ - DexPromise *name_acquired; - gulong acquired_cancelled_id; - DexPromise *name_lost; - gulong lost_cancelled_id; - guint own_name_id; -} BusOwnNameData; - -static void -dex_bus_own_name_data_free (BusOwnNameData *data) -{ - - g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_acquired), - data->acquired_cancelled_id); - g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_lost), - data->lost_cancelled_id); - - if (dex_future_is_pending (DEX_FUTURE (data->name_acquired))) - { - dex_promise_reject (data->name_acquired, - g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to acquire dbus name")); - } - - if (dex_future_is_pending (DEX_FUTURE (data->name_lost))) - { - dex_promise_reject (data->name_lost, - g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, - "Lost dbus name")); - } - - g_clear_pointer (&data->name_acquired, dex_unref); - g_clear_pointer (&data->name_lost, dex_unref); - free (data); -} - -static void -dex_bus_name_acquired_cb (GDBusConnection *connection, - const char *name, - gpointer user_data) -{ - BusOwnNameData *data = user_data; - - dex_promise_resolve_boolean (data->name_acquired, TRUE); -} - -static void -dex_bus_name_lost_cb (GDBusConnection *connection, - const char *name, - gpointer user_data) -{ - BusOwnNameData *data = user_data; - - g_clear_handle_id (&data->own_name_id, g_bus_unown_name); -} - -static void -dex_bus_name_cancelled_cb (GCancellable *cancellable, - gpointer user_data) -{ - BusOwnNameData *data = user_data; - - g_clear_handle_id (&data->own_name_id, g_bus_unown_name); -} - -/** - * dex_bus_own_name_on_connection: - * @connection: The [class@Gio.DBusConnection] to own a name on. - * @name: The well-known name to own. - * @flags: a set of flags with ownership options. - * @out_name_acquired_future: (out) (optional): a location for the name acquired future - * @out_name_lost_future: (out) (optional): a location for the name lost future - * - * Wrapper for [func@Gio.bus_own_name]. - * - * Asks the D-Bus broker to own the well-known name @name on the connection @connection. - * - * @out_name_acquired_future is a future that awaits owning the name and either - * resolves to true, or rejects with an error. - * - * @out_name_lost_future is a future that rejects when the name was lost. - * - * If either future is canceled, the name will be unowned. - * - * Since: 1.1 - */ -void -dex_bus_own_name_on_connection (GDBusConnection *connection, - const char *name, - GBusNameOwnerFlags flags, - DexFuture **out_name_acquired_future, - DexFuture **out_name_lost_future) -{ - BusOwnNameData *data = g_new0 (BusOwnNameData, 1); - - data->name_acquired = dex_promise_new_cancellable (); - data->name_lost = dex_promise_new_cancellable (); - - data->acquired_cancelled_id = - g_signal_connect_swapped (dex_promise_get_cancellable (data->name_acquired), - "cancelled", - G_CALLBACK (dex_bus_name_cancelled_cb), - data); - - data->lost_cancelled_id = - g_signal_connect_swapped (dex_promise_get_cancellable (data->name_lost), - "cancelled", - G_CALLBACK (dex_bus_name_cancelled_cb), - data); - data->own_name_id = - g_bus_own_name_on_connection (connection, - name, - flags, - dex_bus_name_acquired_cb, - dex_bus_name_lost_cb, - data, - (GDestroyNotify) dex_bus_own_name_data_free); - - if (out_name_acquired_future) - *out_name_acquired_future = DEX_FUTURE (dex_ref (data->name_acquired)); - if (out_name_lost_future) - *out_name_lost_future = DEX_FUTURE (dex_ref (data->name_lost)); -} - static void dex_subprocess_wait_check_cb (GObject *object, GAsyncResult *result, @@ -1989,48 +1569,6 @@ dex_async_initable_init (GAsyncInitable *initable, return DEX_FUTURE (promise); } -static void -dex_dbus_connection_close_cb (GObject *object, - GAsyncResult *result, - gpointer user_data) -{ - DexPromise *promise = user_data; - GError *error = NULL; - - if (!g_dbus_connection_close_finish (G_DBUS_CONNECTION (object), result, &error)) - dex_promise_reject (promise, g_steal_pointer (&error)); - else - dex_promise_resolve_boolean (promise, TRUE); - - dex_unref (promise); -} - -/** - * dex_dbus_connection_close: - * @connection: a [class@Gio.DBusConnection] - * - * Asynchronously closes a connection. - * - * Returns: (transfer full): a [class@Dex.Future] that resolves - * to `true` or rejects with error. - * - * Since: 1.0 - */ -DexFuture * -dex_dbus_connection_close (GDBusConnection *connection) -{ - DexPromise *promise; - - dex_return_error_if_fail (G_IS_DBUS_CONNECTION (connection)); - - promise = dex_promise_new_cancellable (); - g_dbus_connection_close (connection, - dex_promise_get_cancellable (promise), - dex_dbus_connection_close_cb, - dex_ref (promise)); - return DEX_FUTURE (promise); -} - static void dex_file_set_attributes_cb (GObject *object, GAsyncResult *result, diff --git a/src/dex-gio.h b/src/dex-gio.h index cd4dfb9..97adfc9 100644 --- a/src/dex-gio.h +++ b/src/dex-gio.h @@ -23,10 +23,6 @@ #include -#ifdef G_OS_UNIX -# include -#endif - #include "dex-future.h" G_BEGIN_DECLS @@ -143,32 +139,6 @@ DEX_AVAILABLE_IN_ALL DexFuture *dex_resolver_lookup_by_name (GResolver *resolver, const char *address) G_GNUC_WARN_UNUSED_RESULT; DEX_AVAILABLE_IN_ALL -DexFuture *dex_bus_get (GBusType bus_type) G_GNUC_WARN_UNUSED_RESULT; -DEX_AVAILABLE_IN_1_1 -void dex_bus_own_name_on_connection (GDBusConnection *connection, - const char *name, - GBusNameOwnerFlags flags, - DexFuture **out_name_acquired_future, - DexFuture **out_name_lost_future); -DEX_AVAILABLE_IN_ALL -DexFuture *dex_dbus_connection_call (GDBusConnection *connection, - const char *bus_name, - const char *object_path, - const char *interface_name, - const char *method_name, - GVariant *parameters, - const GVariantType *reply_type, - GDBusCallFlags flags, - int timeout_msec) G_GNUC_WARN_UNUSED_RESULT; -DEX_AVAILABLE_IN_ALL -DexFuture *dex_dbus_connection_close (GDBusConnection *connection) G_GNUC_WARN_UNUSED_RESULT; -DEX_AVAILABLE_IN_ALL -DexFuture *dex_dbus_connection_send_message_with_reply (GDBusConnection *connection, - GDBusMessage *message, - GDBusSendMessageFlags flags, - int timeout_msec, - guint32 *out_serial) G_GNUC_WARN_UNUSED_RESULT; -DEX_AVAILABLE_IN_ALL DexFuture *dex_subprocess_wait_check (GSubprocess *subprocess) G_GNUC_WARN_UNUSED_RESULT; DEX_AVAILABLE_IN_ALL DexFuture *dex_file_query_exists (GFile *file) G_GNUC_WARN_UNUSED_RESULT; @@ -194,18 +164,4 @@ DEX_AVAILABLE_IN_1_1 DexFuture *dex_fd_watch (int fd, int events) G_GNUC_WARN_UNUSED_RESULT; -#ifdef G_OS_UNIX -DEX_AVAILABLE_IN_ALL -DexFuture *dex_dbus_connection_call_with_unix_fd_list (GDBusConnection *connection, - const char *bus_name, - const char *object_path, - const char *interface_name, - const char *method_name, - GVariant *parameters, - const GVariantType *reply_type, - GDBusCallFlags flags, - int timeout_msec, - GUnixFDList *fd_list) G_GNUC_WARN_UNUSED_RESULT; -#endif - G_END_DECLS diff --git a/src/libdex.h b/src/libdex.h index afa20e5..51bd832 100644 --- a/src/libdex.h +++ b/src/libdex.h @@ -38,6 +38,7 @@ # include "dex-future.h" # include "dex-future-list-model.h" # include "dex-future-set.h" +# include "dex-gdbus.h" # include "dex-gio.h" # include "dex-init.h" # include "dex-main-scheduler.h" diff --git a/src/meson.build b/src/meson.build index 572212a..2ac169b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ libdex_sources = [ 'dex-future.c', 'dex-future-list-model.c', 'dex-future-set.c', + 'dex-gdbus.c', 'dex-gio.c', 'dex-init.c', 'dex-infinite.c', @@ -53,6 +54,7 @@ libdex_headers = [ 'dex-future.h', 'dex-future-list-model.h', 'dex-future-set.h', + 'dex-gdbus.h', 'dex-gio.h', 'dex-init.h', 'dex-main-scheduler.h', -- GitLab From 01795aae0b59b7981afa713ab9d1f7af00e70c9d Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Fri, 17 Oct 2025 18:49:42 +0200 Subject: [PATCH 03/10] gdbus: Fix signal connection for dex_bus_own_name_on_connection() Fixes: df6e310 ("gio: add dex_bus_own_name_on_connection()") --- src/dex-gdbus.c | 100 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 33 deletions(-) diff --git a/src/dex-gdbus.c b/src/dex-gdbus.c index 8a406be..34f32db 100644 --- a/src/dex-gdbus.c +++ b/src/dex-gdbus.c @@ -337,37 +337,18 @@ dex_bus_get (GBusType bus_type) typedef struct { DexPromise *name_acquired; - gulong acquired_cancelled_id; + gulong name_acquired_cancelled_id; DexPromise *name_lost; - gulong lost_cancelled_id; + gulong name_lost_cancelled_id; guint own_name_id; } BusOwnNameData; static void dex_bus_own_name_data_free (BusOwnNameData *data) { - - g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_acquired), - data->acquired_cancelled_id); - g_signal_handler_disconnect (dex_promise_get_cancellable (data->name_lost), - data->lost_cancelled_id); - - if (dex_future_is_pending (DEX_FUTURE (data->name_acquired))) - { - dex_promise_reject (data->name_acquired, - g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to acquire dbus name")); - } - - if (dex_future_is_pending (DEX_FUTURE (data->name_lost))) - { - dex_promise_reject (data->name_lost, - g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, - "Lost dbus name")); - } - g_clear_pointer (&data->name_acquired, dex_unref); g_clear_pointer (&data->name_lost, dex_unref); + free (data); } @@ -378,6 +359,10 @@ dex_bus_name_acquired_cb (GDBusConnection *connection, { BusOwnNameData *data = user_data; + g_cancellable_disconnect (dex_promise_get_cancellable (data->name_acquired), + data->name_acquired_cancelled_id); + data->name_acquired_cancelled_id = 0; + dex_promise_resolve_boolean (data->name_acquired, TRUE); } @@ -388,6 +373,25 @@ dex_bus_name_lost_cb (GDBusConnection *connection, { BusOwnNameData *data = user_data; + if (dex_future_is_pending (DEX_FUTURE (data->name_acquired))) + { + g_cancellable_disconnect (dex_promise_get_cancellable (data->name_acquired), + data->name_acquired_cancelled_id); + data->name_acquired_cancelled_id = 0; + + dex_promise_reject (data->name_acquired, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to acquire dbus name")); + } + + g_cancellable_disconnect (dex_promise_get_cancellable (data->name_lost), + data->name_lost_cancelled_id); + data->name_lost_cancelled_id = 0; + + dex_promise_reject (data->name_lost, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + "Lost dbus name")); + g_clear_handle_id (&data->own_name_id, g_bus_unown_name); } @@ -397,6 +401,37 @@ dex_bus_name_cancelled_cb (GCancellable *cancellable, { BusOwnNameData *data = user_data; + /* Disconnect the other cancellable. This way, neither will signal anymore, and the remaining one + * will get cleaned up when the respective future gets cleaned up. */ + if (dex_promise_get_cancellable (data->name_acquired) != cancellable && + data->name_acquired_cancelled_id) + { + g_cancellable_disconnect (dex_promise_get_cancellable (data->name_acquired), + data->name_acquired_cancelled_id); + data->name_acquired_cancelled_id = 0; + } + else if (dex_promise_get_cancellable (data->name_lost) != cancellable && + data->name_lost_cancelled_id) + { + g_cancellable_disconnect (dex_promise_get_cancellable (data->name_lost), + data->name_lost_cancelled_id); + data->name_lost_cancelled_id = 0; + } + + if (dex_future_is_pending (DEX_FUTURE (data->name_acquired))) + { + dex_promise_reject (data->name_acquired, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Cancelled")); + } + + if (dex_future_is_pending (DEX_FUTURE (data->name_lost))) + { + dex_promise_reject (data->name_lost, + g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Cancelled")); + } + g_clear_handle_id (&data->own_name_id, g_bus_unown_name); } @@ -433,17 +468,16 @@ dex_bus_own_name_on_connection (GDBusConnection *connection, data->name_acquired = dex_promise_new_cancellable (); data->name_lost = dex_promise_new_cancellable (); - data->acquired_cancelled_id = - g_signal_connect_swapped (dex_promise_get_cancellable (data->name_acquired), - "cancelled", - G_CALLBACK (dex_bus_name_cancelled_cb), - data); - - data->lost_cancelled_id = - g_signal_connect_swapped (dex_promise_get_cancellable (data->name_lost), - "cancelled", - G_CALLBACK (dex_bus_name_cancelled_cb), - data); + data->name_acquired_cancelled_id = + g_cancellable_connect (dex_promise_get_cancellable (data->name_acquired), + G_CALLBACK (dex_bus_name_cancelled_cb), + data, NULL); + + data->name_lost_cancelled_id = + g_cancellable_connect (dex_promise_get_cancellable (data->name_lost), + G_CALLBACK (dex_bus_name_cancelled_cb), + data, NULL); + data->own_name_id = g_bus_own_name_on_connection (connection, name, -- GitLab From 30dbe683b554ca001441ae1ef45a7c885b806b99 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 7 Jan 2026 18:40:45 -0800 Subject: [PATCH 04/10] gdbus: add DexDBusInterfaceSkeleton for dispatching methods in fibers It is by itself not that useful, because GDBusInterfaceSkeleton are supposed to be used with the gdbus codegen. We will add that in the next commits. --- meson.build | 10 ++ meson_options.txt | 3 + src/dex-features.h.in | 32 ++++++ src/dex-gdbus.c | 226 ++++++++++++++++++++++++++++++++++++++++++ src/dex-gdbus.h | 42 ++++++++ src/meson.build | 11 ++ 6 files changed, 324 insertions(+) create mode 100644 src/dex-features.h.in diff --git a/meson.build b/meson.build index 67201b9..466050d 100644 --- a/meson.build +++ b/meson.build @@ -58,6 +58,16 @@ if cc.has_header('ucontext.h') config_h.set('HAVE_UCONTEXT_H', 1) endif +have_gdbus_codegen = get_option('gdbus').require( + cc.has_member( + 'GDBusInterfaceSkeletonClass', + 'method_dispatch', + prefix : '#include ', + dependencies: glib_dep, + ), + error_message: 'gio GDBusInterfaceSkeletonClass::method_dispatch vfunc is required for -Dgdbus=enabled', +).allowed() + if host_machine.system() == 'darwin' # known alignment for darwin where we're using helpers if host_machine.cpu_family() == 'aarch64' diff --git a/meson_options.txt b/meson_options.txt index b8c171c..01ea56c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -31,3 +31,6 @@ option('pygobject', option('pygobject-override-dir', type: 'string', value: '', description: 'Path to pygobject overrides directory') +option('gdbus', + type: 'feature', value: 'auto', + description: 'Integrate with gdbus') diff --git a/src/dex-features.h.in b/src/dex-features.h.in new file mode 100644 index 0000000..79755f4 --- /dev/null +++ b/src/dex-features.h.in @@ -0,0 +1,32 @@ +/* dex-features.h.in + * + * Copyright 2025 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +/** + * DEX_FEATURE_GDBUS_CODEGEN: + * + * If libdex is built with gdbus codegen + */ +#define DEX_FEATURE_GDBUS_CODEGEN (@HAVE_GDBUS_CODEGEN@) +#if DEX_FEATURE_GDBUS_CODEGEN == 0 +# undef DEX_FEATURE_GDBUS_CODEGEN +#endif diff --git a/src/dex-gdbus.c b/src/dex-gdbus.c index 34f32db..3d3279c 100644 --- a/src/dex-gdbus.c +++ b/src/dex-gdbus.c @@ -22,12 +22,238 @@ #include "config.h" #include "dex-async-pair-private.h" +#include "dex-cancellable.h" #include "dex-future-private.h" #include "dex-future-set.h" #include "dex-promise.h" +#include "dex-scheduler.h" #include "dex-gdbus.h" +#ifdef DEX_FEATURE_GDBUS_CODEGEN +typedef struct _DexDBusInterfaceSkeletonPrivate +{ + GCancellable *cancellable; + DexDBusInterfaceSkeletonFlags flags; +} DexDBusInterfaceSkeletonPrivate; + +/** + * DexDBusInterfaceSkeleton: + * + * #DexDBusInterfaceSkeleton provides integration between libdex and the GDBus + * codegen. If the gdbus-codegen dex extension is used, all generated + * InterfaceSkeletons inherit from #DexDBusInterfaceSkeleton instead of + * #GDBusInterfaceSkeleton, which allows the use of the API exposed here. + */ + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (DexDBusInterfaceSkeleton, + dex_dbus_interface_skeleton, + G_TYPE_DBUS_INTERFACE_SKELETON, + G_ADD_PRIVATE (DexDBusInterfaceSkeleton)) + +typedef struct _DispatchInFiberData +{ + GDBusInterfaceMethodCallFunc method_call_func; + GDBusMethodInvocation *invocation; + GDBusInterfaceSkeleton *_interface; + GDBusObject *object; +} DispatchInFiberData; + +static void +dispatch_in_fiber_data_free (DispatchInFiberData *data) +{ + g_clear_object (&data->invocation); + g_clear_object (&data->_interface); + g_clear_object (&data->object); + free (data); +} + +static DexFuture * +dispatch_in_fiber (gpointer user_data) +{ + DispatchInFiberData *data = user_data; + GDBusMethodInvocation *invocation = data->invocation; + GDBusInterfaceSkeleton *_interface = data->_interface; + GDBusObject *object = data->object; + gboolean authorized = TRUE; + + if (object != NULL) + { + g_signal_emit_by_name (object, + "authorize-method", + _interface, + invocation, + &authorized); + } + if (authorized) + { + g_signal_emit_by_name (_interface, + "g-authorize-method", + invocation, + &authorized); + } + + g_clear_object (&data->_interface); + g_clear_object (&data->object); + + if (authorized) + { + GDBusInterfaceMethodCallFunc func = data->method_call_func; + + func (g_dbus_method_invocation_get_connection (invocation), + g_dbus_method_invocation_get_sender (invocation), + g_dbus_method_invocation_get_object_path (invocation), + g_dbus_method_invocation_get_interface_name (invocation), + g_dbus_method_invocation_get_method_name (invocation), + g_dbus_method_invocation_get_parameters (invocation), + invocation, + g_dbus_method_invocation_get_user_data (invocation)); + } + + return dex_future_new_true (); +} + +static void +dex_dbus_interface_skeleton_method_dispatch (GDBusInterfaceSkeleton *_interface, + GDBusInterfaceMethodCallFunc method_call_func, + GDBusMethodInvocation *invocation, + GDBusInterfaceSkeletonFlags flags, + GDBusObject *object) +{ + DexDBusInterfaceSkeleton *dex_interface_ = DEX_DBUS_INTERFACE_SKELETON (_interface); + DexDBusInterfaceSkeletonPrivate *priv = + dex_dbus_interface_skeleton_get_instance_private (dex_interface_); + DispatchInFiberData *data; + DexFuture *future; + + if ((priv->flags & DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER) == 0) + { + GDBusInterfaceSkeletonClass *skeleton_class = + G_DBUS_INTERFACE_SKELETON_CLASS (dex_dbus_interface_skeleton_parent_class); + + skeleton_class->method_dispatch (_interface, method_call_func, invocation, flags, object); + return; + } + + g_return_if_fail ((flags & G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD) == 0); + + data = g_new0 (DispatchInFiberData, 1); + data->method_call_func = method_call_func; + g_set_object (&data->invocation, invocation); + g_set_object (&data->object, object); + g_set_object (&data->_interface, _interface); + + future = dex_scheduler_spawn (NULL, 0, + dispatch_in_fiber, + data, + (GDestroyNotify) dispatch_in_fiber_data_free); + + dex_future_disown (dex_future_first (future, + dex_cancellable_new_from_cancellable (priv->cancellable), + NULL)); +} + +static void +dex_dbus_interface_skeleton_dispose (GObject *object) +{ + DexDBusInterfaceSkeleton *interface_ = DEX_DBUS_INTERFACE_SKELETON (object); + DexDBusInterfaceSkeletonPrivate *priv = + dex_dbus_interface_skeleton_get_instance_private (interface_); + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + + G_OBJECT_CLASS (dex_dbus_interface_skeleton_parent_class)->dispose (object); +} + +static void +dex_dbus_interface_skeleton_class_init (DexDBusInterfaceSkeletonClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GDBusInterfaceSkeletonClass *skeleton_class = G_DBUS_INTERFACE_SKELETON_CLASS (klass); + + object_class->dispose = dex_dbus_interface_skeleton_dispose; + + skeleton_class->method_dispatch = dex_dbus_interface_skeleton_method_dispatch; +} + +static void +dex_dbus_interface_skeleton_init (DexDBusInterfaceSkeleton *interface_) +{ + DexDBusInterfaceSkeletonPrivate *priv = + dex_dbus_interface_skeleton_get_instance_private (interface_); + + priv->cancellable = g_cancellable_new (); +} + +/** + * dex_dbus_interface_skeleton_cancel: + * @interface_: a #DexDBusInterfaceSkeleton + * + * Cancels all in-flight fibers. + * + * Since: 1.1 + */ +void +dex_dbus_interface_skeleton_cancel (DexDBusInterfaceSkeleton *interface_) +{ + DexDBusInterfaceSkeletonPrivate *priv; + + g_return_if_fail (DEX_IS_DBUS_INTERFACE_SKELETON (interface_)); + + priv = dex_dbus_interface_skeleton_get_instance_private (interface_); + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + priv->cancellable = g_cancellable_new (); +} + +/** + * dex_dbus_interface_skeleton_get_flags: + * @interface_: a #DexDBusInterfaceSkeleton + * + * Gets the #DexDBusInterfaceSkeletonFlags that describes the behavior + * of @interface_ + * + * Returns: One or more flags from the #DexDBusInterfaceSkeletonFlags enumeration. + * + * Since: 1.1 + */ +DexDBusInterfaceSkeletonFlags +dex_dbus_interface_skeleton_get_flags (DexDBusInterfaceSkeleton *interface_) +{ + DexDBusInterfaceSkeletonPrivate *priv; + + g_return_val_if_fail (DEX_IS_DBUS_INTERFACE_SKELETON (interface_), + DEX_DBUS_INTERFACE_SKELETON_FLAGS_NONE); + + priv = dex_dbus_interface_skeleton_get_instance_private (interface_); + return priv->flags; +} + +/** + * dex_dbus_interface_skeleton_set_flags: + * @interface_: a #DexDBusInterfaceSkeleton + * @flags: Flags from the #DexDBusInterfaceSkeletonFlags enumeration. + * + * Sets flags describing what the behavior of @interface_ should be. + * + * Since: 1.1 + */ +void +dex_dbus_interface_skeleton_set_flags (DexDBusInterfaceSkeleton *interface_, + DexDBusInterfaceSkeletonFlags flags) +{ + DexDBusInterfaceSkeletonPrivate *priv; + + g_return_if_fail (DEX_IS_DBUS_INTERFACE_SKELETON (interface_)); + + priv = dex_dbus_interface_skeleton_get_instance_private (interface_); + + priv->flags = flags; +} +#endif /* DEX_FEATURE_GDBUS_CODEGEN */ + static inline DexAsyncPair * create_async_pair (const char *name) { diff --git a/src/dex-gdbus.h b/src/dex-gdbus.h index dff27db..b9d2197 100644 --- a/src/dex-gdbus.h +++ b/src/dex-gdbus.h @@ -27,10 +27,52 @@ # include #endif +#include "dex-features.h" #include "dex-future.h" G_BEGIN_DECLS +#ifdef DEX_FEATURE_GDBUS_CODEGEN +/** + * DexDBusInterfaceSkeletonFlags: + * @DEX_DBUS_INTERFACE_SKELETON_FLAGS_NONE: No flags set. + * @DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER: Each method invocation is + * handled in a fiber dedicated to the invocation. This means that the method implementation can + * use dex_await or similar. Authorization for method invocations uses the same fiber. + * This can not be used in combination with METHOD_INVOCATIONS_IN_THREAD and trying to do so leads + * to a runtime error. + * + * Flags describing the behavior of a #GDBusInterfaceSkeleton instance. + * + * Since: 1.1 + */ +typedef enum +{ + DEX_DBUS_INTERFACE_SKELETON_FLAGS_NONE = 0, + DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER = (1 << 0), +} DexDBusInterfaceSkeletonFlags; + +#define DEX_TYPE_DBUS_INTERFACE_SKELETON (dex_dbus_interface_skeleton_get_type ()) +DEX_AVAILABLE_IN_1_1 +G_DECLARE_DERIVABLE_TYPE (DexDBusInterfaceSkeleton, + dex_dbus_interface_skeleton, + DEX, DBUS_INTERFACE_SKELETON, + GDBusInterfaceSkeleton) + +struct _DexDBusInterfaceSkeletonClass +{ + GDBusInterfaceSkeletonClass parent_class; +}; + +DEX_AVAILABLE_IN_1_1 +void dex_dbus_interface_skeleton_cancel (DexDBusInterfaceSkeleton *interface_); +DEX_AVAILABLE_IN_1_1 +DexDBusInterfaceSkeletonFlags dex_dbus_interface_skeleton_get_flags (DexDBusInterfaceSkeleton *interface_); +DEX_AVAILABLE_IN_1_1 +void dex_dbus_interface_skeleton_set_flags (DexDBusInterfaceSkeleton *interface_, + DexDBusInterfaceSkeletonFlags flags); +#endif /* DEX_FEATURE_GDBUS_CODEGEN */ + DEX_AVAILABLE_IN_ALL DexFuture *dex_bus_get (GBusType bus_type) G_GNUC_WARN_UNUSED_RESULT; DEX_AVAILABLE_IN_1_1 diff --git a/src/meson.build b/src/meson.build index 2ac169b..a46959e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -74,6 +74,9 @@ libdex_deps = [ glib_dep, ] +features_conf = configuration_data() +features_conf.set10('HAVE_GDBUS_CODEGEN', have_gdbus_codegen) + if host_machine.system() != 'darwin' and cc.get_argument_syntax() != 'msvc' libatomic_dep = cc.find_library('atomic') if not cc.links('int main(){}', dependencies: [libatomic_dep]) @@ -150,6 +153,14 @@ configure_file( install_dir: join_paths(get_option('includedir'), 'libdex-@0@'.format(api_version)) ) +configure_file( + input: 'dex-features.h.in', + output: 'dex-features.h', + configuration: features_conf, + install: true, + install_dir: join_paths(get_option('includedir'), 'libdex-@0@'.format(api_version)) +) + if get_option('sysprof') libdex_deps += [libsysprof_capture_dep] endif -- GitLab From 48c59df8895ccb964f2f1d39c7594287bbbf865c Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 7 Jan 2026 18:41:41 -0800 Subject: [PATCH 05/10] gdbus: add a gdbus codegen extension The extension can be used by projects which depend on libdex by passing `--extension-path=path/to/dex-gdbus-codegen-extension.py` to `gdbus-codegen`. Every proxy creation then has an additional `${interface}_proxy_new_future` function, which returns a future which resolves to a proxy of that interface. Every proxy method call then has an additional `${interface}_call_${method}_future` function, which returns a future which resolves to a boxed type that respresents the results of the method call. Every proxy signal has an additional `${interface}_wait_${signal}_future` function, which waits for a signal to get emitted and returns a future which resolves to a boxed type that respresents the signal emission. Additionally, Skeletons then inherit from DexDBusInterfaceSkeleton instead of GDBusInterfaceSkeleton. It dispatches all the interface method handlers, and the authorize signal in a fiber. --- .gitignore | 1 + src/dex-gdbus-codegen-extension.py | 908 +++++++++++++++++++++++++++++ src/meson.build | 13 + 3 files changed, 922 insertions(+) create mode 100644 src/dex-gdbus-codegen-extension.py diff --git a/.gitignore b/.gitignore index 2a1fb94..4b49892 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *~ build/ .buildconfig +__pycache__ diff --git a/src/dex-gdbus-codegen-extension.py b/src/dex-gdbus-codegen-extension.py new file mode 100644 index 0000000..495018e --- /dev/null +++ b/src/dex-gdbus-codegen-extension.py @@ -0,0 +1,908 @@ +# dex gdbus-codegen extension +# +# Copyright 2025 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 of the +# License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# SPDX-License-Identifier: LGPL-2.1-or-later + + +# This is supposed to be loaded as an extension to gdbus-codegen, by invoking +# gdbus-codegen --extension-path=path/to/dex-gdbus-codegen-extension.py + + +def init(args, options): + # We can require a certain glib version which will support at least + # some version of the codegen, but we might also have a newer version + # which breaks backwards compatibility. + # Here we just ensure that the glib codegen is compatible with this + # extension. If a new version gets released, we have to somehow adjust + # the code accordingly, but ensure it still works with older versions. + assert options["version"] <= 1 + pass + +class HeaderCodeGenerator: + def __init__(self, generator): + self.ifaces = generator.ifaces + self.outfile = generator.outfile + self.symbol_decorator = generator.symbol_decorator + self.generate_autocleanup = generator.generate_autocleanup + self.glib_min_required = generator.glib_min_required + + generator.skeleton_type_camel = "DexDBusInterfaceSkeleton" + + def generate_method_call(self, i, m): + if len(m.out_args) > 0: + result_type = f"{i.camel_name}{m.name}Result" + function_prefix = f"{i.name_lower}_{m.name_lower}_result" + type_name = f"{i.ns_upper}TYPE_{i.name_upper}_{m.name_upper}_RESULT" + + self.outfile.write("\n") + self.outfile.write("typedef struct _%s\n" % result_type) + self.outfile.write("{\n") + for a in m.out_args: + self.outfile.write(" %s %s;\n" % (a.ctype_out, a.name)) + if m.unix_fd: + self.outfile.write(" GUnixFDList *fd_list;\n") + self.outfile.write("} %s;\n" % result_type) + self.outfile.write("\n") + + self.outfile.write( + "#define %s (%s_get_type ())\n" % (type_name, function_prefix) + ) + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + self.outfile.write( + "GType %s_get_type (void) G_GNUC_CONST;\n" % function_prefix + ) + + self.outfile.write("\n") + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + self.outfile.write( + "%s * %s_copy (%s *result);\n" + % (result_type, function_prefix, result_type) + ) + self.outfile.write( + "void %s_free (%s *result);\n" % (function_prefix, result_type) + ) + + if self.generate_autocleanup in ("objects", "all"): + self.outfile.write( + "G_DEFINE_AUTOPTR_CLEANUP_FUNC (%s, %s_free)\n" + % (result_type, function_prefix) + ) + self.outfile.write("\n") + + self.outfile.write("\n") + + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + if m.deprecated: + self.outfile.write("G_GNUC_DEPRECATED ") + self.outfile.write( + "DexFuture * %s_call_%s_future (\n" + " %s *proxy" % (i.name_lower, m.name_lower, i.camel_name) + ) + for a in m.in_args: + self.outfile.write(",\n %sarg_%s" % (a.ctype_in, a.name)) + if self.glib_min_required >= (2, 64): + self.outfile.write( + ",\n GDBusCallFlags call_flags" + ",\n gint timeout_msec" + ) + if m.unix_fd: + self.outfile.write(",\n GUnixFDList *fd_list") + self.outfile.write(");\n") + self.outfile.write("\n") + + def generate_proxy(self, i): + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + if i.deprecated: + self.outfile.write("G_GNUC_DEPRECATED ") + self.outfile.write( + "DexFuture * %s_proxy_new_future (\n" + " GDBusConnection *connection,\n" + " GDBusProxyFlags flags,\n" + " const gchar *name,\n" + " const gchar *object_path);\n" % (i.name_lower) + ) + + def generate_signal_wait(self, i, s): + if len(s.args) > 0: + signal_type = f"{i.camel_name}{s.name}Signal" + function_prefix = f"{i.name_lower}_{s.name_lower}_signal" + type_name = f"{i.ns_upper}TYPE_{i.name_upper}_{s.name_upper}_SIGNAL" + + self.outfile.write("\n") + self.outfile.write("typedef struct _%s\n" % signal_type) + self.outfile.write("{\n") + for a in s.args: + self.outfile.write(" %s %s;\n" % (a.ctype_out, a.name)) + # FIXME: fd passing? + self.outfile.write("} %s;\n" % signal_type) + self.outfile.write("\n") + + self.outfile.write( + "#define %s (%s_get_type ())\n" % (type_name, function_prefix) + ) + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + self.outfile.write( + "GType %s_get_type (void) G_GNUC_CONST;\n" % function_prefix + ) + + self.outfile.write("\n") + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + self.outfile.write( + "%s * %s_copy (%s *signal);\n" + % (signal_type, function_prefix, signal_type) + ) + self.outfile.write( + "void %s_free (%s *signal);\n" % (function_prefix, signal_type) + ) + + if self.generate_autocleanup in ("objects", "all"): + self.outfile.write( + "G_DEFINE_AUTOPTR_CLEANUP_FUNC (%s, %s_free)\n" + % (signal_type, function_prefix) + ) + self.outfile.write("\n") + + self.outfile.write("\n") + + if self.symbol_decorator is not None: + self.outfile.write("%s\n" % self.symbol_decorator) + if s.deprecated: + self.outfile.write("G_GNUC_DEPRECATED ") + self.outfile.write( + "DexFuture * %s_wait_%s_future (%s *proxy);\n" + % (i.name_lower, s.name_lower, i.camel_name) + ) + self.outfile.write("\n") + + def generate_signal_monitor(self, i): + if len(i.signals) == 0: + return + + self.outfile.write( + "typedef enum _%sSignals\n" + "{\n" + % i.camel_name + ) + for n, s in enumerate(i.signals): + self.outfile.write( + " %s%s_SIGNAL_%s = (%d << 0),\n" + % (i.ns_upper, i.name_upper, s.name_upper, n + 1) + ) + self.outfile.write( + "} %sSignals;\n\n" + % i.camel_name + ) + + self.outfile.write( + "struct _%sSignalMonitor\n" + "{\n" + " GObject parent_instance;\n" + "\n" + " %s *object;\n" + % (i.camel_name, i.camel_name) + ) + for s in i.signals: + self.outfile.write( + " DexChannel *%s_channel;\n" + " gulong %s_signal_id;\n" + % (s.name_lower, s.name_lower) + ) + self.outfile.write("};\n\n") + + self.outfile.write( + "#define %sTYPE_%s_SIGNAL_MONITOR (%s_signal_monitor_get_type())\n" + % (i.ns_upper, i.name_upper, i.name_lower) + ) + + self.outfile.write( + "G_DECLARE_FINAL_TYPE (%sSignalMonitor,\n" + " %s_signal_monitor,\n" + " %s, %s_SIGNAL_MONITOR,\n" + " GObject)\n\n" + % (i.camel_name, i.name_lower, i.ns_upper[:-1], i.name_upper) + ) + + self.outfile.write( + "%sSignalMonitor *\n" + "%s_signal_monitor_new (\n" + " %s *object,\n" + " %sSignals signals);\n\n" + % (i.camel_name, i.name_lower, i.camel_name, i.camel_name) + ) + self.outfile.write( + "void\n" + "%s_signal_monitor_cancel (%sSignalMonitor *self);\n\n" + % (i.name_lower, i.camel_name) + ) + for s in i.signals: + self.outfile.write( + "DexFuture *\n" + "%s_signal_monitor_next_%s (%sSignalMonitor *self);\n\n" + % (i.name_lower, s.name_lower, i.camel_name) + ) + + def generate_includes(self): + self.outfile.write("#include \n") + + def declare_types(self): + for i in self.ifaces: + self.generate_proxy(i) + for m in i.methods: + self.generate_method_call(i, m) + for s in i.signals: + self.generate_signal_wait(i, s) + self.generate_signal_monitor(i) + + +class CodeGenerator: + def __init__(self, generator): + self.ifaces = generator.ifaces + self.outfile = generator.outfile + self.symbol_decoration_define = generator.symbol_decoration_define + self.glib_min_required = generator.glib_min_required + self.docbook_gen = generator.docbook_gen + self.write_gtkdoc_deprecated_and_since_and_close = generator.write_gtkdoc_deprecated_and_since_and_close + + generator.skeleton_type_upper = "DEX_TYPE_DBUS_INTERFACE_SKELETON" + + def generate_proxy(self, i): + self.outfile.write( + "static void\n" + "%s_proxy_new_future_cb (\n" + " GObject *object G_GNUC_UNUSED,\n" + " GAsyncResult *result,\n" + " gpointer user_data)\n" + "{\n" + " DexPromise *promise = DEX_PROMISE (user_data);\n" + " %s *proxy;\n" + " GError *error = NULL;\n" + " G_GNUC_BEGIN_IGNORE_DEPRECATIONS\n" + " proxy = %s_proxy_new_finish (result, &error);\n" + " G_GNUC_END_IGNORE_DEPRECATIONS\n" + " if (proxy == NULL)\n" + " dex_promise_reject (promise, error);\n" + " else\n" + " dex_promise_resolve_object (promise, proxy);\n" + "}\n" + "\n" % (i.name_lower, i.camel_name, i.name_lower) + ) + self.outfile.write( + self.docbook_gen.expand( + "/**\n" + " * %s_proxy_new_future:\n" + " * @connection: A #GDBusConnection.\n" + " * @flags: Flags from the #GDBusProxyFlags enumeration.\n" + " * @name: (nullable): A bus name (well-known or unique) or %%NULL if @connection is not a message bus connection.\n" + " * @object_path: An object path.\n" + " *\n" + " * Returns a future which resolves to a proxy for the D-Bus interface #%s. See g_dbus_proxy_new() for more details.\n" + " *\n" + " * See %s_proxy_new_sync() for the synchronous, blocking version of this constructor.\n" + % (i.name_lower, i.name, i.name_lower), + False, + ) + ) + self.write_gtkdoc_deprecated_and_since_and_close(i, self.outfile, 0) + self.outfile.write( + "DexFuture *\n" + "%s_proxy_new_future (\n" + " GDBusConnection *connection,\n" + " GDBusProxyFlags flags,\n" + " const gchar *name,\n" + " const gchar *object_path)\n" + "{\n" + " DexPromise *promise = dex_promise_new_cancellable ();\n" + " G_GNUC_BEGIN_IGNORE_DEPRECATIONS\n" + " %s_proxy_new (\n" + " connection,\n" + " flags,\n" + " name,\n" + " object_path,\n" + " dex_promise_get_cancellable (promise),\n" + " %s_proxy_new_future_cb,\n" + " dex_ref (promise));\n" + " G_GNUC_END_IGNORE_DEPRECATIONS\n" + " return DEX_FUTURE (promise);\n" + "}\n" + "\n" % (i.name_lower, i.name_lower, i.name_lower) + ) + + def generate_method_call(self, i, m): + result_type = f"{i.camel_name}{m.name}Result" + result_function_prefix = f"{i.name_lower}_{m.name_lower}_result" + result_type_name = ( + f"{i.ns_upper}TYPE_{i.name_upper}_{m.name_upper}_RESULT" + ) + call_function_prefix = f"{i.name_lower}_call_{m.name_lower}" + + if len(m.out_args) > 0: + self.outfile.write( + "%s *\n" + "%s_copy (%s *r)\n" + "{\n" + " %s *n = g_new0 (%s, 1);\n" + % ( + result_type, + result_function_prefix, + result_type, + result_type, + result_type, + ) + ) + for a in m.out_args: + if a.copy_func: + self.outfile.write( + " n->%s = r->%s ? %s (r->%s) : NULL;\n" + % (a.name, a.name, a.copy_func, a.name) + ) + else: + self.outfile.write(" n->%s = r->%s;\n" % (a.name, a.name)) + if m.unix_fd: + self.outfile.write( + " n->fd_list = r->fd_list ? g_object_ref (r->fd_list) : NULL;\n" + ) + self.outfile.write(" return n;\n" "}\n" "\n") + self.outfile.write( + "void\n" + "%s_free (%s *r)\n" + "{\n" % (result_function_prefix, result_type) + ) + for a in m.out_args: + if a.free_func: + self.outfile.write( + " %s (r->%s);\n" % (a.free_func, a.name) + ) + if m.unix_fd: + self.outfile.write(" g_clear_object (&r->fd_list);\n") + self.outfile.write(" free (r);\n" "}\n" "\n") + self.outfile.write( + "G_DEFINE_BOXED_TYPE (\n" + " %s,\n" + " %s,\n" + " %s_copy,\n" + " %s_free)\n" + "\n" + % ( + result_type, + result_function_prefix, + result_function_prefix, + result_function_prefix, + ) + ) + self.outfile.write( + "static void\n" + "%s_future_cb (\n" + " GObject *object,\n" + " GAsyncResult *result,\n" + " gpointer user_data)\n" + "{\n" + " DexPromise *promise = DEX_PROMISE (user_data);\n" + " GError *error = NULL;\n" % call_function_prefix + ) + if len(m.out_args) > 0: + self.outfile.write(" %s *r;\n" % result_type) + if m.unix_fd: + self.outfile.write(" GUnixFDList *fd_list;\n") + for a in m.out_args: + self.outfile.write(" %s arg_%s;\n" % (a.ctype_out, a.name)) + self.outfile.write( + " G_GNUC_BEGIN_IGNORE_DEPRECATIONS\n" + " gboolean success = %s_finish (\n" + " %s%s (object),\n" + "" % (call_function_prefix, i.ns_upper, i.name_upper) + ) + for a in m.out_args: + self.outfile.write(" &arg_%s,\n" % a.name) + if m.unix_fd: + self.outfile.write(" &fd_list,\n") + self.outfile.write( + " result,\n" + " &error);\n" + " G_GNUC_END_IGNORE_DEPRECATIONS\n" + " if (!success)\n" + " return dex_promise_reject (promise, error);\n" + ) + if len(m.out_args) > 0: + self.outfile.write(" r = g_new0 (%s, 1);\n" % result_type) + for a in m.out_args: + self.outfile.write(" r->%s = arg_%s;\n" % (a.name, a.name)) + if m.unix_fd: + self.outfile.write(" r->fd_list = fd_list;\n") + self.outfile.write( + " dex_promise_resolve_boxed (\n" + " promise,\n" + " %s,\n" + " r);\n" + "}\n\n" % result_type_name + ) + else: + self.outfile.write( + " dex_promise_resolve_boolean (promise, TRUE);\n" "}\n\n" + ) + + self.outfile.write( + "/**\n" + " * %s_future:\n" + " * @proxy: A #%sProxy.\n" % (call_function_prefix, i.camel_name) + ) + for a in m.in_args: + self.outfile.write( + " * @arg_%s: Argument to pass with the method invocation.\n" + % (a.name) + ) + if self.glib_min_required >= (2, 64): + self.outfile.write( + " * @call_flags: Flags from the #GDBusCallFlags enumeration. If you want to allow interactive\n" + " authorization be sure to set %G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION.\n" + ' * @timeout_msec: The timeout in milliseconds (with %G_MAXINT meaning "infinite") or\n' + " -1 to use the proxy default timeout.\n" + ) + if m.unix_fd: + self.outfile.write( + " * @fd_list: (nullable): A #GUnixFDList or %NULL.\n" + ) + self.outfile.write( + self.docbook_gen.expand( + " *\n" + " * Invokes the %s.%s() D-Bus method on @proxy and returns a future representing the invocation.\n" + " *\n" % (i.name, m.name), + False, + ) + ) + if len(m.out_args) > 0: + self.outfile.write( + " * The future resolves to the boxed #%s on success.\n" + " *\n" % result_type, + ) + else: + self.outfile.write( + " * The future resolves to %TRUE on success.\n" " *\n" + ) + self.outfile.write( + self.docbook_gen.expand( + " * Returns: (transfer full): The future\n" + " *\n" + " * See %s_call_%s_sync() for the synchronous, blocking version of this method.\n" + % (i.name_lower, m.name_lower), + False, + ) + ) + self.write_gtkdoc_deprecated_and_since_and_close(m, self.outfile, 0) + + self.outfile.write( + "DexFuture *\n" + "%s_future (\n" + " %s *proxy" % (call_function_prefix, i.camel_name) + ) + for a in m.in_args: + self.outfile.write(",\n %sarg_%s" % (a.ctype_in, a.name)) + if self.glib_min_required >= (2, 64): + self.outfile.write(",\n GDBusCallFlags call_flags" ",\n gint timeout_msec") + if m.unix_fd: + self.outfile.write(",\n GUnixFDList *fd_list") + self.outfile.write( + ")\n" + "{\n" + " DexPromise *promise = dex_promise_new_cancellable ();\n" + " G_GNUC_BEGIN_IGNORE_DEPRECATIONS\n" + " %s (\n" + " proxy" % call_function_prefix + ) + for a in m.in_args: + self.outfile.write(",\n arg_%s" % a.name) + if self.glib_min_required >= (2, 64): + self.outfile.write( + ",\n call_flags" ",\n timeout_msec" + ) + if m.unix_fd: + self.outfile.write(",\n fd_list") + self.outfile.write( + ",\n dex_promise_get_cancellable (promise),\n" + " %s_future_cb,\n" + " dex_ref (promise));\n" + " G_GNUC_END_IGNORE_DEPRECATIONS\n" + " return DEX_FUTURE (promise);\n" + "}\n" + "\n" % call_function_prefix + ) + + def generate_signal_wait(self, i, s): + signal_type = f"{i.camel_name}{s.name}Signal" + signal_function_prefix = f"{i.name_lower}_{s.name_lower}_signal" + signal_type_name = ( + f"{i.ns_upper}TYPE_{i.name_upper}_{s.name_upper}_SIGNAL" + ) + wait_function_prefix = f"{i.name_lower}_wait_{s.name_lower}" + + if len(s.args) > 0: + self.outfile.write( + "%s *\n" + "%s_copy (%s *r)\n" + "{\n" + " %s *n = g_new0 (%s, 1);\n" + % ( + signal_type, + signal_function_prefix, + signal_type, + signal_type, + signal_type, + ) + ) + for a in s.args: + if a.copy_func: + self.outfile.write( + " n->%s = r->%s ? %s (r->%s) : NULL;\n" + % (a.name, a.name, a.copy_func, a.name) + ) + else: + self.outfile.write(" n->%s = r->%s;\n" % (a.name, a.name)) + # FIXME: fd passing? + self.outfile.write(" return n;\n" "}\n" "\n") + self.outfile.write( + "void\n" + "%s_free (%s *r)\n" + "{\n" % (signal_function_prefix, signal_type) + ) + for a in s.args: + if a.free_func: + self.outfile.write( + " %s (r->%s);\n" % (a.free_func, a.name) + ) + # FIXME: fd passing? + self.outfile.write(" free (r);\n" "}\n" "\n") + self.outfile.write( + "G_DEFINE_BOXED_TYPE (\n" + " %s,\n" + " %s,\n" + " %s_copy,\n" + " %s_free)\n" + "\n" + % ( + signal_type, + signal_function_prefix, + signal_function_prefix, + signal_function_prefix, + ) + ) + self.outfile.write("\n") + + self.outfile.write( + "static void\n" + "_%s_signalled_cb (\n" + " %s *proxy" + % (wait_function_prefix, i.camel_name) + ) + for a in s.args: + self.outfile.write(",\n %sarg_%s" % (a.ctype_in, a.name)) + self.outfile.write( + ",\n gpointer user_data)\n" + "{\n" + " GDBusFutureSignalData *data = user_data;\n" + ) + if len(s.args) > 0: + self.outfile.write( + " %s *signal = g_new0 (%s, 1);\n" + % (signal_type, signal_type, ) + ) + for a in s.args: + if a.copy_func: + self.outfile.write( + " signal->%s = arg_%s ? %s (arg_%s) : NULL;\n" + % (a.name, a.name, a.copy_func, a.name) + ) + else: + self.outfile.write(" signal->%s = arg_%s;\n" % (a.name, a.name)) + self.outfile.write( + " g_cancellable_disconnect (dex_promise_get_cancellable (data->promise),\n" + " data->cancelled_handler_id);\n" + " data->cancelled_handler_id = 0;\n" + ) + if len(s.args) > 0: + self.outfile.write( + " dex_promise_resolve_boxed (data->promise, %s, signal);\n" + % (signal_type_name) + ) + else: + self.outfile.write( + " dex_promise_resolve_boolean (data->promise, TRUE);\n" + ) + self.outfile.write( + " gdbus_future_signal_data_free (data);\n" + "}\n\n" + ) + + self.outfile.write( + "/**\n" + " * %s_future:\n" + " * @proxy: A #%sProxy.\n" % (wait_function_prefix, i.camel_name) + ) + self.outfile.write( + self.docbook_gen.expand( + " *\n" + " * Waits for a %s::%s() D-Bus signal on @proxy and returns a future representing the signal emission.\n" + " *\n" % (i.name, s.name_hyphen), + False, + ) + ) + if len(s.args) > 0: + self.outfile.write( + " * The future resolves to the boxed #%s on success.\n" + " *\n" % signal_type, + ) + else: + self.outfile.write( + " * The future resolves to %TRUE on success.\n" " *\n" + ) + self.outfile.write( + self.docbook_gen.expand( + " * Returns: (transfer full): The future\n", + False, + ) + ) + self.write_gtkdoc_deprecated_and_since_and_close(s, self.outfile, 0) + + self.outfile.write( + "DexFuture *\n" + "%s_future (%s *proxy)" % (wait_function_prefix, i.camel_name) + ) + self.outfile.write( + "{\n" + " GDBusFutureSignalData *data = g_new0 (GDBusFutureSignalData, 1);\n" + " data->proxy = G_DBUS_PROXY (g_object_ref (proxy));\n" + " data->promise = dex_promise_new_cancellable ();\n" + " data->cancelled_handler_id =\n" + " g_cancellable_connect (dex_promise_get_cancellable (data->promise),\n" + " G_CALLBACK (gdbus_future_signal_cancelled_cb),\n" + " data, NULL);\n" + " data->signalled_handler_id =\n" + " g_signal_connect (proxy, \"%s\",\n" + " G_CALLBACK (_%s_signalled_cb),\n" # FIXME! + " data);\n" + " return DEX_FUTURE (dex_ref (data->promise));\n" + "}\n" + % (s.name_hyphen, wait_function_prefix) + ) + + def generate_signal_monitor(self, i): + if len(i.signals) == 0: + return + + self.outfile.write( + "G_DEFINE_FINAL_TYPE (%sSignalMonitor,\n" + " %s_signal_monitor,\n" + " G_TYPE_OBJECT)\n\n" + % (i.camel_name, i.name_lower) + ) + + for s in i.signals: + self.outfile.write( + "static void\n" + "%s_signal_monitor_%s_cb (\n" + " %sSignalMonitor *self,\n" + % (i.name_lower, s.name_lower, i.camel_name) + ) + for a in s.args: + self.outfile.write(" %sarg_%s,\n" % (a.ctype_in, a.name)) + self.outfile.write( + " %s *object)\n" + "{\n" + % (i.camel_name) + ) + if len(s.args) > 0: + self.outfile.write( + " %s%sSignal *signal = NULL;\n" + % (i.camel_name, s.name) + ) + self.outfile.write( + " DexFuture *future = NULL;\n" + "\n" + " if (self->%s_channel == NULL || !dex_channel_can_send (self->%s_channel))\n" + " return;\n" + "\n" + % (s.name_lower, s.name_lower) + ) + if len(s.args) > 0: + self.outfile.write( + " signal = g_new0 (%s%sSignal, 1);\n" + % (i.camel_name, s.name) + ) + for a in s.args: + if a.copy_func: + self.outfile.write( + " signal->%s = arg_%s ? %s (arg_%s) : NULL;\n" + % (a.name, a.name, a.copy_func, a.name) + ) + else: + self.outfile.write(" signal->%s = arg_%s;\n" % (a.name, a.name)) + if len(s.args) > 0: + self.outfile.write( + " future = dex_future_new_take_boxed (%sTYPE_%s_%s_SIGNAL,\n" + " g_steal_pointer (&signal));\n" + "\n" + % (i.ns_upper, i.name_upper, s.name_upper) + ) + else: + self.outfile.write( + " future = dex_future_new_true ();\n" + "\n" + ) + self.outfile.write( + " dex_future_disown (dex_channel_send (self->%s_channel, g_steal_pointer (&future)));\n" + "}\n\n" + % (s.name_lower) + ) + + self.outfile.write( + "static void\n" + "%s_signal_monitor_finalize (GObject *object)\n" + "{\n" + " %sSignalMonitor *self = %s%s_SIGNAL_MONITOR (object);\n" + "\n" + % (i.name_lower, i.camel_name, i.ns_upper, i.name_upper) + ) + for s in i.signals: + self.outfile.write( + " g_clear_signal_handler (&self->%s_signal_id, self->object);\n" + " if (self->%s_channel)\n" + " dex_channel_close_send (self->%s_channel);\n" + " dex_clear (&self->%s_channel);\n" + "\n" + % (s.name_lower, s.name_lower, s.name_lower, s.name_lower) + ) + self.outfile.write( + " g_clear_object (&self->object);\n" + " G_OBJECT_CLASS (%s_signal_monitor_parent_class)->finalize (object);\n" + "}\n\n" + % (i.name_lower) + ) + + self.outfile.write( + "static void\n" + "%s_signal_monitor_class_init (%sSignalMonitorClass *klass)\n" + "{\n" + " GObjectClass *object_class = G_OBJECT_CLASS (klass);\n" + "\n" + " object_class->finalize = %s_signal_monitor_finalize;\n" + "}\n\n" + % (i.name_lower, i.camel_name, i.name_lower) + ) + self.outfile.write( + "static void\n" + "%s_signal_monitor_init (%sSignalMonitor *self)\n" + "{\n" + "}\n\n" + % (i.name_lower, i.camel_name) + ) + self.outfile.write( + "%sSignalMonitor *\n" + "%s_signal_monitor_new (\n" + " %s *object,\n" + " %sSignals signals)\n" + "{\n" + " %sSignalMonitor *self = NULL;\n" + "\n" + " self = g_object_new (%sTYPE_%s_SIGNAL_MONITOR, NULL);\n" + " g_set_object (&self->object, object);\n" + % (i.camel_name, i.name_lower, i.camel_name, i.camel_name, i.camel_name, i.ns_upper, i.name_upper) + ) + for s in i.signals: + self.outfile.write( + " if (signals & %s%s_SIGNAL_%s)\n" + " {\n" + " self->%s_channel = dex_channel_new (0);\n" + " self->%s_signal_id =\n" + " g_signal_connect_swapped (self->object,\n" + " \"%s\",\n" + " G_CALLBACK (%s_signal_monitor_%s_cb),\n" + " self);\n" + " }\n" + % + ( + i.ns_upper, i.name_upper, s.name_upper, + s.name_lower, s.name_lower, + s.name_hyphen, + i.name_lower, s.name_lower + ) + ) + self.outfile.write( + " return g_steal_pointer (&self);\n" + "}\n\n" + ) + + self.outfile.write( + "void\n" + "%s_signal_monitor_cancel (%sSignalMonitor *self)\n" + "{\n" + " g_return_if_fail (%sIS_%s_SIGNAL_MONITOR (self));\n" + "\n" + % (i.name_lower, i.camel_name, i.ns_upper, i.name_upper) + ) + for s in i.signals: + self.outfile.write( + " g_clear_signal_handler (&self->%s_signal_id, self->object);\n" + " if (self->%s_channel)\n" + " dex_channel_close_send (self->%s_channel);\n" + % (s.name_lower, s.name_lower, s.name_lower) + ) + self.outfile.write( + "\n" + " g_clear_object (&self->object);\n" + "}\n\n" + ) + for s in i.signals: + self.outfile.write( + "DexFuture *\n" + "%s_signal_monitor_next_%s (%sSignalMonitor *self)\n" + "{\n" + " g_return_val_if_fail (%sIS_%s_SIGNAL_MONITOR (self), NULL);\n" + "\n" + " if (self->%s_channel != NULL)\n" + " return dex_channel_receive (self->%s_channel);\n" + "\n" + " return dex_future_new_reject (G_IO_ERROR,\n" + " G_IO_ERROR_CANCELLED,\n" + " \"Monitoring cancelled\");\n" + "}\n\n" + % (i.name_lower, s.name_lower, i.camel_name, i.ns_upper, i.name_upper, s.name_lower, s.name_lower) + ) + + def generate_body_preamble(self): + self.outfile.write( + "typedef struct\n" + "{\n" + " GDBusProxy *proxy;\n" + " DexPromise *promise;\n" + " gulong cancelled_handler_id;\n" + " guint signalled_handler_id;\n" + "} GDBusFutureSignalData;\n" + "\n" + "static void\n" + "gdbus_future_signal_data_free (GDBusFutureSignalData *data)\n" + "{\n" + " g_signal_handler_disconnect (data->proxy,\n" + " data->signalled_handler_id);\n" + " g_clear_object (&data->proxy);\n" + " g_clear_pointer (&data->promise, dex_unref);\n" + " free (data);\n" + "}\n" + "\n" + "G_GNUC_UNUSED static void\n" + "gdbus_future_signal_cancelled_cb (GCancellable *cancellable,\n" + " gpointer user_data)\n" + "{\n" + " GDBusFutureSignalData *data = user_data;\n" + " dex_promise_reject (data->promise,\n" + " g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED,\n" + " \"Cancelled\"));\n" + " gdbus_future_signal_data_free (data);\n" + "}\n" + ) + + def generate(self): + for i in self.ifaces: + self.generate_proxy(i) + for m in i.methods: + self.generate_method_call(i, m) + for s in i.signals: + self.generate_signal_wait(i, s) + self.generate_signal_monitor(i) diff --git a/src/meson.build b/src/meson.build index a46959e..df894e4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -77,6 +77,18 @@ libdex_deps = [ features_conf = configuration_data() features_conf.set10('HAVE_GDBUS_CODEGEN', have_gdbus_codegen) +pkgconfig_vars = [] + +if have_gdbus_codegen + codegen_path = '@0@/libdex-@1@/dex-gdbus-codegen-extension.py'.format(get_option('libexecdir'), api_version) + pkgconfig_vars += ['gdbus_codegen_extension=${prefix}/@0@'.format(codegen_path)] + + install_data( + sources: 'dex-gdbus-codegen-extension.py', + install_dir : get_option('libexecdir') / 'libdex-@0@'.format(api_version) + ) +endif + if host_machine.system() != 'darwin' and cc.get_argument_syntax() != 'msvc' libatomic_dep = cc.find_library('atomic') if not cc.links('int main(){}', dependencies: [libatomic_dep]) @@ -221,6 +233,7 @@ pkg.generate( filebase: 'libdex-' + api_version, subdirs: 'libdex-@0@'.format(api_version), requires: ['gio-2.0'], + variables: pkgconfig_vars, ) if get_option('introspection').enabled() -- GitLab From 4057d1fff421604fe6e06ed04745872e93e8f531 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Thu, 23 Oct 2025 00:50:11 +0200 Subject: [PATCH 06/10] testsuite/dbus: Add tests for dbus support --- testsuite/meson.build | 20 +- testsuite/org.example.Foo.xml | 26 + testsuite/test-dbus.c | 965 ++++++++++++++++++++++++++++++++++ 3 files changed, 1010 insertions(+), 1 deletion(-) create mode 100644 testsuite/org.example.Foo.xml create mode 100644 testsuite/test-dbus.c diff --git a/testsuite/meson.build b/testsuite/meson.build index ced0db5..2fc2aeb 100644 --- a/testsuite/meson.build +++ b/testsuite/meson.build @@ -16,9 +16,22 @@ testsuite_c_args = [ '-UG_DISABLE_CAST_CHECKS', ] +dbus_foo = [] +if have_gdbus_codegen + ext = meson.project_source_root() / 'src' / 'dex-gdbus-codegen-extension.py' + dbus_foo = gnome.gdbus_codegen( + 'dex-test-dbus-foo', + sources: ['org.example.Foo.xml'], + interface_prefix: 'org.example', + namespace: 'DexTestDbus', + extra_args: [f'--extension-path=@ext@'], + ) +endif + testsuite = { 'test-async-result': {}, 'test-channel': {}, + 'test-dbus': {'extra-sources': dbus_foo, 'disable': not have_gdbus_codegen}, 'test-object': {}, 'test-fiber': {}, 'test-future': {}, @@ -40,7 +53,12 @@ if get_option('sysprof') endif foreach test, params: testsuite - test_exe = executable(test, '@0@.c'.format(test), + if params.get('disable', false) + continue + endif + + test_exe = executable(test, + sources: ['@0@.c'.format(test)] + params.get('extra-sources', []), c_args: testsuite_c_args + deprecated_c_args, dependencies: testsuite_deps, ) diff --git a/testsuite/org.example.Foo.xml b/testsuite/org.example.Foo.xml new file mode 100644 index 0000000..3bfe258 --- /dev/null +++ b/testsuite/org.example.Foo.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testsuite/test-dbus.c b/testsuite/test-dbus.c new file mode 100644 index 0000000..e844ee0 --- /dev/null +++ b/testsuite/test-dbus.c @@ -0,0 +1,965 @@ +/* test-dbus.c + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include + +#include "dex-fiber-private.h" +#include "dex-test-dbus-foo.h" + +struct _DexTestFoo +{ + DexTestDbusFooSkeleton parent_instance; +}; + +struct _DexTestFooClass +{ + DexTestDbusFooSkeletonClass parent_class; +}; + +static void dex_test_dbus_foo_iface_init (DexTestDbusFooIface *iface); + +#define DEX_TEST_TYPE_FOO (dex_test_foo_get_type ()) +G_DECLARE_FINAL_TYPE (DexTestFoo, + dex_test_foo, + DEX_TEST, FOO, + DexTestDbusFooSkeleton) + +G_DEFINE_TYPE_WITH_CODE (DexTestFoo, + dex_test_foo, + DEX_TEST_DBUS_TYPE_FOO_SKELETON, + G_IMPLEMENT_INTERFACE (DEX_TEST_DBUS_TYPE_FOO, + dex_test_dbus_foo_iface_init)); + +static GDBusConnection * +get_new_session_connection_sync (void) +{ + GDBusConnection *connection; + char *session_address; + GError *error = NULL; + + session_address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (session_address); + + connection = g_dbus_connection_new_for_address_sync (session_address, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + g_free (session_address); + return connection; +} + +static gboolean +handle_foo (DexTestDbusFoo *object, + GDBusMethodInvocation *invocation) +{ + g_usleep (G_USEC_PER_SEC / 10); + + dex_test_dbus_foo_complete_foo (object, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_bar (DexTestDbusFoo *object, + GDBusMethodInvocation *invocation, + const char * const *i1, + uint32_t i2) +{ + GVariantBuilder builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + + g_usleep (G_USEC_PER_SEC / 10); + + g_variant_builder_add (&builder, "{sv}", "foo", g_variant_new_uint32 (3)); + g_variant_builder_add (&builder, "{sv}", "bar", g_variant_new_boolean (FALSE)); + + dex_test_dbus_foo_complete_bar (object, invocation, 42, g_variant_builder_end (&builder)); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_emit_foo_bar (DexTestDbusFoo *object, + GDBusMethodInvocation *invocation) +{ + dex_test_dbus_foo_emit_foo_bar (object); + dex_test_dbus_foo_complete_emit_foo_bar (object, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean +handle_fiber (DexTestDbusFoo *object, + GDBusMethodInvocation *invocation) +{ + dex_await (dex_timeout_new_msec (100), NULL); + dex_test_dbus_foo_complete_fiber (object, invocation); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +dex_test_dbus_foo_iface_init (DexTestDbusFooIface *iface) +{ + iface->handle_foo = handle_foo; + iface->handle_bar = handle_bar; + iface->handle_emit_foo_bar = handle_emit_foo_bar; + iface->handle_fiber = handle_fiber; +} + +static void +dex_test_foo_init (DexTestFoo *foo G_GNUC_UNUSED) +{ +} + +static void +dex_test_foo_class_init (DexTestFooClass *klass G_GNUC_UNUSED) +{ +} + +typedef struct _FooServiceData +{ + GMainLoop *main_loop; + DexTestFoo *foo; + guint own_name_id; + guint timeout_id; + gboolean acquired; +} FooServiceData; + +static gboolean +send_signal_cb (gpointer user_data) +{ + FooServiceData *data = user_data; + GVariantBuilder builder = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + + g_variant_builder_add (&builder, "{sv}", "foo", g_variant_new_uint32 (3)); + g_variant_builder_add (&builder, "{sv}", "bar", g_variant_new_boolean (FALSE)); + + dex_test_dbus_foo_emit_baz (DEX_TEST_DBUS_FOO (data->foo), 11, g_variant_builder_end (&builder)); + + return G_SOURCE_CONTINUE; +} + +static void +bus_name_acquired_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + FooServiceData *data = user_data; + DexTestFoo *foo = data->foo; + GError *error = NULL; + + g_assert_true (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (foo), + connection, + "/org/example/foo", + &error)); + g_assert_no_error (error); + + data->timeout_id = g_timeout_add (100, send_signal_cb, data); + + data->acquired = TRUE; +} + +static void +bus_name_lost_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + FooServiceData *data = user_data; + + g_main_loop_quit (data->main_loop); +} + +static void +foo_service_cancelled_cb (GCancellable *cancellable, + gpointer user_data) +{ + FooServiceData *data = user_data; + + g_clear_handle_id (&data->own_name_id, g_bus_unown_name); + g_main_loop_quit (data->main_loop); +} + +static void +run_foo_service_in_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + FooServiceData data; + GMainLoop *main_loop; + GDBusConnection *connection; + DexTestFoo *foo; + GError *error = NULL; + + main_loop = g_main_loop_new (g_main_context_new (), FALSE); + g_assert_nonnull (main_loop); + + foo = g_object_new (DEX_TEST_TYPE_FOO, NULL); + g_assert_nonnull (foo); + + data.main_loop = main_loop; + data.foo = foo; + data.acquired = FALSE; + data.own_name_id = 0; + data.timeout_id = 0; + + connection = get_new_session_connection_sync (); + g_assert_no_error (error); + g_assert_nonnull (connection); + + data.own_name_id = + g_bus_own_name_on_connection (connection, "org.example.Foo", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + bus_name_acquired_cb, + bus_name_lost_cb, + &data, NULL); + + g_cancellable_connect (cancellable, + G_CALLBACK (foo_service_cancelled_cb), + &data, NULL); + if (!g_cancellable_is_cancelled (cancellable)) + g_main_loop_run (main_loop); + + if (data.acquired) + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (data.foo)); + + g_clear_handle_id (&data.timeout_id, g_source_remove); + + g_main_loop_unref (main_loop); + g_clear_object (&foo); + g_clear_object (&connection); + + g_task_return_boolean (task, TRUE); +} + +static void +run_foo_service_cancelled_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + GTask *task = G_TASK (res); + GCancellable *cancellable; + + cancellable = g_task_get_task_data (task); + g_task_set_task_data (task, NULL, NULL); + g_clear_object (&cancellable); +} + +static GTask * +run_foo_service (void) +{ + GTask *task = NULL; + GCancellable *cancellable = g_cancellable_new (); + + task = g_task_new (NULL, cancellable, run_foo_service_cancelled_cb, NULL); + g_task_set_task_data (task, cancellable, NULL); + g_task_run_in_thread (task, run_foo_service_in_thread); + + return task; +} + +static void +stop_foo_service (GTask *task) +{ + GCancellable *cancellable; + + cancellable = g_task_get_task_data (task); + g_cancellable_cancel (cancellable); + + while (g_task_get_task_data (task) != NULL) + g_main_context_iteration (NULL, TRUE); + + g_clear_object (&task); +} + +static void +test_gdbus_proxy_create (void) +{ + GDBusConnection *connection; + DexFuture *future; + const GValue *value; + DexTestDbusFoo *proxy; + GError *error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + future = dex_test_dbus_foo_proxy_new_future (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo"); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_OBJECT (value)); + proxy = g_value_get_object (value); + g_assert_true (DEX_TEST_DBUS_IS_FOO (proxy)); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&connection); +} + +static void +test_gdbus_method_call_simple (void) +{ + GDBusConnection *connection; + DexFuture *future; + const GValue *value; + DexTestDbusFoo *proxy; + GError *error = NULL; + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + future = dex_test_dbus_foo_call_foo_future (proxy); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN); + g_assert_null (value); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); +} + +static void +test_gdbus_method_call_result (void) +{ + GDBusConnection *connection; + DexFuture *future; + const GValue *value; + DexTestDbusFoo *proxy; + GTask *foo_service; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + future = dex_test_dbus_foo_call_foo_future (proxy); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_value_get_boolean (value)); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_method_call_cancel (void) +{ + GDBusConnection *connection; + DexFuture *future; + DexPromise *promise; + const GValue *value; + DexTestDbusFoo *proxy; + GTask *foo_service; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + promise = dex_promise_new (); + future = dex_future_first (DEX_FUTURE (promise), + dex_test_dbus_foo_call_foo_future (proxy), + NULL); + + dex_promise_reject (promise, g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled")); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_null (value); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_method_call_complex (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexFuture *future; + const GValue *value; + DexTestDbusFooBarResult *result; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + future = dex_test_dbus_foo_call_bar_future (proxy, + (const gchar * []) { + "foo", + "bar", + NULL, + }, + 42); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOXED (value)); + result = g_value_get_boxed (value); + + { + uint32_t foo; + gboolean bar; + + g_assert_nonnull (result); + g_assert_cmpint (result->o1, == , 42); + + g_assert_nonnull (result->o2); + g_assert_true (g_variant_lookup (result->o2, "foo", "u", &foo)); + g_assert_cmpint (foo, == , 3); + g_assert_true (g_variant_lookup (result->o2, "bar", "b", &bar)); + g_assert_true (!bar); + } + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_signal_wait_simple (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexFuture *future; + const GValue *value; + DexTestDbusFooBazSignal *result; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + future = dex_test_dbus_foo_wait_baz_future (proxy); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOXED (value)); + result = g_value_get_boxed (value); + + { + uint32_t foo; + gboolean bar; + + g_assert_nonnull (result); + g_assert_cmpint (result->s1, == , 11); + + g_assert_nonnull (result->s2); + g_assert_true (g_variant_lookup (result->s2, "foo", "u", &foo)); + g_assert_cmpint (foo, == , 3); + g_assert_true (g_variant_lookup (result->s2, "bar", "b", &bar)); + g_assert_true (!bar); + } + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_signal_wait_cancel (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexPromise *promise; + DexFuture *future; + const GValue *value; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + promise = dex_promise_new (); + future = dex_future_first (DEX_FUTURE (promise), + dex_test_dbus_foo_wait_baz_future (proxy), + NULL); + + dex_promise_reject (promise, g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled")); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_null (value); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_signal_monitor_basic (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexFuture *future; + const GValue *value; + DexTestDbusFooSignalMonitor *signal_monitor; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + signal_monitor = dex_test_dbus_foo_signal_monitor_new (proxy, DEX_TEST_DBUS_FOO_SIGNAL_FOO_BAR); + + future = dex_test_dbus_foo_call_emit_foo_bar_future (proxy); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + dex_clear (&future); + + future = dex_test_dbus_foo_signal_monitor_next_foo_bar (signal_monitor); + g_assert_true (dex_future_get_status (future) == DEX_FUTURE_STATUS_RESOLVED); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_value_get_boolean (value)); + + dex_clear (&future); + future = dex_test_dbus_foo_signal_monitor_next_foo_bar (signal_monitor); + g_assert_true (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&signal_monitor); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static void +test_gdbus_signal_monitor_cancel (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexFuture *future; + DexPromise *promise; + const GValue *value; + DexTestDbusFooSignalMonitor *signal_monitor; + GError *error = NULL; + + foo_service = run_foo_service (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + signal_monitor = dex_test_dbus_foo_signal_monitor_new (proxy, DEX_TEST_DBUS_FOO_SIGNAL_FOO_BAR); + + promise = dex_promise_new (); + future = dex_future_first (DEX_FUTURE (promise), + dex_test_dbus_foo_signal_monitor_next_foo_bar (signal_monitor), + NULL); + + dex_promise_reject (promise, g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled")); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert_null (value); + + g_clear_error (&error); + dex_clear (&future); + g_clear_object (&signal_monitor); + g_clear_object (&proxy); + g_clear_object (&connection); + stop_foo_service (foo_service); +} + +static DexFuture * +fiber_basic (gpointer user_data) +{ + GDBusConnection *connection = NULL; + DexTestDbusFoo *proxy = NULL; + GError *error = NULL; + + connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SESSION), &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_await_object (dex_test_dbus_foo_proxy_new_future (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo"), + &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + { + gboolean foo_result; + + foo_result = dex_await_boolean (dex_test_dbus_foo_call_foo_future (proxy), &error); + g_assert_no_error (error); + g_assert_true (foo_result); + } + + { + DexTestDbusFooBazSignal *baz_result; + uint32_t foo; + gboolean bar; + + baz_result = dex_await_boxed (dex_test_dbus_foo_wait_baz_future (proxy), &error); + g_assert_no_error (error); + g_assert_nonnull (baz_result); + + g_assert_cmpint (baz_result->s1, == , 11); + g_assert_nonnull (baz_result->s2); + g_assert_true (g_variant_lookup (baz_result->s2, "foo", "u", &foo)); + g_assert_cmpint (foo, == , 3); + g_assert_true (g_variant_lookup (baz_result->s2, "bar", "b", &bar)); + g_assert_true (!bar); + + g_clear_pointer (&baz_result, dex_test_dbus_foo_baz_signal_free); + } + + { + DexTestDbusFooSignalMonitor *signal_monitor; + gboolean result; + + signal_monitor = dex_test_dbus_foo_signal_monitor_new (proxy, DEX_TEST_DBUS_FOO_SIGNAL_FOO_BAR); + + result = dex_await_boolean (dex_test_dbus_foo_call_emit_foo_bar_future (proxy), &error); + g_assert_no_error (error); + g_assert_true (result); + + result = dex_await_boolean (dex_test_dbus_foo_signal_monitor_next_foo_bar (signal_monitor), &error); + g_assert_no_error (error); + g_assert_true (result); + + g_clear_object (&signal_monitor); + } + + return dex_future_new_true (); +} + +static void +test_gdbus_fiber_basic (void) +{ + GTask *foo_service; + DexFuture *future; + const GValue *value; + GError *error = NULL; + + foo_service = run_foo_service (); + + future = dex_scheduler_spawn (NULL, 0, fiber_basic, NULL, NULL); + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_value_get_boolean (value)); + + g_clear_error (&error); + dex_clear (&future); + stop_foo_service (foo_service); +} + +static DexFuture * +fiber_service (gpointer user_data) +{ + GDBusConnection *connection = NULL; + DexTestFoo *foo = NULL; + DexFuture *name_acquired = NULL; + DexFuture *name_lost = NULL; + GError *error = NULL; + + connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SESSION), &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + dex_bus_own_name_on_connection (connection, + "org.example.Foo", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + &name_acquired, + &name_lost); + + dex_await (g_steal_pointer (&name_acquired), &error); + g_assert_no_error (error); + + foo = g_object_new (DEX_TEST_TYPE_FOO, NULL); + dex_dbus_interface_skeleton_set_flags (DEX_DBUS_INTERFACE_SKELETON (foo), + DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER); + + g_assert_true (g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (foo), + connection, + "/org/example/foo", + &error)); + g_assert_no_error (error); + + dex_await (g_steal_pointer (&name_lost), &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (foo)); + dex_dbus_interface_skeleton_cancel (DEX_DBUS_INTERFACE_SKELETON (foo)); + + return dex_future_new_true (); +} + +static void +call_fiber_cb (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + gboolean *done = data; + GError *error = NULL; + + g_assert_true (dex_test_dbus_foo_call_fiber_finish (DEX_TEST_DBUS_FOO (source_object), res, &error)); + g_assert_no_error (error); + + *done = TRUE; +} + +static void +test_gdbus_fiber_service (void) +{ + GTask *foo_service; + GDBusConnection *connection; + DexTestDbusFoo *proxy; + DexFuture *future; + const GValue *value; + GError *error = NULL; + + future = dex_scheduler_spawn (NULL, 0, fiber_service, NULL, NULL); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (connection); + + proxy = dex_test_dbus_foo_proxy_new_sync (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.Foo", + "/org/example/foo", + NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (proxy); + + while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + g_main_context_iteration (NULL, TRUE); + + { + gboolean done = FALSE; + dex_test_dbus_foo_call_fiber (proxy, NULL, call_fiber_cb, &done); + + while (!done) + g_main_context_iteration (NULL, TRUE); + } + + /* kick the fiber service off the bus */ + foo_service = run_foo_service (); + + while (dex_future_get_status (future) == DEX_FUTURE_STATUS_PENDING) + g_main_context_iteration (NULL, TRUE); + + value = dex_future_get_value (future, &error); + g_assert_no_error (error); + g_assert_nonnull (value); + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_value_get_boolean (value)); + + g_clear_error (&error); + dex_clear (&future); + stop_foo_service (foo_service); +} + +int +main (int argc, + char *argv[]) +{ + dex_init (); + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/Dex/TestSuite/GDBus/proxy/create", test_gdbus_proxy_create); + g_test_add_func ("/Dex/TestSuite/GDBus/method_call/simple", test_gdbus_method_call_simple); + g_test_add_func ("/Dex/TestSuite/GDBus/method_call/result", test_gdbus_method_call_result); + g_test_add_func ("/Dex/TestSuite/GDBus/method_call/cancel", test_gdbus_method_call_cancel); + g_test_add_func ("/Dex/TestSuite/GDBus/method_call/complex", test_gdbus_method_call_complex); + g_test_add_func ("/Dex/TestSuite/GDBus/signal_wait/simple", test_gdbus_signal_wait_simple); + g_test_add_func ("/Dex/TestSuite/GDBus/signal_wait/cancel", test_gdbus_signal_wait_cancel); + g_test_add_func ("/Dex/TestSuite/GDBus/signal_monitor/basic", test_gdbus_signal_monitor_basic); + g_test_add_func ("/Dex/TestSuite/GDBus/signal_monitor/cancel", test_gdbus_signal_monitor_cancel); + g_test_add_func ("/Dex/TestSuite/GDBus/fiber/basic", test_gdbus_fiber_basic); + g_test_add_func ("/Dex/TestSuite/GDBus/fiber/service", test_gdbus_fiber_service); + return g_test_run (); +} -- GitLab From 1e9743ae542e6ab8ac7f13f4a01153646e37829b Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 17 Sep 2025 22:34:36 +0200 Subject: [PATCH 07/10] examples: add a dbus example --- examples/dbus.c | 283 ++++++++++++++++++++++++++++++ examples/meson.build | 20 ++- examples/org.example.PingPong.xml | 21 +++ 3 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 examples/dbus.c create mode 100644 examples/org.example.PingPong.xml diff --git a/examples/dbus.c b/examples/dbus.c new file mode 100644 index 0000000..57d94fe --- /dev/null +++ b/examples/dbus.c @@ -0,0 +1,283 @@ +/* + * dbus.c + * + * Copyright 2025 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + + +#include "dex-dbus-ping-pong.h" + +struct _DexPingPong +{ + DexDbusPingPongSkeleton parent_instance; +}; + +struct DexPingPongClass +{ + DexDbusPingPongSkeletonClass parent_class; +}; + +static void dex_dbus_ping_pong_iface_init (DexDbusPingPongIface *iface); + +#define DEX_TYPE_PING_PONG (dex_ping_pong_get_type ()) +G_DECLARE_FINAL_TYPE (DexPingPong, + dex_ping_pong, + DEX, PING_PONG, + DexDbusPingPongSkeleton) + +G_DEFINE_TYPE_WITH_CODE (DexPingPong, + dex_ping_pong, + DEX_DBUS_TYPE_PING_PONG_SKELETON, + G_IMPLEMENT_INTERFACE (DEX_DBUS_TYPE_PING_PONG, + dex_dbus_ping_pong_iface_init)); + +static gboolean +handle_ping (DexDbusPingPong *object, + GDBusMethodInvocation *invocation, + const char *ping) +{ + g_print ("service: %s\n", ping); + + dex_await (dex_timeout_new_seconds (1), NULL); + dex_dbus_ping_pong_complete_ping (object, invocation, "pong"); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +dex_dbus_ping_pong_iface_init (DexDbusPingPongIface *iface) +{ + iface->handle_ping = handle_ping; +} + +static void +dex_ping_pong_init (DexPingPong *pp G_GNUC_UNUSED) +{ +} + +static void +dex_ping_pong_class_init (DexPingPongClass *klass G_GNUC_UNUSED) +{ +} + +static DexFuture * +emit_reloading_signals_fiber (gpointer user_data) +{ + DexDbusPingPong *pp = DEX_DBUS_PING_PONG (user_data); + + while (TRUE) + { + g_auto(GVariantBuilder) builder = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr(GError) error = NULL; + + g_variant_builder_add (&builder, "{sv}", "test", + g_variant_new_int32 (g_random_int_range (0, 100))); + + dex_dbus_ping_pong_emit_reloading (pp, TRUE, g_variant_builder_end (&builder)); + + if (!dex_await (dex_timeout_new_seconds (g_random_int_range (1, 4)), &error) && + !g_error_matches (error, DEX_ERROR, DEX_ERROR_TIMED_OUT)) + return dex_future_new_for_error (g_steal_pointer (&error)); + } + g_assert_not_reached (); +} + +static DexFuture * +run_service_fiber (gpointer user_data G_GNUC_UNUSED) +{ + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(DexFuture) name_acquired = NULL; + g_autoptr(DexFuture) name_lost = NULL; + g_autoptr(DexPingPong) pp = NULL; + g_autoptr(GError) error = NULL; + + connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SESSION), &error); + if (!connection) + return dex_future_new_for_error (g_steal_pointer (&error)); + + dex_bus_own_name_on_connection (connection, + "org.example.PingPong", + G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT | + G_BUS_NAME_OWNER_FLAGS_REPLACE, + &name_acquired, + &name_lost); + + if (!dex_await (g_steal_pointer (&name_acquired), &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + pp = g_object_new (DEX_TYPE_PING_PONG, NULL); + dex_dbus_interface_skeleton_set_flags (DEX_DBUS_INTERFACE_SKELETON (pp), + DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER); + + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (pp), + connection, + "/org/example/pingpong", + &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + + + + dex_await (dex_future_first (g_steal_pointer (&name_lost), + dex_scheduler_spawn (NULL, 0, + emit_reloading_signals_fiber, + g_object_ref (pp), + g_object_unref), + NULL), + NULL); + + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (pp)); + dex_dbus_interface_skeleton_cancel (DEX_DBUS_INTERFACE_SKELETON (pp)); + + return dex_future_new_true (); +} + +static DexFuture * +ping_fiber (gpointer user_data) +{ + g_autoptr(DexDbusPingPong) pp = g_object_ref (user_data); + + while (TRUE) + { + g_autoptr(DexDbusPingPongPingResult) res = NULL; + g_autoptr(GError) error = NULL; + + dex_await (dex_timeout_new_seconds (1), NULL); + + res = dex_await_boxed (dex_dbus_ping_pong_call_ping_future (pp, "ping"), &error); + if (!res) + return dex_future_new_for_error (g_steal_pointer (&error)); + + g_print ("client: %s\n", res->pong); + } + g_assert_not_reached (); +} + +static DexFuture * +wait_for_reload_fiber (gpointer user_data) +{ + g_autoptr(DexDbusPingPong) pp = user_data; + g_autoptr(DexDbusPingPongSignalMonitor) signal_monitor = + dex_dbus_ping_pong_signal_monitor_new (pp, DEX_DBUS_PING_PONG_SIGNAL_RELOADING); + + while (TRUE) + { + g_autoptr(DexDbusPingPongReloadingSignal) reloading = NULL; + g_autoptr(GError) error = NULL; + int test; + + reloading = dex_await_boxed (dex_future_first (dex_dbus_ping_pong_signal_monitor_next_reloading (signal_monitor), + dex_timeout_new_seconds (10), + NULL), + &error); + if (!reloading) + return dex_future_new_for_error (g_steal_pointer (&error)); + + g_variant_lookup (reloading->options, "test", "i", &test); + g_print ("signal: received reloading, active = %d, options[test] = %d\n", + reloading->active, + test); + } + g_assert_not_reached (); +} + +static DexFuture * +run_client_fiber (gpointer user_data G_GNUC_UNUSED) +{ + g_autoptr(DexDbusPingPong) pp = NULL; + + { + g_autoptr(GDBusConnection) connection = NULL; + g_autoptr(GError) error = NULL; + + connection = dex_await_object (dex_bus_get (G_BUS_TYPE_SESSION), &error); + if (!connection) + return dex_future_new_for_error (g_steal_pointer (&error)); + + pp = dex_await_object (dex_dbus_ping_pong_proxy_new_future (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.PingPong", + "/org/example/pingpong"), + &error); + if (!pp) + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + { + g_autoptr(GError) error = NULL; + + if (!dex_await (dex_future_all (dex_scheduler_spawn (NULL, 0, + ping_fiber, + pp, NULL), + dex_scheduler_spawn (NULL, 0, + wait_for_reload_fiber, + pp, NULL), + NULL), + &error)) + return dex_future_new_for_error (g_steal_pointer (&error)); + } + + return dex_future_new_true (); +} + +static DexFuture * +quit_cb (DexFuture *future, + gpointer user_data) +{ + GMainLoop *main_loop = user_data; + g_autoptr (GError) error = NULL; + + if (!dex_await (dex_ref (future), &error)) + g_printerr ("%s\n", error->message); + + while (g_main_context_iteration (g_main_loop_get_context (main_loop), FALSE)); + g_main_loop_quit (main_loop); + + return NULL; +} + +int +main (int argc G_GNUC_UNUSED, + char **argv G_GNUC_UNUSED) +{ + g_autoptr(GMainLoop) main_loop = NULL; + g_autoptr(DexScheduler) thread_pool = NULL; + g_autoptr(DexFuture) future = NULL; + + dex_init (); + + main_loop = g_main_loop_new (NULL, FALSE); + thread_pool = dex_thread_pool_scheduler_new (); + + future = dex_future_first (dex_scheduler_spawn (NULL, 0, + run_service_fiber, + NULL, NULL), + dex_scheduler_spawn (NULL, 0, + run_client_fiber, + NULL, NULL), + dex_unix_signal_new (SIGINT), + NULL); + future = dex_future_finally (future, quit_cb, main_loop, NULL); + + g_main_loop_run (main_loop); + + return EXIT_SUCCESS; +} diff --git a/examples/meson.build b/examples/meson.build index f93e056..0227bea 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,10 +1,23 @@ libgio_unix_dep = dependency('gio-unix-2.0', required: false, disabler: true) libsoup_dep = dependency('libsoup-3.0', required: false, disabler: true) +dbus_ping_pong = [] +if have_gdbus_codegen + ext = meson.project_source_root() / 'src' / 'dex-gdbus-codegen-extension.py' + dbus_ping_pong = gnome.gdbus_codegen( + 'dex-dbus-ping-pong', + sources: ['org.example.PingPong.xml'], + interface_prefix: 'org.example', + namespace: 'DexDbus', + extra_args: [f'--extension-path=@ext@'], + ) +endif + examples = { 'cat': {'dependencies': libgio_unix_dep}, 'cat-aio': {}, 'cp': {}, + 'dbus': {'extra-sources': dbus_ping_pong, 'disable': not have_gdbus_codegen}, 'echo-bench': {}, 'host': {}, 'httpd': {'dependencies': libsoup_dep}, @@ -14,7 +27,12 @@ examples = { } foreach example, params: examples - example_exe = executable(example, '@0@.c'.format(example), + if params.get('disable', false) + continue + endif + + example_exe = executable(example, + sources: ['@0@.c'.format(example)] + params.get('extra-sources', []), c_args: deprecated_c_args, dependencies: [libdex_static_dep, params.get('dependencies', [])], install: false, diff --git a/examples/org.example.PingPong.xml b/examples/org.example.PingPong.xml new file mode 100644 index 0000000..a8527ed --- /dev/null +++ b/examples/org.example.PingPong.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + -- GitLab From cb107c7b02edbb42370e9f09b3e89554c87002ee Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Wed, 17 Sep 2025 23:46:43 +0200 Subject: [PATCH 08/10] docs: add section on d-bus --- docs/Dex.toml.in | 1 + docs/aio.md | 6 +-- docs/dbus.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 docs/dbus.md diff --git a/docs/Dex.toml.in b/docs/Dex.toml.in index c6aa838..3703afe 100644 --- a/docs/Dex.toml.in +++ b/docs/Dex.toml.in @@ -49,6 +49,7 @@ content_files = [ "schedulers.md", "threads.md", "channels.md", + "dbus.md", "debugging.md", ] diff --git a/docs/aio.md b/docs/aio.md index 052228f..205b4f6 100644 --- a/docs/aio.md +++ b/docs/aio.md @@ -55,11 +55,7 @@ The [func@Dex.socket_listener_accept], [func@Dex.socket_client_connect], and [fu # D-Bus -Light integration exists for D-Bus to perform asynchronous method calls. - -See [func@Dex.dbus_connection_call], [func@Dex.dbus_connection_call_with_unix_fd_list], [func@Dex.dbus_connection_send_message_with_reply] and [func@Dex.dbus_connection_close]. - -We expect additional support for D-Bus to come at a later time. +[See the page about D-Bus](dbus.html) # Subprocesses diff --git a/docs/dbus.md b/docs/dbus.md new file mode 100644 index 0000000..ea53830 --- /dev/null +++ b/docs/dbus.md @@ -0,0 +1,105 @@ +Title: D-Bus + +By default, integration exists for D-Bus to connect to a bus, own a name, and perform method calls, asynchronously. + +See [func@Dex.bus_get], [func@Dex.bus_own_name_on_connection], [func@Dex.dbus_connection_call], [func@Dex.dbus_connection_call_with_unix_fd_list], [func@Dex.dbus_connection_send_message_with_reply] and [func@Dex.dbus_connection_close]. + +# Enabling GDBus codegen + +Integration with GDBus based codegen can be enabled, by calling `gdbus-codegen` with `--extension-path=path/to/dex-gdbus-codegen-extension.py`. +The installed file path can be queried via `pkgconf --variable=gdbus_codegen_extension libdex-1`: + +```meson +libdex_dep = dependency('libdex-1') + +ext = libdex_dep.get_pkgconfig_variable('gdbus_codegen_extension') +dbus_ping_pong = gnome.gdbus_codegen( + 'dex-dbus-ping-pong', + sources: ['org.example.PingPong.xml'], + interface_prefix: 'org.example', + namespace: 'DexDbus', + extra_args: [f'--extension-path=@ext@'], +) +``` + +# Proxy Futures + +With the GDBus codegen enabled, for every proxy, a `${name}_new_future` function is generated in addition to the sync and async/finish variants. The returned future resolves into the proxy object. + +```c + g_autoptr(DexDbusPingPong) *pp = NULL; + pp = dex_await_object (dex_dbus_ping_pong_proxy_new_future (connection, + G_DBUS_PROXY_FLAGS_NONE, + "org.example.PingPong", + "/org/example/pingpong"), + &error); +``` + +For every method, a `${name}_call_${method}_future` function is generated in addition to the sync and async/finish variants. The returned future resolves into the boxed type `${Name}${Method}Result` which contains the results of the call. + +```c + g_autoptr(DexDbusPingPongPingResult) result = NULL; + + result = dex_await_boxed (dex_dbus_ping_pong_call_ping_future (pp, "ping"), &error); + g_assert (result); + g_print ("%s\n", result->pong); +``` + +For every signal, a `${name}_wait_${signal}_future` function is generated. The future gets resolved when the signal got emitted, and the future resolves into the boxed type `${Name}${Signal}Signal` which contains the results of the signal emission. + +```c + g_autoptr(DexDbusPingPongReloadingSignal) signal = NULL; + + signal = dex_await_boxed (dex_dbus_ping_pong_wait_reloading_future (pp), &error); + g_assert (signal); + g_print ("%s\n", signal->active); +``` + +For every `Dex.DBusInterfaceSkeleton`, a corresponding `${Name}SignalMonitor` class is generated. +Objects of the class will listen to the specified signals, and a call to ``${Name}SignalMonitor::next${signal}` returns a future that will resolve either immediately when a signal was emitted previously, or when the next signal arrives. +This can be useful when it is important to not miss signal emissions. + +```c + g_autoptr(DexDbusPingPongSignalMonitor) signal_monitor = + dex_dbus_ping_pong_signal_monitor_new (pp, DEX_DBUS_PING_PONG_SIGNAL_RELOADING); + + signal = dex_await_boxed (dex_dbus_ping_pong_signal_monitor_next_reloading (signal_monitor), &error); + g_assert (signal); + g_print ("%s\n", signal->active); +``` + +# InterfaceSkeleton Fiber Dispatching + +With the GDBus codegen enabled, all generated `${Name}Skeleton`s that application code derives from to implement a service derive from `DexDBusInterfaceSkeleton` instead of directly from `GDBusInterfaceSkeleton`. +This allows application code to enable handling method invocations in fibers, by setting [flags@Dex.DBusInterfaceSkeletonFlags.HANDLE_METHOD_INVOCATIONS_IN_FIBER] with [method@Dex.DBusInterfaceSkeleton.set_flags]. + +```c +static gboolean +handle_ping (DexDbusPingPong *object, + GDBusMethodInvocation *invocation, + const char *ping) +{ + g_print ("service: %s\n", ping); + + dex_await (dex_timeout_new_seconds (1), NULL); + dex_dbus_ping_pong_complete_ping (object, invocation, "pong"); + + return G_DBUS_METHOD_INVOCATION_HANDLED; +} + +static void +dex_dbus_ping_pong_iface_init (DexDbusPingPongIface *iface) +{ + iface->handle_ping = handle_ping; +} + +static DexPingPong * +get_ping_pong (void) +{ + g_autoptr(DexPingPong) pp = NULL; + pp = g_object_new (DEX_TYPE_PING_PONG, NULL); + dex_dbus_interface_skeleton_set_flags (DEX_DBUS_INTERFACE_SKELETON (pp), + DEX_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_FIBER); + return g_steal_pointer (&pp); +} +``` -- GitLab From 8740b58a5acfe7bf251ddd5276e593631cb4a627 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 9 Jan 2026 09:29:54 -0800 Subject: [PATCH 09/10] testsuite/dbus: fix leaks in test-dbus.c --- testsuite/test-dbus.c | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/testsuite/test-dbus.c b/testsuite/test-dbus.c index e844ee0..9ee53b9 100644 --- a/testsuite/test-dbus.c +++ b/testsuite/test-dbus.c @@ -211,11 +211,13 @@ run_foo_service_in_thread (GTask *task, { FooServiceData data; GMainLoop *main_loop; + GMainContext *main_context; GDBusConnection *connection; DexTestFoo *foo; GError *error = NULL; - main_loop = g_main_loop_new (g_main_context_new (), FALSE); + main_context = g_main_context_new (); + main_loop = g_main_loop_new (main_context, FALSE); g_assert_nonnull (main_loop); foo = g_object_new (DEX_TEST_TYPE_FOO, NULL); @@ -251,6 +253,7 @@ run_foo_service_in_thread (GTask *task, g_clear_handle_id (&data.timeout_id, g_source_remove); g_main_loop_unref (main_loop); + g_main_context_unref (main_context); g_clear_object (&foo); g_clear_object (&connection); @@ -375,6 +378,7 @@ test_gdbus_method_call_result (void) DexTestDbusFoo *proxy; GTask *foo_service; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -390,8 +394,9 @@ test_gdbus_method_call_result (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); future = dex_test_dbus_foo_call_foo_future (proxy); @@ -421,6 +426,7 @@ test_gdbus_method_call_cancel (void) DexTestDbusFoo *proxy; GTask *foo_service; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -436,8 +442,9 @@ test_gdbus_method_call_cancel (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); promise = dex_promise_new (); future = dex_future_first (DEX_FUTURE (promise), @@ -470,6 +477,7 @@ test_gdbus_method_call_complex (void) const GValue *value; DexTestDbusFooBarResult *result; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -485,8 +493,9 @@ test_gdbus_method_call_complex (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); future = dex_test_dbus_foo_call_bar_future (proxy, (const gchar * []) { @@ -536,6 +545,7 @@ test_gdbus_signal_wait_simple (void) const GValue *value; DexTestDbusFooBazSignal *result; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -551,8 +561,9 @@ test_gdbus_signal_wait_simple (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); future = dex_test_dbus_foo_wait_baz_future (proxy); @@ -596,6 +607,7 @@ test_gdbus_signal_wait_cancel (void) DexFuture *future; const GValue *value; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -611,8 +623,9 @@ test_gdbus_signal_wait_cancel (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); promise = dex_promise_new (); future = dex_future_first (DEX_FUTURE (promise), @@ -645,6 +658,7 @@ test_gdbus_signal_monitor_basic (void) const GValue *value; DexTestDbusFooSignalMonitor *signal_monitor; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -660,8 +674,9 @@ test_gdbus_signal_monitor_basic (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); signal_monitor = dex_test_dbus_foo_signal_monitor_new (proxy, DEX_TEST_DBUS_FOO_SIGNAL_FOO_BAR); @@ -703,6 +718,7 @@ test_gdbus_signal_monitor_cancel (void) const GValue *value; DexTestDbusFooSignalMonitor *signal_monitor; GError *error = NULL; + char *name; foo_service = run_foo_service (); @@ -718,8 +734,9 @@ test_gdbus_signal_monitor_cancel (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); signal_monitor = dex_test_dbus_foo_signal_monitor_new (proxy, DEX_TEST_DBUS_FOO_SIGNAL_FOO_BAR); @@ -901,6 +918,7 @@ test_gdbus_fiber_service (void) DexFuture *future; const GValue *value; GError *error = NULL; + char *name; future = dex_scheduler_spawn (NULL, 0, fiber_service, NULL, NULL); @@ -916,8 +934,9 @@ test_gdbus_fiber_service (void) g_assert_no_error (error); g_assert_nonnull (proxy); - while (g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)) == NULL) + while (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy)))) g_main_context_iteration (NULL, TRUE); + g_free (name); { gboolean done = FALSE; -- GitLab From ca35975917aa5da0189454b864a3a183e42050d7 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 9 Jan 2026 09:30:36 -0800 Subject: [PATCH 10/10] gdbus: release promise from GAsyncReadyCallbacks --- src/dex-gdbus-codegen-extension.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dex-gdbus-codegen-extension.py b/src/dex-gdbus-codegen-extension.py index 495018e..7576000 100644 --- a/src/dex-gdbus-codegen-extension.py +++ b/src/dex-gdbus-codegen-extension.py @@ -283,6 +283,7 @@ class CodeGenerator: " dex_promise_reject (promise, error);\n" " else\n" " dex_promise_resolve_object (promise, proxy);\n" + " dex_unref (promise);\n" "}\n" "\n" % (i.name_lower, i.camel_name, i.name_lower) ) @@ -420,7 +421,11 @@ class CodeGenerator: " &error);\n" " G_GNUC_END_IGNORE_DEPRECATIONS\n" " if (!success)\n" - " return dex_promise_reject (promise, error);\n" + " {\n" + " dex_promise_reject (promise, error);\n" + " dex_unref (promise);\n" + " return;\n" + " }\n" ) if len(m.out_args) > 0: self.outfile.write(" r = g_new0 (%s, 1);\n" % result_type) @@ -433,11 +438,14 @@ class CodeGenerator: " promise,\n" " %s,\n" " r);\n" + " dex_unref (promise);\n" "}\n\n" % result_type_name ) else: self.outfile.write( - " dex_promise_resolve_boolean (promise, TRUE);\n" "}\n\n" + " dex_promise_resolve_boolean (promise, TRUE);\n" + " dex_unref (promise);\n" + "}\n\n" ) self.outfile.write( -- GitLab