diff --git a/examples/gio_dbus_register_object/main.rs b/examples/gio_dbus_register_object/main.rs index 37a4143e8453..5a7d336fd793 100644 --- a/examples/gio_dbus_register_object/main.rs +++ b/examples/gio_dbus_register_object/main.rs @@ -1,4 +1,4 @@ -use gio::{prelude::*, IOErrorEnum}; +use gio::{prelude::*, DBusMethodCall, IOErrorEnum}; use std::{ sync::mpsc::{channel, Receiver, Sender}, time::Duration, @@ -32,16 +32,21 @@ struct SlowHello { } #[derive(Debug)] -enum Call { +enum HelloMethod { Hello(Hello), SlowHello(SlowHello), } -impl Call { - pub fn parse(method: &str, parameters: glib::Variant) -> Result { +impl DBusMethodCall for HelloMethod { + fn parse_call( + _obj_path: &str, + _interface: &str, + method: &str, + params: glib::Variant, + ) -> Result { match method { - "Hello" => Ok(parameters.get::().map(Call::Hello)), - "SlowHello" => Ok(parameters.get::().map(Call::SlowHello)), + "Hello" => Ok(params.get::().map(HelloMethod::Hello)), + "SlowHello" => Ok(params.get::().map(HelloMethod::SlowHello)), _ => Err(glib::Error::new(IOErrorEnum::Failed, "No such method")), } .and_then(|p| p.ok_or_else(|| glib::Error::new(IOErrorEnum::Failed, "Invalid parameters"))) @@ -58,23 +63,24 @@ fn on_startup(app: &gio::Application, tx: &Sender) { if let Ok(id) = connection .register_object("/com/github/gtk_rs/examples/HelloWorld", &example) - .method_call(move |_, _, _, _, method, params, invocation| { - let call = Call::parse(method, params); - invocation.return_future_local(async move { - match call? { - Call::Hello(Hello { name }) => { + .parse_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())) } - Call::SlowHello(SlowHello { name, delay }) => { + 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())) } } - }); + } }) .build() { diff --git a/gio/src/dbus_connection.rs b/gio/src/dbus_connection.rs index f7ee74e7fbf7..4f29d13c5160 100644 --- a/gio/src/dbus_connection.rs +++ b/gio/src/dbus_connection.rs @@ -1,13 +1,95 @@ // 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; +} + +pub struct MethodCallBuilder<'a, T> { + registration: RegistrationBuilder<'a>, + capture_type: PhantomData, +} + +impl<'a, T: DBusMethodCall> MethodCallBuilder<'a, T> { + /// 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, + /// using any of its `return_` functions, such as + /// [`DBusMethodInvocation::return_result`] or + /// [`DBusMethodInvocation::return_future_local`], to finish the call. + 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), + } + }, + ) + } + + /// 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. + pub fn invoke_and_return(self, f: F) -> RegistrationBuilder<'a> + where + F: Fn(DBusConnection, &str, T) -> Result, glib::Error> + 'static, + { + self.invoke(move |connection, sender, call, invocation| { + invocation.return_result(f(connection, sender, call)) + }) + } + + /// Handle an async 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. + pub fn invoke_and_return_future_local(self, f: F) -> RegistrationBuilder<'a> + where + F: Fn(DBusConnection, &str, T) -> Fut + 'static, + Fut: Future, glib::Error>> + 'static, + { + self.invoke(move |connection, sender, call, invocation| { + invocation.return_future_local(f(connection, sender, call)); + }) + } +} #[derive(Debug, Eq, PartialEq)] pub struct RegistrationId(NonZeroU32); @@ -49,6 +131,13 @@ impl<'a> RegistrationBuilder<'a> { self } + pub fn parse_method_call(self) -> MethodCallBuilder<'a, T> { + MethodCallBuilder { + registration: self, + capture_type: Default::default(), + } + } + #[doc(alias = "get_property")] pub fn property glib::Variant + 'static>( mut self, diff --git a/gio/src/lib.rs b/gio/src/lib.rs index b559db77329d..970a5afe5912 100644 --- a/gio/src/lib.rs +++ b/gio/src/lib.rs @@ -28,8 +28,8 @@ mod dbus; pub use self::dbus::*; mod dbus_connection; pub use self::dbus_connection::{ - ActionGroupExportId, FilterId, MenuModelExportId, RegistrationId, SignalSubscriptionId, - WatcherId, + ActionGroupExportId, DBusMethodCall, FilterId, MenuModelExportId, RegistrationId, + SignalSubscriptionId, WatcherId, }; mod dbus_message; mod dbus_method_invocation;