diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/dbus/freedesktop_screensaver.rs | 163 | ||||
| -rw-r--r-- | src/dbus/mod.rs | 6 | ||||
| -rw-r--r-- | src/niri.rs | 15 |
3 files changed, 178 insertions, 6 deletions
diff --git a/src/dbus/freedesktop_screensaver.rs b/src/dbus/freedesktop_screensaver.rs new file mode 100644 index 00000000..aa72bcea --- /dev/null +++ b/src/dbus/freedesktop_screensaver.rs @@ -0,0 +1,163 @@ +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex, OnceLock}; + +use anyhow::Context; +use futures_util::StreamExt; +use zbus::fdo::{self, RequestNameFlags}; +use zbus::names::OwnedUniqueName; +use zbus::{dbus_interface, MessageHeader, Task}; + +use super::Start; + +pub struct ScreenSaver { + is_inhibited: Arc<AtomicBool>, + is_broken: Arc<AtomicBool>, + inhibitors: Arc<Mutex<HashMap<u32, OwnedUniqueName>>>, + counter: u32, + monitor_task: Arc<OnceLock<Task<()>>>, +} + +#[dbus_interface(name = "org.freedesktop.ScreenSaver")] +impl ScreenSaver { + async fn inhibit( + &mut self, + #[zbus(header)] hdr: MessageHeader<'_>, + application_name: &str, + reason_for_inhibit: &str, + ) -> fdo::Result<u32> { + trace!( + "fdo inhibit, app: `{application_name}`, reason: `{reason_for_inhibit}`, owner: {:?}", + hdr.sender() + ); + + let Ok(Some(name)) = hdr.sender() else { + return Err(fdo::Error::Failed(String::from("no sender"))); + }; + let name = OwnedUniqueName::from(name.to_owned()); + + let mut inhibitors = self.inhibitors.lock().unwrap(); + + let mut cookie = None; + for _ in 0..3 { + // Start from 1 because some clients don't like 0. + self.counter = self.counter.wrapping_add(1); + if self.counter == 0 { + self.counter += 1; + } + + if let Entry::Vacant(entry) = inhibitors.entry(self.counter) { + entry.insert(name); + self.is_inhibited.store(true, Ordering::SeqCst); + cookie = Some(self.counter); + break; + } + } + + cookie.ok_or_else(|| fdo::Error::Failed(String::from("no available cookie"))) + } + + async fn un_inhibit(&mut self, cookie: u32) -> fdo::Result<()> { + trace!("fdo uninhibit, cookie: {cookie}"); + + let mut inhibitors = self.inhibitors.lock().unwrap(); + + if inhibitors.remove(&cookie).is_some() { + if inhibitors.is_empty() { + self.is_inhibited.store(false, Ordering::SeqCst); + } + + Ok(()) + } else { + Err(fdo::Error::Failed(String::from("invalid cookie"))) + } + } +} + +impl ScreenSaver { + pub fn new(is_inhibited: Arc<AtomicBool>) -> Self { + Self { + is_inhibited, + is_broken: Arc::new(AtomicBool::new(false)), + inhibitors: Arc::new(Mutex::new(HashMap::new())), + counter: 0, + monitor_task: Arc::new(OnceLock::new()), + } + } +} + +async fn monitor_disappeared_clients( + conn: &zbus::Connection, + is_inhibited: Arc<AtomicBool>, + inhibitors: Arc<Mutex<HashMap<u32, OwnedUniqueName>>>, +) -> anyhow::Result<()> { + let proxy = fdo::DBusProxy::new(conn) + .await + .context("error creating a DBusProxy")?; + + let mut stream = proxy + .receive_name_owner_changed() + .await + .context("error creating a NameOwnerChanged stream")?; + + while let Some(signal) = stream.next().await { + let args = signal + .args() + .context("error retrieving NameOwnerChanged args")?; + + let Some(name) = &**args.old_owner() else { + continue; + }; + + if args.new_owner().is_none() { + trace!("fdo ScreenSaver client disappeared: {name}"); + + let mut inhibitors = inhibitors.lock().unwrap(); + inhibitors.retain(|_, owner| owner != name); + is_inhibited.store(!inhibitors.is_empty(), Ordering::SeqCst); + } + } + + Ok(()) +} + +impl Start for ScreenSaver { + fn start(self) -> anyhow::Result<zbus::blocking::Connection> { + let is_inhibited = self.is_inhibited.clone(); + let is_broken = self.is_broken.clone(); + let inhibitors = self.inhibitors.clone(); + let monitor_task = self.monitor_task.clone(); + + let conn = zbus::blocking::Connection::session()?; + let flags = RequestNameFlags::AllowReplacement + | RequestNameFlags::ReplaceExisting + | RequestNameFlags::DoNotQueue; + + conn.object_server() + .at("/org/freedesktop/ScreenSaver", self)?; + conn.request_name_with_flags("org.freedesktop.ScreenSaver", flags)?; + + let async_conn = conn.inner(); + let future = { + let conn = async_conn.clone(); + async move { + if let Err(err) = + monitor_disappeared_clients(&conn, is_inhibited.clone(), inhibitors.clone()) + .await + { + warn!("error monitoring org.freedesktop.ScreenSaver clients: {err:?}"); + is_broken.store(true, Ordering::SeqCst); + is_inhibited.store(false, Ordering::SeqCst); + inhibitors.lock().unwrap().clear(); + } + } + }; + let task = async_conn + .executor() + .spawn(future, "monitor disappearing clients"); + monitor_task.set(task).unwrap(); + + Ok(conn) + } +} diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index 8dc51386..78b6004b 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -4,6 +4,7 @@ use zbus::Interface; use crate::niri::State; +pub mod freedesktop_screensaver; pub mod gnome_shell_screenshot; pub mod mutter_display_config; pub mod mutter_service_channel; @@ -13,6 +14,7 @@ pub mod mutter_screen_cast; #[cfg(feature = "xdp-gnome-screencast")] use mutter_screen_cast::ScreenCast; +use self::freedesktop_screensaver::ScreenSaver; use self::mutter_display_config::DisplayConfig; use self::mutter_service_channel::ServiceChannel; @@ -24,6 +26,7 @@ trait Start: Interface { pub struct DBusServers { pub conn_service_channel: Option<Connection>, pub conn_display_config: Option<Connection>, + pub conn_screen_saver: Option<Connection>, pub conn_screen_shot: Option<Connection>, #[cfg(feature = "xdp-gnome-screencast")] pub conn_screen_cast: Option<Connection>, @@ -48,6 +51,9 @@ impl DBusServers { let display_config = DisplayConfig::new(backend.enabled_outputs()); dbus.conn_display_config = try_start(display_config); + let screen_saver = ScreenSaver::new(niri.is_fdo_idle_inhibited.clone()); + dbus.conn_screen_saver = try_start(screen_saver); + let (to_niri, from_screenshot) = calloop::channel::channel(); let (to_screenshot, from_niri) = async_channel::unbounded(); niri.event_loop diff --git a/src/niri.rs b/src/niri.rs index c0e33969..ca9a7c47 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::path::PathBuf; use std::rc::Rc; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use std::{env, mem, thread}; @@ -181,6 +181,7 @@ pub struct Niri { pub keyboard_focus: Option<WlSurface>, pub idle_inhibiting_surfaces: HashSet<WlSurface>, + pub is_fdo_idle_inhibited: Arc<AtomicBool>, pub cursor_manager: CursorManager, pub cursor_texture_cache: CursorTextureCache, @@ -1029,6 +1030,7 @@ impl Niri { seat, keyboard_focus: None, idle_inhibiting_surfaces: HashSet::new(), + is_fdo_idle_inhibited: Arc::new(AtomicBool::new(false)), cursor_manager, cursor_texture_cache: Default::default(), cursor_shape_manager_state, @@ -1880,11 +1882,12 @@ impl Niri { self.idle_inhibiting_surfaces.retain(|s| s.is_alive()); - let is_inhibited = self.idle_inhibiting_surfaces.iter().any(|surface| { - with_states(surface, |states| { - surface_primary_scanout_output(surface, states).is_some() - }) - }); + let is_inhibited = self.is_fdo_idle_inhibited.load(Ordering::SeqCst) + || self.idle_inhibiting_surfaces.iter().any(|surface| { + with_states(surface, |states| { + surface_primary_scanout_output(surface, states).is_some() + }) + }); self.idle_notifier_state.set_is_inhibited(is_inhibited); } |
