diff --git a/examples/gio_dbus_register_object/main.rs b/examples/gio_dbus_register_object/main.rs index e5d5302aa767..cc169a5a4d6b 100644 --- a/examples/gio_dbus_register_object/main.rs +++ b/examples/gio_dbus_register_object/main.rs @@ -1,126 +1,186 @@ -use gio::{prelude::*, IOErrorEnum}; -use std::{ - sync::mpsc::{channel, Receiver, Sender}, - time::Duration, -}; - -const EXAMPLE_XML: &str = r#" - - - - - - - - - - - - - -"#; +use gio::prelude::*; -#[derive(Debug, glib::Variant)] -struct Hello { - name: String, +glib::wrapper! { + pub struct SampleApplication(ObjectSubclass) + @extends gio::Application, + @implements gio::ActionGroup, gio::ActionMap; } -#[derive(Debug, glib::Variant)] -struct SlowHello { - name: String, - delay: u32, +impl Default for SampleApplication { + fn default() -> Self { + glib::Object::builder() + .property( + "application-id", + "com.github.gtk-rs.examples.RegisterDBusObject", + ) + .build() + } } -#[derive(Debug)] -enum HelloMethod { - Hello(Hello), - SlowHello(SlowHello), -} +mod imp { + use std::cell::RefCell; + use std::time::Duration; + + use gio::prelude::*; + use gio::subclass::prelude::*; + use gio::{DBusConnection, DBusError}; + + const EXAMPLE_XML: &str = r#" + + + + + + + + + + + + + + +"#; + + #[derive(Debug, glib::Variant)] + struct Hello { + name: String, + } + + #[derive(Debug, glib::Variant)] + struct SlowHello { + name: String, + delay: u32, + } -impl DBusMethodCall for HelloMethod { - fn parse_call( - _obj_path: &str, - _interface: Option<&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")), + #[derive(Debug)] + enum HelloMethod { + Hello(Hello), + SlowHello(SlowHello), + GoodBye, + } + + impl DBusMethodCall for HelloMethod { + fn parse_call( + _obj_path: &str, + _interface: Option<&str>, + method: &str, + params: glib::Variant, + ) -> Result { + match method { + "Hello" => Ok(params.get::().map(Self::Hello)), + "SlowHello" => Ok(params.get::().map(Self::SlowHello)), + "GoodBye" => Ok(Some(Self::GoodBye)), + _ => Err(glib::Error::new(DBusError::UnknownMethod, "No such method")), + } + .and_then(|p| { + p.ok_or_else(|| glib::Error::new(DBusError::InvalidArgs, "Invalid parameters")) + }) } - .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"); - - let example = gio::DBusNodeInfo::for_xml(EXAMPLE_XML) - .ok() - .and_then(|e| e.lookup_interface("com.github.gtk_rs.examples.HelloWorld")) - .expect("Example interface"); - - if let Ok(id) = connection - .register_object("/com/github/gtk_rs/examples/HelloWorld", &example) - .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())) + #[derive(Default)] + pub struct SampleApplication { + registration_id: RefCell>, + } + + impl SampleApplication { + fn register_object( + &self, + connection: &DBusConnection, + ) -> Result { + let example = gio::DBusNodeInfo::for_xml(EXAMPLE_XML) + .ok() + .and_then(|e| e.lookup_interface("com.github.gtk_rs.examples.HelloWorld")) + .expect("Example interface"); + + connection + .register_object("/com/github/gtk_rs/examples/HelloWorld", &example) + .typed_method_call::() + .invoke_and_return_future_local(glib::clone!( + #[weak_allow_none(rename_to = app)] + self.obj(), + move |_, sender, call| { + println!("Method call from {sender:?}"); + let app = app.clone(); + async move { + 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())) + } + HelloMethod::GoodBye => { + if let Some(app) = app { + app.quit(); + } + Ok(None) + } + } + } } + )) + .build() + } + } + + #[glib::object_subclass] + impl ObjectSubclass for SampleApplication { + const NAME: &'static str = "SampleApplication"; + + type Type = super::SampleApplication; + + type ParentType = gio::Application; + } + + impl ObjectImpl for SampleApplication {} + + impl ApplicationImpl for SampleApplication { + fn dbus_register( + &self, + connection: &DBusConnection, + object_path: &str, + ) -> Result<(), glib::Error> { + self.parent_dbus_register(connection, object_path)?; + self.registration_id + .replace(Some(self.register_object(connection)?)); + println!("registered object on session bus"); + Ok(()) + } + + fn dbus_unregister(&self, connection: &DBusConnection, object_path: &str) { + self.parent_dbus_unregister(connection, object_path); + if let Some(id) = self.registration_id.take() { + if connection.unregister_object(id).is_ok() { + println!("Unregistered object"); + } else { + eprintln!("Could not unregister object"); } } - }) - .build() - { - println!("Registered object"); - tx.send(id).unwrap(); - } else { - eprintln!("Could not register object"); - } -} + } + + fn shutdown(&self) { + self.parent_shutdown(); + println!("Good bye!"); + } -fn on_shutdown(app: &gio::Application, rx: &Receiver) { - let connection = app.dbus_connection().expect("connection"); - if let Ok(registration_id) = rx.try_recv() { - if connection.unregister_object(registration_id).is_ok() { - println!("Unregistered object"); - } else { - eprintln!("Could not unregister object"); + fn activate(&self) { + println!("Waiting for DBus Hello method to be called. Call the following command from another terminal:"); + println!("dbus-send --print-reply --dest=com.github.gtk-rs.examples.RegisterDBusObject /com/github/gtk_rs/examples/HelloWorld com.github.gtk_rs.examples.HelloWorld.Hello string:YourName"); + println!("Quit with the following command:"); + println!("dbus-send --print-reply --dest=com.github.gtk-rs.examples.RegisterDBusObject /com/github/gtk_rs/examples/HelloWorld com.github.gtk_rs.examples.HelloWorld.GoodBye"); } } } fn main() -> glib::ExitCode { - let app = gio::Application::builder() - .application_id("com.github.gtk-rs.examples.RegisterDBusObject") - .build(); + let app = SampleApplication::default(); let _guard = app.hold(); - let (tx, rx) = channel::(); - - app.connect_startup(move |app| { - on_startup(app, &tx); - }); - - app.connect_activate(move |_| { - println!("Waiting for DBus Hello method to be called. Call the following command from another terminal:"); - println!("dbus-send --print-reply --dest=com.github.gtk-rs.examples.RegisterDBusObject /com/github/gtk_rs/examples/HelloWorld com.github.gtk_rs.examples.HelloWorld.Hello string:YourName"); - }); - - app.connect_shutdown(move |app| { - on_shutdown(app, &rx); - }); - app.run() } diff --git a/gio/src/subclass/application.rs b/gio/src/subclass/application.rs index de0b44be2918..559233edc434 100644 --- a/gio/src/subclass/application.rs +++ b/gio/src/subclass/application.rs @@ -2,10 +2,12 @@ use std::{ffi::OsString, fmt, ops::Deref, ptr}; -use glib::{prelude::*, subclass::prelude::*, translate::*, ExitCode, VariantDict}; +use glib::{ + prelude::*, subclass::prelude::*, translate::*, Error, ExitCode, Propagation, VariantDict, +}; use libc::{c_char, c_int, c_void}; -use crate::{ffi, ActionGroup, ActionMap, Application}; +use crate::{ffi, ActionGroup, ActionMap, Application, DBusConnection}; pub struct ArgumentList { pub(crate) ptr: *mut *mut *mut c_char, @@ -110,6 +112,18 @@ pub trait ApplicationImpl: fn handle_local_options(&self, options: &VariantDict) -> ExitCode { self.parent_handle_local_options(options) } + + fn dbus_register(&self, connection: &DBusConnection, object_path: &str) -> Result<(), Error> { + self.parent_dbus_register(connection, object_path) + } + + fn dbus_unregister(&self, connection: &DBusConnection, object_path: &str) { + self.parent_dbus_unregister(connection, object_path) + } + + fn name_lost(&self) -> Propagation { + self.parent_name_lost() + } } pub trait ApplicationImplExt: ApplicationImpl { @@ -266,6 +280,63 @@ pub trait ApplicationImplExt: ApplicationImpl { } } } + + fn parent_dbus_register( + &self, + connection: &DBusConnection, + object_path: &str, + ) -> Result<(), glib::Error> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass; + let f = (*parent_class) + .dbus_register + .expect("No parent class implementation for \"dbus_register\""); + let mut err = ptr::null_mut(); + let res = f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + connection.to_glib_none().0, + object_path.to_glib_none().0, + &mut err, + ); + if res == glib::ffi::GFALSE { + Err(from_glib_full(err)) + } else { + debug_assert!(err.is_null()); + Ok(()) + } + } + } + + fn parent_dbus_unregister(&self, connection: &DBusConnection, object_path: &str) { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass; + let f = (*parent_class) + .dbus_unregister + .expect("No parent class implementation for \"dbus_unregister\""); + f( + self.obj().unsafe_cast_ref::().to_glib_none().0, + connection.to_glib_none().0, + object_path.to_glib_none().0, + ); + } + } + + fn parent_name_lost(&self) -> Propagation { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GApplicationClass; + let f = (*parent_class) + .name_lost + .expect("No parent class implementation for \"name_lost\""); + Propagation::from_glib(f(self + .obj() + .unsafe_cast_ref::() + .to_glib_none() + .0)) + } + } } impl ApplicationImplExt for T {} @@ -286,6 +357,9 @@ unsafe impl IsSubclassable for Application { klass.shutdown = Some(application_shutdown::); klass.startup = Some(application_startup::); klass.handle_local_options = Some(application_handle_local_options::); + klass.dbus_register = Some(application_dbus_register::); + klass.dbus_unregister = Some(application_dbus_unregister::); + klass.name_lost = Some(application_name_lost::); } } @@ -390,6 +464,50 @@ unsafe extern "C" fn application_handle_local_options( imp.handle_local_options(&from_glib_borrow(options)).into() } +unsafe extern "C" fn application_dbus_register( + ptr: *mut ffi::GApplication, + connection: *mut ffi::GDBusConnection, + object_path: *const c_char, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + + match imp.dbus_register( + &from_glib_borrow(connection), + &glib::GString::from_glib_borrow(object_path), + ) { + Ok(()) => glib::ffi::GTRUE, + Err(e) => { + if !error.is_null() { + *error = e.into_glib_ptr(); + } + glib::ffi::GFALSE + } + } +} + +unsafe extern "C" fn application_dbus_unregister( + ptr: *mut ffi::GApplication, + connection: *mut ffi::GDBusConnection, + object_path: *const c_char, +) { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + imp.dbus_unregister( + &from_glib_borrow(connection), + &glib::GString::from_glib_borrow(object_path), + ); +} + +unsafe extern "C" fn application_name_lost( + ptr: *mut ffi::GApplication, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + imp.name_lost().into_glib() +} + #[cfg(test)] mod tests { use super::*;