diff --git a/README.md b/README.md index 8d37264..de1091e 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ [![Build Status](https://github.com/2e3s/awatcher/workflows/check/badge.svg?branch=main)](https://github.com/2e3s/awatcher/actions?query=branch%3Amain) [![Dependency Status](https://deps.rs/repo/github/2e3s/awatcher/status.svg)](https://deps.rs/repo/github/2e3s/awatcher) Awatcher is a window activity and idle watcher with an optional tray and UI for statistics. -The goal is to compensate the fragmentation of desktop environments on Linux, -and to add more flexibility to reports. +The goal is to compensate the fragmentation of desktop environments on Linux by supporting all reportable environments, +to add more flexibility to reports with filters, and to have better UX with the distribution by a single executable. -The server and web UI are taken from [ActivityWatch](https://github.com/ActivityWatch) project, -which has a worse support of Linux environment, with a pretty bulky distribution. -The crate also provides a library with watchers which can send statistics to the server. +The foundation is taken from [ActivityWatch](https://github.com/ActivityWatch), which includes the server and web UI. +The unbundled watcher can replace the original idle and active window watchers in the original distribution if necessary. + +The crate also provides a library with watchers which can send the data to the server. ## Build diff --git a/watchers/src/watchers/gnome_idle.rs b/watchers/src/watchers/gnome_idle.rs index 1ae804e..4361fa4 100644 --- a/watchers/src/watchers/gnome_idle.rs +++ b/watchers/src/watchers/gnome_idle.rs @@ -9,7 +9,7 @@ pub struct IdleWatcher { } impl idle::SinceLastInput for IdleWatcher { - fn seconds_since_input(&self) -> anyhow::Result { + fn seconds_since_input(&mut self) -> anyhow::Result { let ms = self .dbus_connection .call_method( @@ -26,10 +26,10 @@ impl idle::SinceLastInput for IdleWatcher { impl Watcher for IdleWatcher { fn new() -> anyhow::Result { - let watcher = Self { + let mut watcher = Self { dbus_connection: Connection::session()?, }; - idle::SinceLastInput::seconds_since_input(&watcher)?; + idle::SinceLastInput::seconds_since_input(&mut watcher)?; Ok(watcher) } diff --git a/watchers/src/watchers/idle.rs b/watchers/src/watchers/idle.rs index 4a34729..85d3558 100644 --- a/watchers/src/watchers/idle.rs +++ b/watchers/src/watchers/idle.rs @@ -3,11 +3,11 @@ use chrono::{Duration, Utc}; use std::sync::Arc; pub trait SinceLastInput { - fn seconds_since_input(&self) -> anyhow::Result; + fn seconds_since_input(&mut self) -> anyhow::Result; } pub fn ping_since_last_input( - watcher: &impl SinceLastInput, + watcher: &mut impl SinceLastInput, is_idle: bool, client: &Arc, ) -> anyhow::Result { diff --git a/watchers/src/watchers/x11_connection.rs b/watchers/src/watchers/x11_connection.rs index 3b8c248..91244cc 100644 --- a/watchers/src/watchers/x11_connection.rs +++ b/watchers/src/watchers/x11_connection.rs @@ -11,12 +11,12 @@ pub struct WindowData { pub app_id: String, } -pub struct X11Connection { +pub struct X11Client { connection: RustConnection, screen_root: Window, } -impl X11Connection { +impl X11Client { pub fn new() -> anyhow::Result { if env::var("DISPLAY").is_err() { warn!("DISPLAY is not set, setting to the default value \":0\""); @@ -26,44 +26,71 @@ impl X11Connection { let (connection, screen_num) = x11rb::connect(None)?; let screen_root = connection.setup().roots[screen_num].root; - Ok(X11Connection { + Ok(X11Client { connection, screen_root, }) } - pub fn seconds_since_last_input(&self) -> anyhow::Result { - let reply = self - .connection - .screensaver_query_info(self.screen_root)? - .reply()?; - - Ok(reply.ms_since_user_input / 1000) + fn reconnect(&mut self) { + match x11rb::connect(None) { + Ok((connection, screen_num)) => { + self.screen_root = connection.setup().roots[screen_num].root; + self.connection = connection; + } + Err(e) => error!("Failed to reconnect to X11: {e}"), + }; } - pub fn active_window_data(&self) -> anyhow::Result { - let focus: Window = self.find_active_window()?; + fn execute_with_reconnect( + &mut self, + action: fn(&Self) -> anyhow::Result, + ) -> anyhow::Result { + match action(self) { + Ok(v) => Ok(v), + Err(_) => { + self.reconnect(); + action(self) + } + } + } - let name = self.get_property( - focus, - self.intern_atom("_NET_WM_NAME")?, - "_NET_WM_NAME", - self.intern_atom("UTF8_STRING")?, - u32::MAX, - )?; - let class = self.get_property( - focus, - AtomEnum::WM_CLASS.into(), - "WM_CLASS", - AtomEnum::STRING.into(), - u32::MAX, - )?; + pub fn seconds_since_last_input(&mut self) -> anyhow::Result { + self.execute_with_reconnect(|client| { + let reply = client + .connection + .screensaver_query_info(client.screen_root)? + .reply()?; - let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?; + Ok(reply.ms_since_user_input / 1000) + }) + } - Ok(WindowData { - title: title.to_string(), - app_id: parse_wm_class(&class)?.to_string(), + pub fn active_window_data(&mut self) -> anyhow::Result { + self.execute_with_reconnect(|client| { + let focus: Window = client.find_active_window()?; + + let name = client.get_property( + focus, + client.intern_atom("_NET_WM_NAME")?, + "_NET_WM_NAME", + client.intern_atom("UTF8_STRING")?, + u32::MAX, + )?; + let class = client.get_property( + focus, + AtomEnum::WM_CLASS.into(), + "WM_CLASS", + AtomEnum::STRING.into(), + u32::MAX, + )?; + + let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?; + + Ok(WindowData { + title: title.to_string(), + app_id: parse_wm_class(&class)?.to_string(), + }) }) } diff --git a/watchers/src/watchers/x11_screensaver_idle.rs b/watchers/src/watchers/x11_screensaver_idle.rs index 8def1bf..12632da 100644 --- a/watchers/src/watchers/x11_screensaver_idle.rs +++ b/watchers/src/watchers/x11_screensaver_idle.rs @@ -1,25 +1,25 @@ -use super::{idle, x11_connection::X11Connection, Watcher}; +use super::{idle, x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; use std::{sync::Arc, thread}; pub struct IdleWatcher { - connection: X11Connection, + client: X11Client, } impl idle::SinceLastInput for IdleWatcher { - fn seconds_since_input(&self) -> anyhow::Result { - self.connection.seconds_since_last_input() + fn seconds_since_input(&mut self) -> anyhow::Result { + self.client.seconds_since_last_input() } } impl Watcher for IdleWatcher { fn new() -> anyhow::Result { - let connection = X11Connection::new()?; + let mut client = X11Client::new()?; // Check if screensaver extension is supported - connection.seconds_since_last_input()?; + client.seconds_since_last_input()?; - Ok(IdleWatcher { connection }) + Ok(IdleWatcher { client }) } fn watch(&mut self, client: &Arc) { diff --git a/watchers/src/watchers/x11_window.rs b/watchers/src/watchers/x11_window.rs index 367c62b..b0f842d 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -1,17 +1,17 @@ -use super::{x11_connection::X11Connection, Watcher}; +use super::{x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; use anyhow::Context; use std::thread; pub struct WindowWatcher { - connection: X11Connection, + client: X11Client, last_title: String, last_app_id: String, } impl WindowWatcher { fn send_active_window(&mut self, client: &ReportClient) -> anyhow::Result<()> { - let data = self.connection.active_window_data()?; + let data = self.client.active_window_data()?; if data.app_id != self.last_app_id || data.title != self.last_title { debug!( @@ -30,11 +30,11 @@ impl WindowWatcher { impl Watcher for WindowWatcher { fn new() -> anyhow::Result { - let connection = X11Connection::new()?; - connection.active_window_data()?; + let mut client = X11Client::new()?; + client.active_window_data()?; Ok(WindowWatcher { - connection, + client, last_title: String::new(), last_app_id: String::new(), })