Implement Gnome idle watcher

This commit is contained in:
Demmie 2023-04-24 01:08:09 -04:00
parent a758913d52
commit 11cfb7c54b
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
5 changed files with 107 additions and 42 deletions

View File

@ -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();

View File

@ -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 = &[

View File

@ -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<u32, BoxedError> {
let ms = self
.dbus_connection
.call_method(
Some("org.gnome.Mutter.IdleMonitor"),
"/org/gnome/Mutter/IdleMonitor/Core",
Some("org.gnome.Mutter.IdleMonitor"),
"GetIdletime",
&(),
)?
.body::<u64>()?;
u32::try_from(ms / 1000).map_err(|_| format!("Number {ms} is invalid").into())
}
}
impl Watcher for IdleWatcher {
fn new() -> Result<Self, crate::BoxedError> {
let watcher = Self {
dbus_connection: Connection::session()?,
};
idle::SinceLastInput::seconds_since_input(&watcher)?;
Ok(watcher)
}
fn watch(&mut self, client: &Arc<ReportClient>) {
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);
}
}
}

49
src/watchers/idle.rs Normal file
View File

@ -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<u32, BoxedError>;
}
pub fn ping_since_last_input(
watcher: &impl SinceLastInput,
is_idle: bool,
client: &Arc<ReportClient>,
) -> Result<bool, BoxedError> {
// 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)
}

View File

@ -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<ReportClient>) -> Result<bool, BoxedError> {
// 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<u32, BoxedError> {
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;
}