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 (); +}