From 79f8e5385014c0b9356a66ebc29a615ba403c76e Mon Sep 17 00:00:00 2001 From: Demmie <2e3s19@gmail.com> Date: Thu, 11 May 2023 18:42:18 -0400 Subject: [PATCH] Refactor to use common watchers loop --- watchers/src/watchers.rs | 124 +++++++++++++----- watchers/src/watchers/gnome_idle.rs | 27 ++-- watchers/src/watchers/gnome_window.rs | 18 +-- watchers/src/watchers/kwin_window.rs | 35 +++-- watchers/src/watchers/wl_foreign_toplevel.rs | 71 +++++----- watchers/src/watchers/wl_kwin_idle.rs | 70 ++++------ watchers/src/watchers/x11_screensaver_idle.rs | 32 ++--- watchers/src/watchers/x11_window.rs | 19 +-- 8 files changed, 199 insertions(+), 197 deletions(-) diff --git a/watchers/src/watchers.rs b/watchers/src/watchers.rs index 4ce5f33..9a52ea5 100644 --- a/watchers/src/watchers.rs +++ b/watchers/src/watchers.rs @@ -13,54 +13,113 @@ mod x11_connection; mod x11_screensaver_idle; mod x11_window; -use crate::report_client::ReportClient; +use crate::{config::Config, report_client::ReportClient}; use std::{ - sync::{atomic::AtomicBool, Arc}, + fmt::Display, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, thread::{self, JoinHandle}, + time::Duration, }; +pub enum WatcherType { + Idle, + ActiveWindow, +} + +impl WatcherType { + fn sleep_time(&self, config: &Config) -> Duration { + match self { + WatcherType::Idle => config.poll_time_idle, + WatcherType::ActiveWindow => config.poll_time_idle, + } + } +} + +impl Display for WatcherType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WatcherType::Idle => write!(f, "idle"), + WatcherType::ActiveWindow => write!(f, "active window"), + } + } +} + pub trait Watcher: Send { - fn new() -> anyhow::Result + fn new(client: &Arc) -> anyhow::Result where Self: Sized; - fn watch(&mut self, client: &Arc, is_stopped: Arc); + + fn run( + &mut self, + watcher_type: &WatcherType, + client: &Arc, + is_stopped: Arc, + ) { + info!("Starting {watcher_type} watcher"); + loop { + if is_stopped.load(Ordering::Relaxed) { + warn!("Received an exit signal, shutting down {watcher_type}"); + break; + } + self.run_iteration(client); + thread::sleep(watcher_type.sleep_time(&client.config)); + } + } + + fn run_iteration(&mut self, client: &Arc); } type BoxedWatcher = Box; - -type WatcherConstructor = (&'static str, fn() -> anyhow::Result); -type WatcherConstructors = [WatcherConstructor]; +type WatcherConstructors = [( + &'static str, + WatcherType, + fn(&Arc) -> anyhow::Result, +)]; pub trait ConstructorFilter { - fn filter_first_supported(&self) -> Option; + fn filter_first_supported( + &self, + client: &Arc, + ) -> Option<(&WatcherType, BoxedWatcher)>; fn run_first_supported( - &self, + &'static self, client: &Arc, is_stopped: Arc, ) -> Option>; } impl ConstructorFilter for WatcherConstructors { - fn filter_first_supported(&self) -> Option { - self.iter().find_map(|(name, watcher)| match watcher() { - Ok(watcher) => Some(watcher), - Err(e) => { - debug!("{name} cannot run: {e}"); - None - } - }) + fn filter_first_supported( + &self, + client: &Arc, + ) -> Option<(&WatcherType, BoxedWatcher)> { + self.iter() + .find_map(|(name, watcher_type, watcher)| match watcher(client) { + Ok(watcher) => { + info!("Selected {name} as {watcher_type} watcher"); + Some((watcher_type, watcher)) + } + Err(e) => { + debug!("{name} cannot run: {e}"); + None + } + }) } fn run_first_supported( - &self, + &'static self, client: &Arc, is_stopped: Arc, ) -> Option> { - let idle_watcher = self.filter_first_supported(); - if let Some(mut watcher) = idle_watcher { + let idle_watcher = self.filter_first_supported(client); + if let Some((watcher_type, mut watcher)) = idle_watcher { let thread_client = Arc::clone(client); - let idle_handler = thread::spawn(move || watcher.watch(&thread_client, is_stopped)); + let idle_handler = + thread::spawn(move || watcher.run(watcher_type, &thread_client, is_stopped)); Some(idle_handler) } else { None @@ -69,26 +128,29 @@ impl ConstructorFilter for WatcherConstructors { } macro_rules! watcher { - ($watcher_struct:ty) => { - (stringify!($watcher_struct), || { - Ok(Box::new(<$watcher_struct>::new()?)) + ($watcher_struct:ty, $watcher_type:expr) => { + (stringify!($watcher_struct), $watcher_type, |client| { + Ok(Box::new(<$watcher_struct>::new(client)?)) }) }; } pub const IDLE: &WatcherConstructors = &[ - watcher!(wl_kwin_idle::IdleWatcher), - watcher!(x11_screensaver_idle::IdleWatcher), + watcher!(wl_kwin_idle::IdleWatcher, WatcherType::Idle), + watcher!(x11_screensaver_idle::IdleWatcher, WatcherType::Idle), #[cfg(feature = "gnome")] - watcher!(gnome_idle::IdleWatcher), + watcher!(gnome_idle::IdleWatcher, WatcherType::Idle), ]; pub const ACTIVE_WINDOW: &WatcherConstructors = &[ - watcher!(wl_foreign_toplevel::WindowWatcher), + watcher!( + wl_foreign_toplevel::WindowWatcher, + WatcherType::ActiveWindow + ), // XWayland gives _NET_WM_NAME on some windows in KDE, but not on others #[cfg(feature = "kwin_window")] - watcher!(kwin_window::WindowWatcher), - watcher!(x11_window::WindowWatcher), + watcher!(kwin_window::WindowWatcher, WatcherType::ActiveWindow), + watcher!(x11_window::WindowWatcher, WatcherType::ActiveWindow), #[cfg(feature = "gnome")] - watcher!(gnome_window::WindowWatcher), + watcher!(gnome_window::WindowWatcher, WatcherType::ActiveWindow), ]; diff --git a/watchers/src/watchers/gnome_idle.rs b/watchers/src/watchers/gnome_idle.rs index ad4b2c8..48ec538 100644 --- a/watchers/src/watchers/gnome_idle.rs +++ b/watchers/src/watchers/gnome_idle.rs @@ -1,13 +1,12 @@ use super::{idle, Watcher}; use crate::report_client::ReportClient; use anyhow::Context; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::thread; use zbus::blocking::Connection; pub struct IdleWatcher { dbus_connection: Connection, + is_idle: bool, } impl idle::SinceLastInput for IdleWatcher { @@ -27,30 +26,22 @@ impl idle::SinceLastInput for IdleWatcher { } impl Watcher for IdleWatcher { - fn new() -> anyhow::Result { + fn new(_: &Arc) -> anyhow::Result { let mut watcher = Self { dbus_connection: Connection::session()?, + is_idle: false, }; idle::SinceLastInput::seconds_since_input(&mut watcher)?; Ok(watcher) } - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - let mut is_idle = false; - info!("Starting idle watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; + fn run_iteration(&mut self, client: &Arc) { + match idle::ping_since_last_input(self, self.is_idle, client) { + Ok(is_idle_again) => { + self.is_idle = is_idle_again; } - match idle::ping_since_last_input(self, is_idle, client) { - Ok(is_idle_again) => { - is_idle = is_idle_again; - } - Err(e) => error!("Error on idle iteration: {e}"), - }; - thread::sleep(client.config.poll_time_idle); - } + Err(e) => error!("Error on idle iteration: {e}"), + }; } } diff --git a/watchers/src/watchers/gnome_window.rs b/watchers/src/watchers/gnome_window.rs index 89ab53d..8208795 100644 --- a/watchers/src/watchers/gnome_window.rs +++ b/watchers/src/watchers/gnome_window.rs @@ -1,9 +1,7 @@ use crate::report_client::ReportClient; use anyhow::Context; use serde::Deserialize; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::thread; use zbus::blocking::Connection; use super::Watcher; @@ -77,7 +75,7 @@ impl WindowWatcher { } impl Watcher for WindowWatcher { - fn new() -> anyhow::Result { + fn new(_: &Arc) -> anyhow::Result { let watcher = Self { dbus_connection: Connection::session()?, last_app_id: String::new(), @@ -87,17 +85,9 @@ impl Watcher for WindowWatcher { Ok(watcher) } - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - info!("Starting active window watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; - } - if let Err(error) = self.send_active_window(client) { - error!("Error on active window: {error}"); - } - thread::sleep(client.config.poll_time_window); + fn run_iteration(&mut self, client: &Arc) { + if let Err(error) = self.send_active_window(client) { + error!("Error on active window: {error}"); } } } diff --git a/watchers/src/watchers/kwin_window.rs b/watchers/src/watchers/kwin_window.rs index 1b0ab72..b34f9e2 100644 --- a/watchers/src/watchers/kwin_window.rs +++ b/watchers/src/watchers/kwin_window.rs @@ -8,7 +8,6 @@ use crate::report_client::ReportClient; use anyhow::{anyhow, Context}; use std::env::temp_dir; use std::path::Path; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{mpsc::channel, Arc, Mutex}; use std::thread; use zbus::blocking::{Connection, ConnectionBuilder}; @@ -151,22 +150,19 @@ impl ActiveWindowInterface { } pub struct WindowWatcher { - kwin_script: KWinScript, + active_window: Arc>, + // Prolong its lifetime + _kwin_script: KWinScript, } impl Watcher for WindowWatcher { - fn new() -> anyhow::Result { - let kwin_script = KWinScript::new(Connection::session()?); + fn new(_: &Arc) -> anyhow::Result { + let mut kwin_script = KWinScript::new(Connection::session()?); if kwin_script.is_loaded()? { debug!("KWin script is already loaded, unloading"); kwin_script.unload()?; } - - Ok(Self { kwin_script }) - } - - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - self.kwin_script.load().unwrap(); + kwin_script.load().unwrap(); let active_window = Arc::new(Mutex::new(ActiveWindow { caption: String::new(), @@ -199,16 +195,15 @@ impl Watcher for WindowWatcher { panic!("Failed to run a DBus interface: {error}"); } - info!("Starting active window watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; - } - if let Err(error) = send_active_window(client, &active_window) { - error!("Error on sending active window: {error}"); - } - thread::sleep(client.config.poll_time_window); + Ok(Self { + active_window, + _kwin_script: kwin_script, + }) + } + + fn run_iteration(&mut self, client: &Arc) { + if let Err(error) = send_active_window(client, &self.active_window) { + error!("Error on sending active window: {error}"); } } } diff --git a/watchers/src/watchers/wl_foreign_toplevel.rs b/watchers/src/watchers/wl_foreign_toplevel.rs index 2867c47..ce0abdd 100644 --- a/watchers/src/watchers/wl_foreign_toplevel.rs +++ b/watchers/src/watchers/wl_foreign_toplevel.rs @@ -9,8 +9,7 @@ use super::{wl_connection::subscribe_state, Watcher}; use crate::report_client::ReportClient; use anyhow::{anyhow, Context}; use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::{sync::Arc, thread}; +use std::sync::Arc; use wayland_client::{ event_created_child, globals::GlobalListContents, protocol::wl_registry, Connection, Dispatch, Proxy, QueueHandle, @@ -24,15 +23,13 @@ struct WindowData { struct ToplevelState { windows: HashMap, current_window_id: Option, - client: Arc, } impl ToplevelState { - fn new(client: Arc) -> Self { + fn new() -> Self { Self { windows: HashMap::new(), current_window_id: None, - client, } } } @@ -114,55 +111,59 @@ impl Dispatch for ToplevelState { } } -impl ToplevelState { - fn send_active_window(&self) -> anyhow::Result<()> { +pub struct WindowWatcher { + connection: WlEventConnection, + toplevel_state: ToplevelState, +} + +impl WindowWatcher { + fn send_active_window(&self, client: &Arc) -> anyhow::Result<()> { let active_window_id = self + .toplevel_state .current_window_id .as_ref() .ok_or(anyhow!("Current window is unknown"))?; - let active_window = self.windows.get(active_window_id).ok_or(anyhow!( - "Current window is not found by ID {active_window_id}" - ))?; + let active_window = self + .toplevel_state + .windows + .get(active_window_id) + .ok_or(anyhow!( + "Current window is not found by ID {active_window_id}" + ))?; - self.client + client .send_active_window(&active_window.app_id, &active_window.title) .with_context(|| "Failed to send heartbeat for active window") } } -pub struct WindowWatcher { - connection: WlEventConnection, -} - impl Watcher for WindowWatcher { - fn new() -> anyhow::Result { - let connection: WlEventConnection = WlEventConnection::connect()?; + fn new(_: &Arc) -> anyhow::Result { + let mut connection: WlEventConnection = WlEventConnection::connect()?; connection.get_foreign_toplevel_manager()?; - Ok(Self { connection }) - } + let mut toplevel_state = ToplevelState::new(); - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - let mut toplevel_state = ToplevelState::new(Arc::clone(client)); - - self.connection + connection .event_queue .roundtrip(&mut toplevel_state) .unwrap(); - info!("Starting wlr foreign toplevel watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; - } - if let Err(e) = self.connection.event_queue.roundtrip(&mut toplevel_state) { - error!("Event queue is not processed: {e}"); - } else if let Err(e) = toplevel_state.send_active_window() { - error!("Error on iteration: {e}"); - } + Ok(Self { + connection, + toplevel_state, + }) + } - thread::sleep(client.config.poll_time_window); + fn run_iteration(&mut self, client: &Arc) { + if let Err(e) = self + .connection + .event_queue + .roundtrip(&mut self.toplevel_state) + { + error!("Event queue is not processed: {e}"); + } else if let Err(e) = self.send_active_window(client) { + error!("Error on iteration: {e}"); } } } diff --git a/watchers/src/watchers/wl_kwin_idle.rs b/watchers/src/watchers/wl_kwin_idle.rs index c2d1435..7dbcb9c 100644 --- a/watchers/src/watchers/wl_kwin_idle.rs +++ b/watchers/src/watchers/wl_kwin_idle.rs @@ -3,8 +3,7 @@ use super::wl_connection::{subscribe_state, WlEventConnection}; use super::Watcher; use crate::report_client::ReportClient; use chrono::{DateTime, Duration, Utc}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::{sync::Arc, thread}; +use std::sync::Arc; use wayland_client::{ globals::GlobalListContents, protocol::{wl_registry, wl_seat::WlSeat}, @@ -20,7 +19,6 @@ struct IdleState { last_input_time: DateTime, is_idle: bool, is_changed: bool, - client: Arc, } impl Drop for IdleState { @@ -31,13 +29,12 @@ impl Drop for IdleState { } impl IdleState { - fn new(idle_timeout: OrgKdeKwinIdleTimeout, client: Arc) -> Self { + fn new(idle_timeout: OrgKdeKwinIdleTimeout) -> Self { Self { idle_timeout, last_input_time: Utc::now(), is_idle: false, is_changed: false, - client, } } @@ -54,7 +51,7 @@ impl IdleState { debug!("Resumed"); } - fn send_ping(&mut self) -> anyhow::Result<()> { + fn send_ping(&mut self, client: &Arc) -> anyhow::Result<()> { let now = Utc::now(); if !self.is_idle { self.last_input_time = now; @@ -63,9 +60,8 @@ impl IdleState { if self.is_changed { let result = if self.is_idle { debug!("Reporting as changed to idle"); - self.client - .ping(false, self.last_input_time, Duration::zero())?; - self.client.ping( + client.ping(false, self.last_input_time, Duration::zero())?; + client.ping( true, self.last_input_time + Duration::milliseconds(1), Duration::zero(), @@ -73,9 +69,8 @@ impl IdleState { } else { debug!("Reporting as no longer idle"); - self.client - .ping(true, self.last_input_time, Duration::zero())?; - self.client.ping( + client.ping(true, self.last_input_time, Duration::zero())?; + client.ping( false, self.last_input_time + Duration::milliseconds(1), Duration::zero(), @@ -85,12 +80,10 @@ impl IdleState { result } else if self.is_idle { trace!("Reporting as idle"); - self.client - .ping(true, self.last_input_time, now - self.last_input_time) + client.ping(true, self.last_input_time, now - self.last_input_time) } else { trace!("Reporting as not idle"); - self.client - .ping(false, self.last_input_time, Duration::zero()) + client.ping(false, self.last_input_time, Duration::zero()) } } } @@ -119,41 +112,30 @@ impl Dispatch for IdleState { pub struct IdleWatcher { connection: WlEventConnection, + idle_state: IdleState, } impl Watcher for IdleWatcher { - fn new() -> anyhow::Result { - let connection: WlEventConnection = WlEventConnection::connect()?; + fn new(client: &Arc) -> anyhow::Result { + let mut connection: WlEventConnection = WlEventConnection::connect()?; connection.get_kwin_idle()?; - Ok(Self { connection }) + let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000); + let mut idle_state = + IdleState::new(connection.get_kwin_idle_timeout(timeout.unwrap()).unwrap()); + connection.event_queue.roundtrip(&mut idle_state).unwrap(); + + Ok(Self { + connection, + idle_state, + }) } - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000); - let mut idle_state = IdleState::new( - self.connection - .get_kwin_idle_timeout(timeout.unwrap()) - .unwrap(), - Arc::clone(client), - ); - self.connection - .event_queue - .roundtrip(&mut idle_state) - .unwrap(); - - info!("Starting idle watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; - } - if let Err(e) = self.connection.event_queue.roundtrip(&mut idle_state) { - error!("Event queue is not processed: {e}"); - } else if let Err(e) = idle_state.send_ping() { - error!("Error on idle iteration: {e}"); - } - thread::sleep(client.config.poll_time_idle); + fn run_iteration(&mut self, client: &Arc) { + if let Err(e) = self.connection.event_queue.roundtrip(&mut self.idle_state) { + error!("Event queue is not processed: {e}"); + } else if let Err(e) = self.idle_state.send_ping(client) { + error!("Error on idle iteration: {e}"); } } } diff --git a/watchers/src/watchers/x11_screensaver_idle.rs b/watchers/src/watchers/x11_screensaver_idle.rs index 014af3a..7a79688 100644 --- a/watchers/src/watchers/x11_screensaver_idle.rs +++ b/watchers/src/watchers/x11_screensaver_idle.rs @@ -1,11 +1,10 @@ use super::{idle, x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::thread; pub struct IdleWatcher { client: X11Client, + is_idle: bool, } impl idle::SinceLastInput for IdleWatcher { @@ -15,31 +14,24 @@ impl idle::SinceLastInput for IdleWatcher { } impl Watcher for IdleWatcher { - fn new() -> anyhow::Result { + fn new(_: &Arc) -> anyhow::Result { let mut client = X11Client::new()?; // Check if screensaver extension is supported client.seconds_since_last_input()?; - Ok(IdleWatcher { client }) + Ok(IdleWatcher { + client, + is_idle: false, + }) } - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - info!("Starting idle watcher"); - let mut is_idle = false; - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; + fn run_iteration(&mut self, client: &Arc) { + match idle::ping_since_last_input(self, self.is_idle, client) { + Ok(is_idle_again) => { + self.is_idle = is_idle_again; } - match idle::ping_since_last_input(self, is_idle, client) { - Ok(is_idle_again) => { - is_idle = is_idle_again; - } - Err(e) => error!("Error on idle iteration: {e}"), - }; - - thread::sleep(client.config.poll_time_idle); - } + Err(e) => error!("Error on idle iteration: {e}"), + }; } } diff --git a/watchers/src/watchers/x11_window.rs b/watchers/src/watchers/x11_window.rs index 148b30a..b75ed84 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -1,9 +1,7 @@ use super::{x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; use anyhow::Context; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::thread; pub struct WindowWatcher { client: X11Client, @@ -31,7 +29,7 @@ impl WindowWatcher { } impl Watcher for WindowWatcher { - fn new() -> anyhow::Result { + fn new(_: &Arc) -> anyhow::Result { let mut client = X11Client::new()?; client.active_window_data()?; @@ -42,18 +40,9 @@ impl Watcher for WindowWatcher { }) } - fn watch(&mut self, client: &Arc, is_stopped: Arc) { - info!("Starting active window watcher"); - loop { - if is_stopped.load(Ordering::Relaxed) { - warn!("Received an exit signal, shutting down"); - break; - } - if let Err(error) = self.send_active_window(client) { - error!("Error on sending active window: {error}"); - } - - thread::sleep(client.config.poll_time_window); + fn run_iteration(&mut self, client: &Arc) { + if let Err(error) = self.send_active_window(client) { + error!("Error on sending active window: {error}"); } } }