diff --git a/examples/gio_dbus_register_object/main.rs b/examples/gio_dbus_register_object/main.rs
index 8915dd9561a3..f3422e232aeb 100644
--- a/examples/gio_dbus_register_object/main.rs
+++ b/examples/gio_dbus_register_object/main.rs
@@ -1,5 +1,8 @@
-use gio::prelude::*;
-use std::sync::mpsc::{channel, Receiver, Sender};
+use gio::{prelude::*, IOErrorEnum};
+use std::{
+ sync::mpsc::{channel, Receiver, Sender},
+ time::Duration,
+};
const EXAMPLE_XML: &str = r#"
@@ -8,10 +11,48 @@ const EXAMPLE_XML: &str = r#"
+
+
+
+
+
"#;
+#[derive(Debug, glib::Variant)]
+struct Hello {
+ name: String,
+}
+
+#[derive(Debug, glib::Variant)]
+struct SlowHello {
+ name: String,
+ delay: u32,
+}
+
+#[derive(Debug)]
+enum HelloMethod {
+ Hello(Hello),
+ SlowHello(SlowHello),
+}
+
+impl DBusMethodCall for HelloMethod {
+ fn parse_call(
+ _obj_path: &str,
+ _interface: &str,
+ method: &str,
+ params: glib::Variant,
+ ) -> Result {
+ match method {
+ "Hello" => Ok(params.get::().map(Self::Hello)),
+ "SlowHello" => Ok(params.get::().map(Self::SlowHello)),
+ _ => Err(glib::Error::new(IOErrorEnum::Failed, "No such method")),
+ }
+ .and_then(|p| p.ok_or_else(|| glib::Error::new(IOErrorEnum::Failed, "Invalid parameters")))
+ }
+}
+
fn on_startup(app: &gio::Application, tx: &Sender) {
let connection = app.dbus_connection().expect("connection");
@@ -22,25 +63,25 @@ fn on_startup(app: &gio::Application, tx: &Sender) {
if let Ok(id) = connection
.register_object("/com/github/gtk_rs/examples/HelloWorld", &example)
- .method_call(glib::clone!(
- #[strong]
- app,
- move |_, _, _, _, method, params, invocation| {
- match method {
- "Hello" => {
- if let Some((name,)) = <(String,)>::from_variant(¶ms) {
- let greet = format!("Hello {name}!");
- println!("{greet}");
- invocation.return_value(Some(&(greet,).to_variant()));
- } else {
- invocation.return_error(gio::IOErrorEnum::Failed, "Invalid parameters");
- }
+ .typed_method_call::()
+ .invoke_and_return_future_local(|_, sender, call| {
+ println!("Method call from {sender}");
+ async {
+ match call {
+ HelloMethod::Hello(Hello { name }) => {
+ let greet = format!("Hello {name}!");
+ println!("{greet}");
+ Ok(Some(greet.to_variant()))
+ }
+ HelloMethod::SlowHello(SlowHello { name, delay }) => {
+ glib::timeout_future(Duration::from_secs(delay as u64)).await;
+ let greet = format!("Hello {name} after {delay} seconds!");
+ println!("{greet}");
+ Ok(Some(greet.to_variant()))
}
- _ => unreachable!(),
}
- app.quit();
}
- ))
+ })
.build()
{
println!("Registered object");
diff --git a/gio/src/dbus_connection.rs b/gio/src/dbus_connection.rs
index 982c2f8a164c..d9b15e98cb1e 100644
--- a/gio/src/dbus_connection.rs
+++ b/gio/src/dbus_connection.rs
@@ -1,13 +1,110 @@
// Take a look at the license at the top of the repository in the LICENSE file.
-use std::{boxed::Box as Box_, num::NonZeroU32};
-
-use glib::{prelude::*, translate::*};
+use std::{boxed::Box as Box_, future::Future, marker::PhantomData, num::NonZeroU32};
use crate::{
ffi, ActionGroup, DBusConnection, DBusInterfaceInfo, DBusMessage, DBusMethodInvocation,
DBusSignalFlags, MenuModel,
};
+use glib::{prelude::*, translate::*};
+
+pub trait DBusMethodCall: Sized {
+ fn parse_call(
+ obj_path: &str,
+ interface: &str,
+ method: &str,
+ params: glib::Variant,
+ ) -> Result;
+}
+
+// rustdoc-stripper-ignore-next
+/// Handle method invocations.
+pub struct MethodCallBuilder<'a, T> {
+ registration: RegistrationBuilder<'a>,
+ capture_type: PhantomData,
+}
+
+impl<'a, T: DBusMethodCall> MethodCallBuilder<'a, T> {
+ // rustdoc-stripper-ignore-next
+ /// Handle invocation of a parsed method call.
+ ///
+ /// For each DBus method call parse the call, and then invoke the given closure
+ /// with
+ ///
+ /// 1. the DBus connection object,
+ /// 2. the name of the sender of the method call,
+ /// 3. the parsed call, and
+ /// 4. the method invocation object.
+ ///
+ /// The closure **must** return a value through the invocation object in all
+ /// code paths, using any of its `return_` functions, such as
+ /// [`DBusMethodInvocation::return_result`] or
+ /// [`DBusMethodInvocation::return_future_local`], to finish the call.
+ ///
+ /// If direct access to the invocation object is not needed,
+ /// [`invoke_and_return`] and [`invoke_and_return_future_local`] provide a
+ /// safer interface where the callback returns a result directly.
+ pub fn invoke(self, f: F) -> RegistrationBuilder<'a>
+ where
+ F: Fn(DBusConnection, &str, T, DBusMethodInvocation) + 'static,
+ {
+ self.registration.method_call(
+ move |connection, sender, obj_path, interface, method, params, invocation| {
+ match T::parse_call(obj_path, interface, method, params) {
+ Ok(call) => f(connection, sender, call, invocation),
+ Err(error) => invocation.return_gerror(error),
+ }
+ },
+ )
+ }
+
+ // rustdoc-stripper-ignore-next
+ /// Handle invocation of a parsed method call.
+ ///
+ /// For each DBus method call parse the call, and then invoke the given closure
+ /// with
+ ///
+ /// 1. the DBus connection object,
+ /// 2. the name of the sender of the method call, and
+ /// 3. the parsed call.
+ ///
+ /// The return value of the closure is then returned on the method call.
+ /// If the returned variant value is not a tuple, it is automatically wrapped
+ /// in a single element tuple, as DBus methods must always return tuples.
+ /// See [`DBusMethodInvocation::return_result`] for details.
+ pub fn invoke_and_return(self, f: F) -> RegistrationBuilder<'a>
+ where
+ F: Fn(DBusConnection, &str, T) -> Result