diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 3c2e86a..aedde51 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -2,6 +2,9 @@ pub mod common; pub mod input; pub mod notifications; +#[allow(clippy::all)] +mod notifications_dbus; + #[cfg(feature = "openvr")] pub mod openvr; diff --git a/src/backend/notifications.rs b/src/backend/notifications.rs index 19ad338..69ab6e9 100644 --- a/src/backend/notifications.rs +++ b/src/backend/notifications.rs @@ -1,4 +1,9 @@ -use dbus::{blocking::Connection, channel::MatchingReceiver, message::MatchRule}; +use dbus::{ + arg::{PropMap, Variant}, + blocking::Connection, + channel::MatchingReceiver, + message::MatchRule, +}; use serde::Deserialize; use std::{ sync::{ @@ -9,6 +14,7 @@ use std::{ }; use crate::{ + backend::notifications_dbus::OrgFreedesktopNotifications, overlays::toast::{Toast, ToastTopic}, state::AppState, }; @@ -196,6 +202,60 @@ impl Drop for NotificationManager { } } +pub struct DbusNotificationSender { + connection: Connection, +} + +impl DbusNotificationSender { + pub fn new() -> anyhow::Result { + Ok(Self { + connection: Connection::new_session()?, + }) + } + + pub fn notify_send( + &self, + summary: &str, + body: &str, + urgency: u8, + timeout: i32, + replaces_id: u32, + transient: bool, + ) -> anyhow::Result { + let proxy = self.connection.with_proxy( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + Duration::from_millis(1000), + ); + + let mut hints = PropMap::new(); + hints.insert("urgency".to_string(), Variant(Box::new(urgency))); + hints.insert("transient".to_string(), Variant(Box::new(transient))); + + Ok(proxy.notify( + "WlxOverlay-S", + replaces_id, + "", + summary, + body, + vec![], + hints, + timeout, + )?) + } + + pub fn notify_close(&self, id: u32) -> anyhow::Result<()> { + let proxy = self.connection.with_proxy( + "org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + Duration::from_millis(1000), + ); + + proxy.close_notification(id)?; + Ok(()) + } +} + fn parse_dbus(msg: &dbus::Message) -> anyhow::Result { let mut args = msg.iter_init(); let app_name: String = args.read()?; diff --git a/src/backend/notifications_dbus.rs b/src/backend/notifications_dbus.rs new file mode 100644 index 0000000..336abf9 --- /dev/null +++ b/src/backend/notifications_dbus.rs @@ -0,0 +1,267 @@ +// This code was autogenerated with `dbus-codegen-rust -g -m None -d org.freedesktop.Notifications -p /org/freedesktop/Notifications`, see https://github.com/diwic/dbus-rs +use dbus as dbus; +#[allow(unused_imports)] +use dbus::arg; +use dbus::blocking; + +pub trait OrgFreedesktopDBusProperties { + fn get arg::Get<'b> + 'static>(&self, interface_name: &str, property_name: &str) -> Result; + fn get_all(&self, interface_name: &str) -> Result; + fn set(&self, interface_name: &str, property_name: &str, value: I2) -> Result<(), dbus::Error>; +} + +#[derive(Debug)] +pub struct OrgFreedesktopDBusPropertiesPropertiesChanged { + pub interface_name: String, + pub changed_properties: arg::PropMap, + pub invalidated_properties: Vec, +} + +impl arg::AppendAll for OrgFreedesktopDBusPropertiesPropertiesChanged { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.interface_name, i); + arg::RefArg::append(&self.changed_properties, i); + arg::RefArg::append(&self.invalidated_properties, i); + } +} + +impl arg::ReadAll for OrgFreedesktopDBusPropertiesPropertiesChanged { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopDBusPropertiesPropertiesChanged { + interface_name: i.read()?, + changed_properties: i.read()?, + invalidated_properties: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopDBusPropertiesPropertiesChanged { + const NAME: &'static str = "PropertiesChanged"; + const INTERFACE: &'static str = "org.freedesktop.DBus.Properties"; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopDBusProperties for blocking::Proxy<'a, C> { + + fn get arg::Get<'b> + 'static>(&self, interface_name: &str, property_name: &str) -> Result { + self.method_call("org.freedesktop.DBus.Properties", "Get", (interface_name, property_name, )) + .and_then(|r: (arg::Variant, )| Ok((r.0).0, )) + } + + fn get_all(&self, interface_name: &str) -> Result { + self.method_call("org.freedesktop.DBus.Properties", "GetAll", (interface_name, )) + .and_then(|r: (arg::PropMap, )| Ok(r.0, )) + } + + fn set(&self, interface_name: &str, property_name: &str, value: I2) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.DBus.Properties", "Set", (interface_name, property_name, arg::Variant(value), )) + } +} + +pub trait OrgFreedesktopDBusIntrospectable { + fn introspect(&self) -> Result; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopDBusIntrospectable for blocking::Proxy<'a, C> { + + fn introspect(&self) -> Result { + self.method_call("org.freedesktop.DBus.Introspectable", "Introspect", ()) + .and_then(|r: (String, )| Ok(r.0, )) + } +} + +pub trait OrgFreedesktopDBusPeer { + fn ping(&self) -> Result<(), dbus::Error>; + fn get_machine_id(&self) -> Result; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopDBusPeer for blocking::Proxy<'a, C> { + + fn ping(&self) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.DBus.Peer", "Ping", ()) + } + + fn get_machine_id(&self) -> Result { + self.method_call("org.freedesktop.DBus.Peer", "GetMachineId", ()) + .and_then(|r: (String, )| Ok(r.0, )) + } +} + +pub trait OrgFreedesktopNotifications { + fn set_noti_window_visibility(&self, value: bool) -> Result<(), dbus::Error>; + fn toggle_dnd(&self) -> Result; + fn set_dnd(&self, state: bool) -> Result<(), dbus::Error>; + fn get_dnd(&self) -> Result; + fn manually_close_notification(&self, id: u32, timeout: bool) -> Result<(), dbus::Error>; + fn close_all_notifications(&self) -> Result<(), dbus::Error>; + fn hide_latest_notification(&self, close: bool) -> Result<(), dbus::Error>; + fn get_capabilities(&self) -> Result, dbus::Error>; + fn notify(&self, app_name: &str, replaces_id: u32, app_icon: &str, summary: &str, body: &str, actions: Vec<&str>, hints: arg::PropMap, expire_timeout: i32) -> Result; + fn close_notification(&self, id: u32) -> Result<(), dbus::Error>; + fn get_server_information(&self) -> Result<(String, String, String, String), dbus::Error>; + fn dnd(&self) -> Result; + fn set_dnd_(&self, value: bool) -> Result<(), dbus::Error>; +} + +#[derive(Debug)] +pub struct OrgFreedesktopNotificationsOnDndToggle { + pub dnd: bool, +} + +impl arg::AppendAll for OrgFreedesktopNotificationsOnDndToggle { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.dnd, i); + } +} + +impl arg::ReadAll for OrgFreedesktopNotificationsOnDndToggle { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopNotificationsOnDndToggle { + dnd: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopNotificationsOnDndToggle { + const NAME: &'static str = "OnDndToggle"; + const INTERFACE: &'static str = "org.freedesktop.Notifications"; +} + +#[derive(Debug)] +pub struct OrgFreedesktopNotificationsNotificationClosed { + pub id: u32, + pub reason: u32, +} + +impl arg::AppendAll for OrgFreedesktopNotificationsNotificationClosed { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.id, i); + arg::RefArg::append(&self.reason, i); + } +} + +impl arg::ReadAll for OrgFreedesktopNotificationsNotificationClosed { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopNotificationsNotificationClosed { + id: i.read()?, + reason: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopNotificationsNotificationClosed { + const NAME: &'static str = "NotificationClosed"; + const INTERFACE: &'static str = "org.freedesktop.Notifications"; +} + +#[derive(Debug)] +pub struct OrgFreedesktopNotificationsActionInvoked { + pub id: u32, + pub action_key: String, +} + +impl arg::AppendAll for OrgFreedesktopNotificationsActionInvoked { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.id, i); + arg::RefArg::append(&self.action_key, i); + } +} + +impl arg::ReadAll for OrgFreedesktopNotificationsActionInvoked { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopNotificationsActionInvoked { + id: i.read()?, + action_key: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopNotificationsActionInvoked { + const NAME: &'static str = "ActionInvoked"; + const INTERFACE: &'static str = "org.freedesktop.Notifications"; +} + +#[derive(Debug)] +pub struct OrgFreedesktopNotificationsNotificationReplied { + pub id: u32, + pub text: String, +} + +impl arg::AppendAll for OrgFreedesktopNotificationsNotificationReplied { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.id, i); + arg::RefArg::append(&self.text, i); + } +} + +impl arg::ReadAll for OrgFreedesktopNotificationsNotificationReplied { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopNotificationsNotificationReplied { + id: i.read()?, + text: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopNotificationsNotificationReplied { + const NAME: &'static str = "NotificationReplied"; + const INTERFACE: &'static str = "org.freedesktop.Notifications"; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopNotifications for blocking::Proxy<'a, C> { + + fn set_noti_window_visibility(&self, value: bool) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "SetNotiWindowVisibility", (value, )) + } + + fn toggle_dnd(&self) -> Result { + self.method_call("org.freedesktop.Notifications", "ToggleDnd", ()) + .and_then(|r: (bool, )| Ok(r.0, )) + } + + fn set_dnd(&self, state: bool) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "SetDnd", (state, )) + } + + fn get_dnd(&self) -> Result { + self.method_call("org.freedesktop.Notifications", "GetDnd", ()) + .and_then(|r: (bool, )| Ok(r.0, )) + } + + fn manually_close_notification(&self, id: u32, timeout: bool) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "ManuallyCloseNotification", (id, timeout, )) + } + + fn close_all_notifications(&self) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "CloseAllNotifications", ()) + } + + fn hide_latest_notification(&self, close: bool) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "HideLatestNotification", (close, )) + } + + fn get_capabilities(&self) -> Result, dbus::Error> { + self.method_call("org.freedesktop.Notifications", "GetCapabilities", ()) + .and_then(|r: (Vec, )| Ok(r.0, )) + } + + fn notify(&self, app_name: &str, replaces_id: u32, app_icon: &str, summary: &str, body: &str, actions: Vec<&str>, hints: arg::PropMap, expire_timeout: i32) -> Result { + self.method_call("org.freedesktop.Notifications", "Notify", (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout, )) + .and_then(|r: (u32, )| Ok(r.0, )) + } + + fn close_notification(&self, id: u32) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "CloseNotification", (id, )) + } + + fn get_server_information(&self) -> Result<(String, String, String, String), dbus::Error> { + self.method_call("org.freedesktop.Notifications", "GetServerInformation", ()) + } + + fn dnd(&self) -> Result { + ::get(self, "org.freedesktop.Notifications", "Dnd") + } + + fn set_dnd_(&self, value: bool) -> Result<(), dbus::Error> { + ::set(self, "org.freedesktop.Notifications", "Dnd", value) + } +} diff --git a/src/overlays/screen.rs b/src/overlays/screen.rs index 5e4a5d8..8dc0336 100644 --- a/src/overlays/screen.rs +++ b/src/overlays/screen.rs @@ -18,6 +18,7 @@ use wlx_capture::{ DrmFormat, FrameFormat, MouseMeta, WlxFrame, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ABGR8888, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR2101010, DRM_FORMAT_XBGR8888, DRM_FORMAT_XRGB8888, }, + pipewire::PipewireSelectScreenResult, WlxCapture, }; @@ -25,8 +26,8 @@ use wlx_capture::{ use { crate::config_io, std::error::Error, - std::{ops::Deref, path::PathBuf}, - wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture}, + std::{ops::Deref, path::PathBuf, task}, + wlx_capture::pipewire::PipewireCapture, }; #[cfg(all(feature = "x11", feature = "pipewire"))] @@ -337,21 +338,22 @@ impl ScreenRenderer { )> { let name = output.name.clone(); let embed_mouse = !session.config.double_cursor_fix; - log::info!( - "On screen share prompt, pick: {} {} {} (pos {}, {})", - &output.name, - &output.make, - &output.model, - &output.logical_pos.0, - &output.logical_pos.1, - ); - let select_screen_result = futures::executor::block_on(pipewire_select_screen( + + let select_screen_result = select_pw_screen( + &format!( + "Now select: {} {} {} @ {},{}", + &output.name, + &output.make, + &output.model, + &output.logical_pos.0, + &output.logical_pos.1 + ), token, embed_mouse, true, true, false, - ))?; + )?; let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element @@ -866,8 +868,16 @@ pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result( log::debug!("best: {:?}", best.map(|b| &b.monitor)); best } + +#[cfg(feature = "pipewire")] +fn select_pw_screen( + instructions: &str, + token: Option<&str>, + embed_mouse: bool, + screens_only: bool, + persist: bool, + multiple: bool, +) -> Result { + use crate::backend::notifications::DbusNotificationSender; + use wlx_capture::pipewire::pipewire_select_screen; + + let future = async move { + let print_at = Instant::now() + Duration::from_millis(250); + let mut notify = None; + + let f = pipewire_select_screen(token, embed_mouse, screens_only, persist, multiple); + futures::pin_mut!(f); + + loop { + match futures::poll!(&mut f) { + task::Poll::Ready(result) => return result, + task::Poll::Pending => { + if Instant::now() >= print_at { + log::info!("{}", instructions); + if let Ok(sender) = DbusNotificationSender::new() { + if let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) { + notify = Some((sender, id)); + } + } + break; + } + futures::future::lazy(|_| { + std::thread::sleep(Duration::from_millis(10)); + }) + .await; + continue; + } + } + } + + let result = f.await; + if let Some((sender, id)) = notify { + let _ = sender.notify_close(id); + } + result + }; + + futures::executor::block_on(future) +}