From ea215b432dcca78fb448f8d790072e198f0afd33 Mon Sep 17 00:00:00 2001 From: Demmie <2e3s19@gmail.com> Date: Thu, 16 May 2024 15:36:04 -0400 Subject: [PATCH] Unify idle logic --- watchers/src/watchers/gnome_idle.rs | 37 +++-- watchers/src/watchers/gnome_wayland.rs | 17 +- watchers/src/watchers/gnome_window.rs | 27 ++- watchers/src/watchers/idle.rs | 154 +++++++++++++----- watchers/src/watchers/wl_ext_idle_notify.rs | 104 +++--------- watchers/src/watchers/wl_kwin_idle.rs | 104 +++--------- watchers/src/watchers/x11_screensaver_idle.rs | 16 +- 7 files changed, 210 insertions(+), 249 deletions(-) diff --git a/watchers/src/watchers/gnome_idle.rs b/watchers/src/watchers/gnome_idle.rs index e72d7e5..a7a2bb4 100644 --- a/watchers/src/watchers/gnome_idle.rs +++ b/watchers/src/watchers/gnome_idle.rs @@ -1,16 +1,17 @@ -use super::{gnome_wayland::load_watcher, gnome_wayland::GnomeWatcher, idle, Watcher}; +use super::{gnome_wayland::load_watcher, idle, Watcher}; use crate::report_client::ReportClient; use anyhow::Context; use async_trait::async_trait; +use chrono::Duration; use std::sync::Arc; use zbus::Connection; pub struct IdleWatcher { dbus_connection: Connection, - is_idle: bool, + idle_state: idle::State, } -impl idle::SinceLastInput for IdleWatcher { +impl IdleWatcher { async fn seconds_since_input(&mut self) -> anyhow::Result { let ms = self .dbus_connection @@ -27,26 +28,26 @@ impl idle::SinceLastInput for IdleWatcher { } } -impl GnomeWatcher for IdleWatcher { - async fn load() -> anyhow::Result { - let mut watcher = Self { - dbus_connection: Connection::session().await?, - is_idle: false, - }; - idle::SinceLastInput::seconds_since_input(&mut watcher).await?; - - Ok(watcher) - } -} - #[async_trait] impl Watcher for IdleWatcher { - async fn new(_: &Arc) -> anyhow::Result { - load_watcher().await + async fn new(client: &Arc) -> anyhow::Result { + let duration = Duration::from_std(client.config.idle_timeout).unwrap(); + load_watcher(|| async move { + let mut watcher = Self { + dbus_connection: Connection::session().await?, + idle_state: idle::State::new(duration), + }; + watcher.seconds_since_input().await?; + Ok(watcher) + }) + .await } async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { - self.is_idle = idle::ping_since_last_input(self, self.is_idle, client).await?; + let seconds = self.seconds_since_input().await?; + self.idle_state + .send_with_last_input(seconds, client) + .await?; Ok(()) } diff --git a/watchers/src/watchers/gnome_wayland.rs b/watchers/src/watchers/gnome_wayland.rs index dc3376d..7f8e655 100644 --- a/watchers/src/watchers/gnome_wayland.rs +++ b/watchers/src/watchers/gnome_wayland.rs @@ -1,9 +1,4 @@ -// The extension may not be loaded and available right away in Gnome, this mod will retry a few times. -pub trait GnomeWatcher { - async fn load() -> anyhow::Result - where - Self: Sized; -} +use std::future::Future; fn is_gnome() -> bool { if let Ok(de) = std::env::var("XDG_CURRENT_DESKTOP") { @@ -21,12 +16,16 @@ fn is_wayland() -> bool { .contains("wayland") } -pub async fn load_watcher() -> anyhow::Result { +pub async fn load_watcher(loader: F) -> anyhow::Result +where + F: Fn() -> Fut, + Fut: Future>, +{ if is_gnome() && is_wayland() { debug!("Gnome Wayland detected"); let mut watcher = Err(anyhow::anyhow!("")); for _ in 0..3 { - watcher = T::load().await; + watcher = loader().await; if let Err(e) = &watcher { debug!("Failed to load Gnome Wayland watcher: {e}"); tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; @@ -35,6 +34,6 @@ pub async fn load_watcher() -> anyhow::Result { watcher } else { - T::load().await + loader().await } } diff --git a/watchers/src/watchers/gnome_window.rs b/watchers/src/watchers/gnome_window.rs index 4f5bebf..5b3d562 100644 --- a/watchers/src/watchers/gnome_window.rs +++ b/watchers/src/watchers/gnome_window.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use std::sync::Arc; use zbus::Connection; -use super::{gnome_wayland::load_watcher, gnome_wayland::GnomeWatcher, Watcher}; +use super::{gnome_wayland::load_watcher, Watcher}; pub struct WindowWatcher { dbus_connection: Connection, @@ -79,23 +79,20 @@ impl WindowWatcher { } } -impl GnomeWatcher for WindowWatcher { - async fn load() -> anyhow::Result { - let watcher = Self { - dbus_connection: Connection::session().await?, - last_app_id: String::new(), - last_title: String::new(), - }; - watcher.get_window_data().await?; - - Ok(watcher) - } -} - #[async_trait] impl Watcher for WindowWatcher { async fn new(_: &Arc) -> anyhow::Result { - load_watcher().await + load_watcher(|| async move { + let watcher = Self { + dbus_connection: Connection::session().await?, + last_app_id: String::new(), + last_title: String::new(), + }; + watcher.get_window_data().await?; + + Ok(watcher) + }) + .await } async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { diff --git a/watchers/src/watchers/idle.rs b/watchers/src/watchers/idle.rs index 895daf7..3a32d06 100644 --- a/watchers/src/watchers/idle.rs +++ b/watchers/src/watchers/idle.rs @@ -1,53 +1,119 @@ use crate::report_client::ReportClient; -use chrono::{Duration, Utc}; +use chrono::{DateTime, Duration, Utc}; use std::sync::Arc; -pub trait SinceLastInput { - async fn seconds_since_input(&mut self) -> anyhow::Result; +pub struct State { + last_input_time: DateTime, + is_idle: bool, + is_changed: bool, + idle_timeout: Duration, } -pub async fn ping_since_last_input( - watcher: &mut impl SinceLastInput, - is_idle: bool, - client: &Arc, -) -> anyhow::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().await?; - 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).await?; - 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) - .await?; - } 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).await?; - 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) - .await?; - } 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).await?; - } else { - trace!("Reporting as not idle"); - client.ping(is_idle, last_input, duration_zero).await?; +impl State { + pub fn new(idle_timeout: Duration) -> Self { + Self { + last_input_time: Utc::now(), + is_idle: false, + is_changed: false, + idle_timeout, } } - Ok(is_idle_again) + pub fn mark_not_idle(&mut self) { + self.is_idle = false; + self.is_changed = true; + self.last_input_time = Utc::now(); + } + + pub fn mark_idle(&mut self) { + self.is_idle = true; + self.is_changed = true; + self.last_input_time -= self.idle_timeout; + } + + // The logic is rewritten from the original Python code: + // https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73 + pub async fn send_with_last_input( + &mut self, + seconds_since_input: u32, + client: &Arc, + ) -> anyhow::Result<()> { + let time_since_input = Duration::seconds(i64::from(seconds_since_input)); + + self.last_input_time = Utc::now() - time_since_input; + + if self.is_idle + && u64::from(seconds_since_input) < self.idle_timeout.num_seconds().try_into().unwrap() + { + debug!("No longer idle"); + self.is_idle = false; + self.is_changed = true; + } else if !self.is_idle + && u64::from(seconds_since_input) >= self.idle_timeout.num_seconds().try_into().unwrap() + { + debug!("Idle again"); + self.is_idle = true; + self.is_changed = true; + } + + self.send_ping(client).await?; + + Ok(()) + } + + pub async fn send_reactive(&mut self, client: &Arc) -> anyhow::Result<()> { + let now = Utc::now(); + if !self.is_idle { + self.last_input_time = now; + } + + self.send_ping(client).await + } + + async fn send_ping(&mut self, client: &Arc) -> anyhow::Result<()> { + let now = Utc::now(); + + if self.is_changed { + let result = if self.is_idle { + debug!("Reporting as changed to idle"); + client + .ping(false, self.last_input_time, Duration::zero()) + .await?; + + // ping with timestamp+1ms with the next event (to ensure the latest event gets retrieved by get_event) + client + .ping( + true, + self.last_input_time + Duration::milliseconds(1), + Duration::zero(), + ) + .await + } else { + debug!("Reporting as no longer idle"); + + client + .ping(true, self.last_input_time, Duration::zero()) + .await?; + client + .ping( + false, + self.last_input_time + Duration::milliseconds(1), + Duration::zero(), + ) + .await + }; + self.is_changed = false; + result + } else if self.is_idle { + trace!("Reporting as idle"); + client + .ping(true, self.last_input_time, now - self.last_input_time) + .await + } else { + trace!("Reporting as not idle"); + client + .ping(false, self.last_input_time, Duration::zero()) + .await + } + } } diff --git a/watchers/src/watchers/wl_ext_idle_notify.rs b/watchers/src/watchers/wl_ext_idle_notify.rs index fbd9146..c923b59 100644 --- a/watchers/src/watchers/wl_ext_idle_notify.rs +++ b/watchers/src/watchers/wl_ext_idle_notify.rs @@ -1,9 +1,10 @@ +use super::idle; use super::wl_connection::{subscribe_state, WlEventConnection}; use super::Watcher; use crate::report_client::ReportClient; use anyhow::anyhow; use async_trait::async_trait; -use chrono::{DateTime, Duration, Utc}; +use chrono::Duration; use std::sync::Arc; use wayland_client::{ globals::GlobalListContents, @@ -14,101 +15,43 @@ use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::E use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notification_v1::ExtIdleNotificationV1; use wayland_protocols::ext::idle_notify::v1::client::ext_idle_notifier_v1::ExtIdleNotifierV1; -struct IdleState { +struct WatcherState { idle_notification: ExtIdleNotificationV1, - last_input_time: DateTime, - is_idle: bool, - is_changed: bool, - idle_timeout: Duration, + idle_state: idle::State, } -impl Drop for IdleState { +impl Drop for WatcherState { fn drop(&mut self) { info!("Releasing idle notification"); self.idle_notification.destroy(); } } -impl IdleState { +impl WatcherState { fn new(idle_notification: ExtIdleNotificationV1, idle_timeout: Duration) -> Self { Self { idle_notification, - last_input_time: Utc::now(), - is_idle: false, - is_changed: false, - idle_timeout, + idle_state: idle::State::new(idle_timeout), } } fn idle(&mut self) { - self.is_idle = true; - self.is_changed = true; - self.last_input_time -= self.idle_timeout; + self.idle_state.mark_idle(); debug!("Idle"); } fn resume(&mut self) { - self.is_idle = false; - self.last_input_time = Utc::now(); - self.is_changed = true; + self.idle_state.mark_not_idle(); debug!("Resumed"); } - - async fn send_ping(&mut self, client: &Arc) -> anyhow::Result<()> { - let now = Utc::now(); - if !self.is_idle { - self.last_input_time = now; - } - - if self.is_changed { - let result = if self.is_idle { - debug!("Reporting as changed to idle"); - client - .ping(false, self.last_input_time, Duration::zero()) - .await?; - client - .ping( - true, - self.last_input_time + Duration::milliseconds(1), - Duration::zero(), - ) - .await - } else { - debug!("Reporting as no longer idle"); - - client - .ping(true, self.last_input_time, Duration::zero()) - .await?; - client - .ping( - false, - self.last_input_time + Duration::milliseconds(1), - Duration::zero(), - ) - .await - }; - self.is_changed = false; - result - } else if self.is_idle { - trace!("Reporting as idle"); - client - .ping(true, self.last_input_time, now - self.last_input_time) - .await - } else { - trace!("Reporting as not idle"); - client - .ping(false, self.last_input_time, Duration::zero()) - .await - } - } } -subscribe_state!(wl_registry::WlRegistry, GlobalListContents, IdleState); -subscribe_state!(wl_registry::WlRegistry, (), IdleState); -subscribe_state!(WlSeat, (), IdleState); -subscribe_state!(ExtIdleNotifierV1, (), IdleState); +subscribe_state!(wl_registry::WlRegistry, GlobalListContents, WatcherState); +subscribe_state!(wl_registry::WlRegistry, (), WatcherState); +subscribe_state!(WlSeat, (), WatcherState); +subscribe_state!(ExtIdleNotifierV1, (), WatcherState); -impl Dispatch for IdleState { +impl Dispatch for WatcherState { fn event( state: &mut Self, _: &ExtIdleNotificationV1, @@ -126,37 +69,40 @@ impl Dispatch for IdleState { } pub struct IdleWatcher { - connection: WlEventConnection, - idle_state: IdleState, + connection: WlEventConnection, + watcher_state: WatcherState, } #[async_trait] impl Watcher for IdleWatcher { async fn new(client: &Arc) -> anyhow::Result { - let mut connection: WlEventConnection = WlEventConnection::connect()?; + let mut connection: WlEventConnection = WlEventConnection::connect()?; connection.get_ext_idle()?; let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000); - let mut idle_state = IdleState::new( + let mut watcher_state = WatcherState::new( connection .get_ext_idle_notification(timeout.unwrap()) .unwrap(), Duration::from_std(client.config.idle_timeout).unwrap(), ); - connection.event_queue.roundtrip(&mut idle_state).unwrap(); + connection + .event_queue + .roundtrip(&mut watcher_state) + .unwrap(); Ok(Self { connection, - idle_state, + watcher_state, }) } async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { self.connection .event_queue - .roundtrip(&mut self.idle_state) + .roundtrip(&mut self.watcher_state) .map_err(|e| anyhow!("Event queue is not processed: {e}"))?; - self.idle_state.send_ping(client).await + self.watcher_state.idle_state.send_reactive(client).await } } diff --git a/watchers/src/watchers/wl_kwin_idle.rs b/watchers/src/watchers/wl_kwin_idle.rs index e4a878f..551a288 100644 --- a/watchers/src/watchers/wl_kwin_idle.rs +++ b/watchers/src/watchers/wl_kwin_idle.rs @@ -1,9 +1,10 @@ +use super::idle; use super::wl_connection::{subscribe_state, WlEventConnection}; use super::Watcher; use crate::report_client::ReportClient; use anyhow::anyhow; use async_trait::async_trait; -use chrono::{DateTime, Duration, Utc}; +use chrono::Duration; use std::sync::Arc; use wayland_client::{ globals::GlobalListContents, @@ -15,101 +16,43 @@ use wayland_protocols_plasma::idle::client::org_kde_kwin_idle_timeout::{ Event as OrgKdeKwinIdleTimeoutEvent, OrgKdeKwinIdleTimeout, }; -struct IdleState { +struct WatcherState { kwin_idle_timeout: OrgKdeKwinIdleTimeout, - last_input_time: DateTime, - is_idle: bool, - is_changed: bool, - idle_timeout: Duration, + idle_state: idle::State, } -impl Drop for IdleState { +impl Drop for WatcherState { fn drop(&mut self) { info!("Releasing idle timeout"); self.kwin_idle_timeout.release(); } } -impl IdleState { +impl WatcherState { fn new(kwin_idle_timeout: OrgKdeKwinIdleTimeout, idle_timeout: Duration) -> Self { Self { kwin_idle_timeout, - last_input_time: Utc::now(), - is_idle: false, - is_changed: false, - idle_timeout, + idle_state: idle::State::new(idle_timeout), } } fn idle(&mut self) { - self.is_idle = true; - self.is_changed = true; - self.last_input_time -= self.idle_timeout; + self.idle_state.mark_idle(); debug!("Idle"); } fn resume(&mut self) { - self.is_idle = false; - self.last_input_time = Utc::now(); - self.is_changed = true; + self.idle_state.mark_not_idle(); debug!("Resumed"); } - - async fn send_ping(&mut self, client: &Arc) -> anyhow::Result<()> { - let now = Utc::now(); - if !self.is_idle { - self.last_input_time = now; - } - - if self.is_changed { - let result = if self.is_idle { - debug!("Reporting as changed to idle"); - client - .ping(false, self.last_input_time, Duration::zero()) - .await?; - client - .ping( - true, - self.last_input_time + Duration::milliseconds(1), - Duration::zero(), - ) - .await - } else { - debug!("Reporting as no longer idle"); - - client - .ping(true, self.last_input_time, Duration::zero()) - .await?; - client - .ping( - false, - self.last_input_time + Duration::milliseconds(1), - Duration::zero(), - ) - .await - }; - self.is_changed = false; - result - } else if self.is_idle { - trace!("Reporting as idle"); - client - .ping(true, self.last_input_time, now - self.last_input_time) - .await - } else { - trace!("Reporting as not idle"); - client - .ping(false, self.last_input_time, Duration::zero()) - .await - } - } } -subscribe_state!(wl_registry::WlRegistry, GlobalListContents, IdleState); -subscribe_state!(wl_registry::WlRegistry, (), IdleState); -subscribe_state!(WlSeat, (), IdleState); -subscribe_state!(OrgKdeKwinIdle, (), IdleState); +subscribe_state!(wl_registry::WlRegistry, GlobalListContents, WatcherState); +subscribe_state!(wl_registry::WlRegistry, (), WatcherState); +subscribe_state!(WlSeat, (), WatcherState); +subscribe_state!(OrgKdeKwinIdle, (), WatcherState); -impl Dispatch for IdleState { +impl Dispatch for WatcherState { fn event( state: &mut Self, _: &OrgKdeKwinIdleTimeout, @@ -127,35 +70,38 @@ impl Dispatch for IdleState { } pub struct IdleWatcher { - connection: WlEventConnection, - idle_state: IdleState, + connection: WlEventConnection, + watcher_state: WatcherState, } #[async_trait] impl Watcher for IdleWatcher { async fn new(client: &Arc) -> anyhow::Result { - let mut connection: WlEventConnection = WlEventConnection::connect()?; + let mut connection: WlEventConnection = WlEventConnection::connect()?; connection.get_kwin_idle()?; let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000); - let mut idle_state = IdleState::new( + let mut watcher_state = WatcherState::new( connection.get_kwin_idle_timeout(timeout.unwrap()).unwrap(), Duration::from_std(client.config.idle_timeout).unwrap(), ); - connection.event_queue.roundtrip(&mut idle_state).unwrap(); + connection + .event_queue + .roundtrip(&mut watcher_state) + .unwrap(); Ok(Self { connection, - idle_state, + watcher_state, }) } async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { self.connection .event_queue - .roundtrip(&mut self.idle_state) + .roundtrip(&mut self.watcher_state) .map_err(|e| anyhow!("Event queue is not processed: {e}"))?; - self.idle_state.send_ping(client).await + self.watcher_state.idle_state.send_reactive(client).await } } diff --git a/watchers/src/watchers/x11_screensaver_idle.rs b/watchers/src/watchers/x11_screensaver_idle.rs index d53b94d..ea3d748 100644 --- a/watchers/src/watchers/x11_screensaver_idle.rs +++ b/watchers/src/watchers/x11_screensaver_idle.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use chrono::Duration; use super::{idle, x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; @@ -6,10 +7,10 @@ use std::sync::Arc; pub struct IdleWatcher { client: X11Client, - is_idle: bool, + idle_state: idle::State, } -impl idle::SinceLastInput for IdleWatcher { +impl IdleWatcher { async fn seconds_since_input(&mut self) -> anyhow::Result { self.client.seconds_since_last_input() } @@ -17,7 +18,7 @@ impl idle::SinceLastInput for IdleWatcher { #[async_trait] impl Watcher for IdleWatcher { - async fn new(_: &Arc) -> anyhow::Result { + async fn new(report_client: &Arc) -> anyhow::Result { let mut client = X11Client::new()?; // Check if screensaver extension is supported @@ -25,12 +26,17 @@ impl Watcher for IdleWatcher { Ok(IdleWatcher { client, - is_idle: false, + idle_state: idle::State::new( + Duration::from_std(report_client.config.idle_timeout).unwrap(), + ), }) } async fn run_iteration(&mut self, client: &Arc) -> anyhow::Result<()> { - self.is_idle = idle::ping_since_last_input(self, self.is_idle, client).await?; + let seconds = self.seconds_since_input().await?; + self.idle_state + .send_with_last_input(seconds, client) + .await?; Ok(()) }