diff --git a/src/main.rs b/src/main.rs index fbe14be..ec6cc6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn main() -> Result<(), BoxedError> { ); info!( "Window polling period: {} seconds", - client.config.poll_time_idle.as_secs() + client.config.poll_time_window.as_secs() ); let mut thread_handlers = Vec::new(); diff --git a/src/watchers.rs b/src/watchers.rs index 36b86d0..8dc0736 100644 --- a/src/watchers.rs +++ b/src/watchers.rs @@ -1,3 +1,5 @@ +mod gnome_idle; +mod idle; mod kwin_window; mod wl_bindings; mod wl_connection; @@ -49,6 +51,7 @@ macro_rules! watcher { pub const IDLE: &WatcherConstructors = &[ watcher!(wl_kwin_idle::IdleWatcher), watcher!(x11_screensaver_idle::IdleWatcher), + watcher!(gnome_idle::IdleWatcher), ]; pub const ACTIVE_WINDOW: &WatcherConstructors = &[ diff --git a/src/watchers/gnome_idle.rs b/src/watchers/gnome_idle.rs new file mode 100644 index 0000000..f8442ca --- /dev/null +++ b/src/watchers/gnome_idle.rs @@ -0,0 +1,49 @@ +use super::{idle, Watcher}; +use crate::{report_client::ReportClient, BoxedError}; +use std::{sync::Arc, thread}; +use zbus::blocking::Connection; + +pub struct IdleWatcher { + dbus_connection: Connection, +} + +impl idle::SinceLastInput for IdleWatcher { + fn seconds_since_input(&self) -> Result { + let ms = self + .dbus_connection + .call_method( + Some("org.gnome.Mutter.IdleMonitor"), + "/org/gnome/Mutter/IdleMonitor/Core", + Some("org.gnome.Mutter.IdleMonitor"), + "GetIdletime", + &(), + )? + .body::()?; + u32::try_from(ms / 1000).map_err(|_| format!("Number {ms} is invalid").into()) + } +} + +impl Watcher for IdleWatcher { + fn new() -> Result { + let watcher = Self { + dbus_connection: Connection::session()?, + }; + idle::SinceLastInput::seconds_since_input(&watcher)?; + + Ok(watcher) + } + + fn watch(&mut self, client: &Arc) { + let mut is_idle = false; + info!("Starting idle watcher"); + loop { + 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); + } + } +} diff --git a/src/watchers/idle.rs b/src/watchers/idle.rs new file mode 100644 index 0000000..fb1b861 --- /dev/null +++ b/src/watchers/idle.rs @@ -0,0 +1,49 @@ +use crate::{report_client::ReportClient, BoxedError}; +use chrono::{Duration, Utc}; +use std::sync::Arc; + +pub trait SinceLastInput { + fn seconds_since_input(&self) -> Result; +} + +pub fn ping_since_last_input( + watcher: &impl SinceLastInput, + is_idle: bool, + client: &Arc, +) -> Result { + // The logic is rewritten from the original Python code: + // https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73 + let duration_1ms: Duration = Duration::milliseconds(1); + let duration_zero: Duration = Duration::zero(); + + let seconds_since_input = watcher.seconds_since_input()?; + let now = Utc::now(); + let time_since_input = Duration::seconds(i64::from(seconds_since_input)); + let last_input = now - time_since_input; + let mut is_idle_again = is_idle; + + if is_idle && u64::from(seconds_since_input) < client.config.idle_timeout.as_secs() { + debug!("No longer idle"); + client.ping(is_idle, last_input, duration_zero)?; + is_idle_again = false; + // ping with timestamp+1ms with the next event (to ensure the latest event gets retrieved by get_event) + client.ping(is_idle, last_input + duration_1ms, duration_zero)?; + } else if !is_idle && u64::from(seconds_since_input) >= client.config.idle_timeout.as_secs() { + debug!("Idle again"); + client.ping(is_idle, last_input, duration_zero)?; + is_idle_again = true; + // ping with timestamp+1ms with the next event (to ensure the latest event gets retrieved by get_event) + client.ping(is_idle, last_input + duration_1ms, time_since_input)?; + } else { + // Send a heartbeat if no state change was made + if is_idle { + trace!("Reporting as idle"); + client.ping(is_idle, last_input, time_since_input)?; + } else { + trace!("Reporting as not idle"); + client.ping(is_idle, last_input, duration_zero)?; + } + } + + Ok(is_idle_again) +} diff --git a/src/watchers/x11_screensaver_idle.rs b/src/watchers/x11_screensaver_idle.rs index dbd6c12..6779bf7 100644 --- a/src/watchers/x11_screensaver_idle.rs +++ b/src/watchers/x11_screensaver_idle.rs @@ -1,51 +1,15 @@ use std::{sync::Arc, thread}; -use super::{x11_connection::X11Connection, BoxedError, Watcher}; +use super::{idle, x11_connection::X11Connection, BoxedError, Watcher}; use crate::report_client::ReportClient; -use chrono::{Duration, Utc}; pub struct IdleWatcher { connection: X11Connection, } -impl IdleWatcher { - fn run(&self, is_idle: bool, client: &Arc) -> Result { - // The logic is rewritten from the original Python code: - // https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73 - let duration_1ms: Duration = Duration::milliseconds(1); - let duration_zero: Duration = Duration::zero(); - - let seconds_since_input = self.connection.seconds_since_last_input()?; - let now = Utc::now(); - let time_since_input = Duration::seconds(i64::from(seconds_since_input)); - let last_input = now - time_since_input; - let mut is_idle_again = is_idle; - - if is_idle && u64::from(seconds_since_input) < client.config.idle_timeout.as_secs() { - debug!("No longer idle"); - client.ping(is_idle, last_input, duration_zero)?; - is_idle_again = false; - // ping with timestamp+1ms with the next event (to ensure the latest event gets retrieved by get_event) - client.ping(is_idle, last_input + duration_1ms, duration_zero)?; - } else if !is_idle && u64::from(seconds_since_input) >= client.config.idle_timeout.as_secs() - { - debug!("Idle again"); - client.ping(is_idle, last_input, duration_zero)?; - is_idle_again = true; - // ping with timestamp+1ms with the next event (to ensure the latest event gets retrieved by get_event) - client.ping(is_idle, last_input + duration_1ms, time_since_input)?; - } else { - // Send a heartbeat if no state change was made - if is_idle { - trace!("Reporting as idle"); - client.ping(is_idle, last_input, time_since_input)?; - } else { - trace!("Reporting as not idle"); - client.ping(is_idle, last_input, duration_zero)?; - } - } - - Ok(is_idle_again) +impl idle::SinceLastInput for IdleWatcher { + fn seconds_since_input(&self) -> Result { + self.connection.seconds_since_last_input() } } @@ -63,7 +27,7 @@ impl Watcher for IdleWatcher { info!("Starting idle watcher"); let mut is_idle = false; loop { - match self.run(is_idle, client) { + match idle::ping_since_last_input(self, is_idle, client) { Ok(is_idle_again) => { is_idle = is_idle_again; }