diff --git a/src/main.rs b/src/main.rs index 5259fec..fa55dce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,8 @@ use fern::colors::{Color, ColoredLevelConfig}; use report_client::ReportClient; use std::env; use std::{error::Error, str::FromStr, sync::Arc, thread}; -use wl_kwin_idle::run as run_kwin_idle; -use wl_kwin_window::run as run_kwin_active_window; +use wl_kwin_idle::KwinIdleWatcher; +use wl_kwin_window::KwinWindowWatcher; type BoxedError = Box; @@ -47,6 +47,14 @@ fn setup_logger() -> Result<(), fern::InitError> { Ok(()) } +type WatcherConstructor = fn() -> Result, BoxedError>; +trait Watcher: Send { + fn new() -> Result + where + Self: Sized; + fn watch(&mut self, client: &Arc); +} + fn main() { setup_logger().unwrap(); @@ -60,14 +68,39 @@ fn main() { info!("Idle timeout: {} seconds", client.config.idle_timeout); info!("Polling period: {} seconds", client.config.poll_time_idle); - let client1 = Arc::clone(&client); - let idle_handler = thread::spawn(move || run_kwin_idle(&client1)); + let mut thread_handlers = Vec::new(); + let idle_watchers: Vec = vec![|| Ok(Box::new(KwinIdleWatcher::new()?))]; + let window_watchers: Vec = vec![|| Ok(Box::new(KwinWindowWatcher::new()?))]; - let client2 = Arc::clone(&client); - let active_window_handler = thread::spawn(move || run_kwin_active_window(&client2)); + let filter_watcher = |watchers: Vec| { + watchers.iter().find_map(|watcher| match watcher() { + Ok(watcher) => Some(watcher), + Err(e) => { + info!("Watcher cannot run: {e}"); + None + } + }) + }; - idle_handler.join().expect("Idle thread failed"); - active_window_handler - .join() - .expect("Active window thread failed"); + let idle_watcher = filter_watcher(idle_watchers); + if let Some(mut watcher) = idle_watcher { + let thread_client = Arc::clone(&client); + let idle_handler = thread::spawn(move || watcher.watch(&thread_client)); + thread_handlers.push(idle_handler); + } else { + warn!("No supported idle handler is found"); + } + + let window_watcher = filter_watcher(window_watchers); + if let Some(mut watcher) = window_watcher { + let thread_client = Arc::clone(&client); + let active_window_handler = thread::spawn(move || watcher.watch(&thread_client)); + thread_handlers.push(active_window_handler); + } else { + warn!("No supported active window handler is found"); + } + + for handler in thread_handlers { + handler.join().unwrap(); + } } diff --git a/src/wl_connection.rs b/src/wl_connection.rs index 81bff96..1c88c9d 100644 --- a/src/wl_connection.rs +++ b/src/wl_connection.rs @@ -37,7 +37,20 @@ where }) } - pub fn get_idle_timeout(&self, timeout: u32) -> Result + pub fn get_kwin_idle(&self) -> Result + where + T: Dispatch, + { + self.globals + .bind::( + &self.queue_handle, + 1..=OrgKdeKwinIdle::interface().version, + (), + ) + .map_err(std::convert::Into::into) + } + + pub fn get_kwin_idle_timeout(&self, timeout: u32) -> Result where T: Dispatch + Dispatch @@ -47,11 +60,7 @@ where self.globals .bind(&self.queue_handle, 1..=WlSeat::interface().version, ())?; - let idle: OrgKdeKwinIdle = self.globals.bind( - &self.queue_handle, - 1..=OrgKdeKwinIdle::interface().version, - (), - )?; + let idle = self.get_kwin_idle()?; Ok(idle.get_idle_timeout(&seat, timeout, &self.queue_handle, ())) } } diff --git a/src/wl_kwin_idle.rs b/src/wl_kwin_idle.rs index 80ab82c..3fe60bb 100644 --- a/src/wl_kwin_idle.rs +++ b/src/wl_kwin_idle.rs @@ -1,3 +1,5 @@ +use crate::Watcher; + use super::report_client::ReportClient; use super::wl_bindings; use super::wl_connection::WlEventConnection; @@ -60,7 +62,6 @@ impl IdleState { } fn run_loop(&mut self, connection: &mut WlEventConnection) -> Result<(), BoxedError> { - // connection.event_queue.blocking_dispatch(self).unwrap(); connection.event_queue.roundtrip(self).unwrap(); let now = Utc::now(); if !self.is_idle { @@ -158,32 +159,46 @@ impl Dispatch for IdleState { } } -pub fn run(client: &Arc) { - let bucket_name = format!( - "aw-watcher-afk_{}", - gethostname::gethostname().into_string().unwrap() - ); +pub struct KwinIdleWatcher { + connection: WlEventConnection, +} - client.create_bucket(&bucket_name, "afkstatus").unwrap(); +impl Watcher for KwinIdleWatcher { + fn new() -> Result { + let connection: WlEventConnection = WlEventConnection::connect()?; + connection.get_kwin_idle()?; - let mut connection = WlEventConnection::connect().unwrap(); + Ok(Self { connection }) + } - let mut idle_state = IdleState::new( - connection - .get_idle_timeout(client.config.idle_timeout * 1000) - .unwrap(), - Arc::clone(client), - bucket_name, - ); - connection.event_queue.roundtrip(&mut idle_state).unwrap(); + fn watch(&mut self, client: &Arc) { + let bucket_name = format!( + "aw-watcher-afk_{}", + gethostname::gethostname().into_string().unwrap() + ); - info!("Starting idle watcher"); - loop { - if let Err(e) = idle_state.run_loop(&mut connection) { - error!("Error on idle iteration {e}"); + client.create_bucket(&bucket_name, "afkstatus").unwrap(); + + let mut idle_state = IdleState::new( + self.connection + .get_kwin_idle_timeout(client.config.idle_timeout * 1000) + .unwrap(), + Arc::clone(client), + bucket_name, + ); + self.connection + .event_queue + .roundtrip(&mut idle_state) + .unwrap(); + + info!("Starting idle watcher"); + loop { + if let Err(e) = idle_state.run_loop(&mut self.connection) { + error!("Error on idle iteration {e}"); + } + thread::sleep(time::Duration::from_secs(u64::from( + client.config.poll_time_idle, + ))); } - thread::sleep(time::Duration::from_secs(u64::from( - client.config.poll_time_idle, - ))); } } diff --git a/src/wl_kwin_window.rs b/src/wl_kwin_window.rs index 9a8b3f0..b3995ba 100644 --- a/src/wl_kwin_window.rs +++ b/src/wl_kwin_window.rs @@ -1,3 +1,5 @@ +use crate::Watcher; + /* * This uses a hack with KWin scripts in order to receive the active window. * For the moment of writing, KWin doesn't implement the appropriate protocols to get a top level window. @@ -29,11 +31,6 @@ impl KWinScript { } fn load(&self) -> Result<(), BoxedError> { - if self.is_loaded()? { - warn!("KWin script is already loaded, unloading"); - self.unload().unwrap(); - } - let path = temp_dir().join("kwin_window.js"); std::fs::write(&path, KWIN_SCRIPT).unwrap(); @@ -168,49 +165,64 @@ impl ActiveWindowInterface { } } -pub fn run(client: &Arc) { - let hostname = gethostname::gethostname().into_string().unwrap(); - let bucket_name = format!("aw-watcher-window_{hostname}"); - let kwin_script = KWinScript::new(Connection::session().unwrap()); +pub struct KwinWindowWatcher { + kwin_script: KWinScript, +} - kwin_script.load().unwrap(); +impl Watcher for KwinWindowWatcher { + fn new() -> Result { + let kwin_script = KWinScript::new(Connection::session()?); + if kwin_script.is_loaded()? { + warn!("KWin script is already loaded, unloading"); + kwin_script.unload()?; + } - let active_window = Arc::new(Mutex::new(ActiveWindow { - caption: String::new(), - resource_name: String::new(), - resource_class: String::new(), - })); - let active_window_interface = ActiveWindowInterface { - active_window: Arc::clone(&active_window), - }; + Ok(Self { kwin_script }) + } - let (tx, rx) = channel(); - thread::spawn(move || { - let result = (|| { - ConnectionBuilder::session()? - .name("com._2e3s.Awatcher")? - .serve_at("/com/_2e3s/Awatcher", active_window_interface)? - .build() - })(); - match result { - Ok(connection) => { - tx.send(Ok(())).unwrap(); - loop { - connection.monitor_activity().wait(); + fn watch(&mut self, client: &Arc) { + let hostname = gethostname::gethostname().into_string().unwrap(); + let bucket_name = format!("aw-watcher-window_{hostname}"); + + self.kwin_script.load().unwrap(); + + let active_window = Arc::new(Mutex::new(ActiveWindow { + caption: String::new(), + resource_name: String::new(), + resource_class: String::new(), + })); + let active_window_interface = ActiveWindowInterface { + active_window: Arc::clone(&active_window), + }; + + let (tx, rx) = channel(); + thread::spawn(move || { + let result = (|| { + ConnectionBuilder::session()? + .name("com._2e3s.Awatcher")? + .serve_at("/com/_2e3s/Awatcher", active_window_interface)? + .build() + })(); + match result { + Ok(connection) => { + tx.send(Ok(())).unwrap(); + loop { + connection.monitor_activity().wait(); + } } + Err(e) => tx.send(Err(e)), } - Err(e) => tx.send(Err(e)), - } - }); - let _ = rx.recv().unwrap(); + }); + let _ = rx.recv().unwrap(); - info!("Starting active window watcher"); - loop { - if let Err(error) = send_heartbeat(client, &bucket_name, &active_window) { - error!("Error on sending active window heartbeat: {error}"); + info!("Starting active window watcher"); + loop { + if let Err(error) = send_heartbeat(client, &bucket_name, &active_window) { + error!("Error on sending active window heartbeat: {error}"); + } + thread::sleep(time::Duration::from_secs(u64::from( + client.config.poll_time_window, + ))); } - thread::sleep(time::Duration::from_secs(u64::from( - client.config.poll_time_window, - ))); } }