diff --git a/.gitignore b/.gitignore
index 2a1fb9417f7d982d4f67bf06452944690bc254bb..4b49892d2532480d1e6d0bc6786398758d8e35ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*~
build/
.buildconfig
+__pycache__
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 223bd93cfa5dee1ef37dd736483ae8873878a962..160103c23b977afa2ce57207aca6ace91e81598f 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
diff --git a/docs/Dex.toml.in b/docs/Dex.toml.in
index c6aa83838ec3938c71819f8e5b1d34a0a0e22b96..3703afe53558282a96c1ad5151c17bf149fc0e94 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 052228f0d4fe68a3b612682293a3b6fb391bcf31..205b4f60a885f620b26a2850fcac81606c6ff683 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 0000000000000000000000000000000000000000..ea53830af90dcbc4aa1f21d23e25a5cdbb9dd548
--- /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);
+}
+```
diff --git a/examples/dbus.c b/examples/dbus.c
new file mode 100644
index 0000000000000000000000000000000000000000..57d94fe372a88e444106af0bd1457b046ac2c435
--- /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 f93e0565e78c9fcb139546d18ae762d44362715b..0227bea47ee028d62d6f1704678cde9f2cf5b95b 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 0000000000000000000000000000000000000000..a8527ed522d3dac5b3afad2a1ff02255c8c3c93a
--- /dev/null
+++ b/examples/org.example.PingPong.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/meson.build b/meson.build
index 67201b9272431d57e4197d4ff07f1b68ade5a602..466050d04cdaf4f8c7538b5d6816d612e20349d0 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 b8c171cea3cae1ea3e454bdeff80076b8fd078c7..01ea56c4ad2f53dc5144f6da84a3eb2ed86e3c42 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 0000000000000000000000000000000000000000..79755f4e0a79823d646c74318d6eaccee64d244f
--- /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-codegen-extension.py b/src/dex-gdbus-codegen-extension.py
new file mode 100644
index 0000000000000000000000000000000000000000..75760000d93a2dcff2dca1c484ad70d5438c7281
--- /dev/null
+++ b/src/dex-gdbus-codegen-extension.py
@@ -0,0 +1,916 @@
+# 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"
+ " dex_unref (promise);\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"
+ " {\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)
+ 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"
+ " dex_unref (promise);\n"
+ "}\n\n" % result_type_name
+ )
+ else:
+ self.outfile.write(
+ " dex_promise_resolve_boolean (promise, TRUE);\n"
+ " dex_unref (promise);\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/dex-gdbus.c b/src/dex-gdbus.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d3279cc8e7ffe71ceda0043440c1da0b0f866d8
--- /dev/null
+++ b/src/dex-gdbus.c
@@ -0,0 +1,762 @@
+/*
+ * 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-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)
+{
+ 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 name_acquired_cancelled_id;
+ DexPromise *name_lost;
+ gulong name_lost_cancelled_id;
+ guint own_name_id;
+} BusOwnNameData;
+
+static void
+dex_bus_own_name_data_free (BusOwnNameData *data)
+{
+ 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;
+
+ 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);
+}
+
+static void
+dex_bus_name_lost_cb (GDBusConnection *connection,
+ const char *name,
+ gpointer user_data)
+{
+ 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);
+}
+
+static void
+dex_bus_name_cancelled_cb (GCancellable *cancellable,
+ gpointer user_data)
+{
+ 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);
+}
+
+/**
+ * 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->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,
+ 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 0000000000000000000000000000000000000000..b9d2197449d5789b219d2b8370b6aa10116dfcf3
--- /dev/null
+++ b/src/dex-gdbus.h
@@ -0,0 +1,116 @@
+/*
+ * 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-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
+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 82fd9a999f1592c524d124ef51a05e25ebb0f466..df215c9963b5285b7516826fd6530490142a847a 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 cd4dfb9828c21b5ec9e5a83ee626e7c49c019f5d..97adfc906b8f32c6d2d1ce78f3239b44ed1b4a78 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 afa20e5952b32b08f9f71af1898899b8b257afce..51bd8329f43ef6b6c90a7578bb221ffdbcc44154 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 572212a28101569e063160a397176debd59a2937..df894e492ed591e42238ced290c268efbc55ad7d 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',
@@ -72,6 +74,21 @@ libdex_deps = [
glib_dep,
]
+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])
@@ -148,6 +165,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
@@ -208,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()
diff --git a/testsuite/meson.build b/testsuite/meson.build
index ced0db5d42c168ba0f67376a8a69db6f75387671..2fc2aeb71960d414e486117ae04f94cf5d67a4b7 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 0000000000000000000000000000000000000000..3bfe2582b05147fcc07e4905fcff50f6540a0d09
--- /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 0000000000000000000000000000000000000000..9ee53b9abe46f2492fba249ec038fde7d50e4365
--- /dev/null
+++ b/testsuite/test-dbus.c
@@ -0,0 +1,984 @@
+/* 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;
+ GMainContext *main_context;
+ GDBusConnection *connection;
+ DexTestFoo *foo;
+ GError *error = NULL;
+
+ 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);
+ 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_main_context_unref (main_context);
+ 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;
+ char *name;
+
+ 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 (!(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);
+
+ 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;
+ char *name;
+
+ 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 (!(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),
+ 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;
+ char *name;
+
+ 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 (!(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 * []) {
+ "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;
+ char *name;
+
+ 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 (!(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);
+
+ 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;
+ char *name;
+
+ 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 (!(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),
+ 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;
+ char *name;
+
+ 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 (!(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);
+
+ 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;
+ char *name;
+
+ 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 (!(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);
+
+ 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;
+ char *name;
+
+ 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 (!(name = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (proxy))))
+ g_main_context_iteration (NULL, TRUE);
+ g_free (name);
+
+ {
+ 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 ();
+}