diff --git a/sidebus-broker/src/main.rs b/sidebus-broker/src/main.rs index 194f2af..fc79898 100644 --- a/sidebus-broker/src/main.rs +++ b/sidebus-broker/src/main.rs @@ -155,12 +155,14 @@ async fn main() -> eyre::Result<()> { cli.guest_mountpoint, ) .await?; + let notification_imp = portal::notification::Notification::new(&host_session_conn).await?; let settings_imp = portal::settings::Settings::new(&host_session_conn).await?; async fn on_vm_bus_connected( vm_bus_conn: zbus::Connection, file_chooser: portal::file_chooser::FileChooser, file_transfer: portal::file_transfer::FileTransfer, + notification: portal::notification::Notification, settings: portal::settings::Settings, ) -> Result<(), eyre::Report> { if !vm_bus_conn @@ -192,6 +194,25 @@ async fn main() -> eyre::Result<()> { } }); + if !vm_bus_conn + .object_server() + .at("/org/freedesktop/portal/desktop", notification) + .await? + { + error!("org.freedesktop.portal.Notification already provided"); + }; + let notification_ref = vm_bus_conn + .object_server() + .interface::<_, portal::notification::Notification>("/org/freedesktop/portal/desktop") + .await?; + tokio::spawn(async move { + let notification = notification_ref.get().await; + let emitter = notification_ref.signal_emitter(); + if let Err(err) = notification.forward_actions(emitter.clone()).await { + error!(%err, "forwarding notification changes ended"); + } + }); + if !vm_bus_conn .object_server() .at("/org/freedesktop/portal/desktop", settings) @@ -217,16 +238,16 @@ async fn main() -> eyre::Result<()> { if let Some(path) = cli.unix_path { let vm_unix_listener = UnixListener::bind(path)?; - server_tasks.spawn(enclose!((file_chooser_imp, file_transfer_imp, settings_imp) async move { + server_tasks.spawn(enclose!((file_chooser_imp, file_transfer_imp, notification_imp, settings_imp) async move { while let Ok((socket, remote_addr)) = vm_unix_listener.accept().await { - let f = enclose!((file_chooser_imp, file_transfer_imp, settings_imp) async move { + let f = enclose!((file_chooser_imp, file_transfer_imp, notification_imp, settings_imp) async move { let client_conn = zbus::connection::Builder::unix_stream(socket) .auth_mechanism(zbus::AuthMechanism::Anonymous) .name("org.freedesktop.portal.Desktop")? .name("org.freedesktop.portal.Documents")? .build() .await?; - on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, settings_imp).await + on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, notification_imp, settings_imp).await }); tokio::spawn( async { @@ -247,10 +268,10 @@ async fn main() -> eyre::Result<()> { vsock::ListenerBuilder::new(vsock::VsockAddr::new(vsock::VMADDR_CID_HOST, port)) .with_label("VM Bus") .listen(move |client| { - enclose!((file_chooser_imp, file_transfer_imp, settings_imp) async move { + enclose!((file_chooser_imp, file_transfer_imp, notification_imp, settings_imp) async move { // TODO: Not necessary to go through the channel, add vsock support to the Peer too? let client_conn = client.build().await?; - on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, settings_imp).await + on_vm_bus_connected(client_conn, file_chooser_imp, file_transfer_imp, notification_imp, settings_imp).await }) }) .map_ok_or_else( diff --git a/sidebus-broker/src/portal/mod.rs b/sidebus-broker/src/portal/mod.rs index be14b9f..0f097ac 100644 --- a/sidebus-broker/src/portal/mod.rs +++ b/sidebus-broker/src/portal/mod.rs @@ -1,5 +1,6 @@ pub mod documents; pub mod file_chooser; pub mod file_transfer; +pub mod notification; pub mod request; pub mod settings; diff --git a/sidebus-broker/src/portal/notification.rs b/sidebus-broker/src/portal/notification.rs new file mode 100644 index 0000000..0d41850 --- /dev/null +++ b/sidebus-broker/src/portal/notification.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; + +use tokio_stream::StreamExt; +use tracing::warn; +use zbus::{Connection, fdo::Result, names::UniqueName, object_server::SignalEmitter, zvariant}; + +#[derive(Clone)] +pub struct Notification { + host: NotificationProxy<'static>, +} + +#[zbus::interface( + name = "org.freedesktop.portal.Notification", + proxy( + default_service = "org.freedesktop.portal.Desktop", + default_path = "/org/freedesktop/portal/desktop" + ) +)] +impl Notification { + async fn add_notification( + &self, + #[zbus(header)] hdr: zbus::message::Header<'_>, + id: &str, + notification: HashMap<&str, zvariant::Value<'_>>, + ) -> Result<()> { + let sender = hdr.sender().ok_or_else(|| zbus::Error::MissingField)?; + self.host + .add_notification( + &format!("{sender}\x0C\x0CSIDEBUS\x0C\x0C{id}"), + notification, + ) + .await + } + + async fn remove_notification( + &self, + #[zbus(header)] hdr: zbus::message::Header<'_>, + id: &str, + ) -> Result<()> { + let sender = hdr.sender().ok_or_else(|| zbus::Error::MissingField)?; + self.host + .remove_notification(&format!("{sender}\x0C\x0CSIDEBUS\x0C\x0C{id}")) + .await + } + + #[zbus(signal)] + async fn action_invoked( + signal_emitter: &SignalEmitter<'_>, + id: &str, + action: &str, + parameter: Vec>, + ) -> zbus::Result<()>; + + #[zbus(property)] + async fn supported_options(&self) -> Result> { + self.host + .supported_options() + .await + .map_err(|err| err.into()) + } + + #[zbus(property, name = "version")] + fn version(&self) -> Result { + Ok(2) + } +} + +impl Notification { + pub async fn new(host_session_conn: &Connection) -> Result { + let host = NotificationProxy::builder(host_session_conn) + .build() + .await?; + Ok(Self { host }) + } + + pub async fn forward_actions(&self, mut signal_emitter: SignalEmitter<'static>) -> Result<()> { + let mut stream = self.host.receive_action_invoked().await?; + while let Some(x) = stream.next().await { + let args = x.args()?; + let mut split = args.id.split("\x0C\x0CSIDEBUS\x0C\x0C"); + let sender = split + .next() + .and_then(|x| UniqueName::try_from(x).ok().map(|x| x.to_owned())) + .ok_or_else(|| zbus::fdo::Error::Failed("bad ID".to_owned()))?; + let id = split + .next() + .ok_or_else(|| zbus::fdo::Error::Failed("bad ID".to_owned()))?; + signal_emitter = signal_emitter.set_destination(sender.into()); + Notification::action_invoked(&signal_emitter, id, args.action, args.parameter).await?; + () + } + warn!("actions stream end"); + Ok(()) + } +}