mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-06 11:35:46 +00:00
Try to reconnect to X11 on error
X11 connection may sometimes be lost.
This commit is contained in:
parent
c8ca1debab
commit
f1f22089a9
11
README.md
11
README.md
@ -2,12 +2,13 @@
|
||||
[](https://github.com/2e3s/awatcher/actions?query=branch%3Amain) [](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
|
||||
|
||||
|
@ -9,7 +9,7 @@ pub struct IdleWatcher {
|
||||
}
|
||||
|
||||
impl idle::SinceLastInput for IdleWatcher {
|
||||
fn seconds_since_input(&self) -> anyhow::Result<u32> {
|
||||
fn seconds_since_input(&mut self) -> anyhow::Result<u32> {
|
||||
let ms = self
|
||||
.dbus_connection
|
||||
.call_method(
|
||||
@ -26,10 +26,10 @@ impl idle::SinceLastInput for IdleWatcher {
|
||||
|
||||
impl Watcher for IdleWatcher {
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
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)
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ use chrono::{Duration, Utc};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub trait SinceLastInput {
|
||||
fn seconds_since_input(&self) -> anyhow::Result<u32>;
|
||||
fn seconds_since_input(&mut self) -> anyhow::Result<u32>;
|
||||
}
|
||||
|
||||
pub fn ping_since_last_input(
|
||||
watcher: &impl SinceLastInput,
|
||||
watcher: &mut impl SinceLastInput,
|
||||
is_idle: bool,
|
||||
client: &Arc<ReportClient>,
|
||||
) -> anyhow::Result<bool> {
|
||||
|
@ -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<Self> {
|
||||
if env::var("DISPLAY").is_err() {
|
||||
warn!("DISPLAY is not set, setting to the default value \":0\"");
|
||||
@ -26,32 +26,58 @@ 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<u32> {
|
||||
let reply = self
|
||||
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}"),
|
||||
};
|
||||
}
|
||||
|
||||
fn execute_with_reconnect<T>(
|
||||
&mut self,
|
||||
action: fn(&Self) -> anyhow::Result<T>,
|
||||
) -> anyhow::Result<T> {
|
||||
match action(self) {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => {
|
||||
self.reconnect();
|
||||
action(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn seconds_since_last_input(&mut self) -> anyhow::Result<u32> {
|
||||
self.execute_with_reconnect(|client| {
|
||||
let reply = client
|
||||
.connection
|
||||
.screensaver_query_info(self.screen_root)?
|
||||
.screensaver_query_info(client.screen_root)?
|
||||
.reply()?;
|
||||
|
||||
Ok(reply.ms_since_user_input / 1000)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_window_data(&self) -> anyhow::Result<WindowData> {
|
||||
let focus: Window = self.find_active_window()?;
|
||||
pub fn active_window_data(&mut self) -> anyhow::Result<WindowData> {
|
||||
self.execute_with_reconnect(|client| {
|
||||
let focus: Window = client.find_active_window()?;
|
||||
|
||||
let name = self.get_property(
|
||||
let name = client.get_property(
|
||||
focus,
|
||||
self.intern_atom("_NET_WM_NAME")?,
|
||||
client.intern_atom("_NET_WM_NAME")?,
|
||||
"_NET_WM_NAME",
|
||||
self.intern_atom("UTF8_STRING")?,
|
||||
client.intern_atom("UTF8_STRING")?,
|
||||
u32::MAX,
|
||||
)?;
|
||||
let class = self.get_property(
|
||||
let class = client.get_property(
|
||||
focus,
|
||||
AtomEnum::WM_CLASS.into(),
|
||||
"WM_CLASS",
|
||||
@ -65,6 +91,7 @@ impl X11Connection {
|
||||
title: title.to_string(),
|
||||
app_id: parse_wm_class(&class)?.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_property(
|
||||
|
@ -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<u32> {
|
||||
self.connection.seconds_since_last_input()
|
||||
fn seconds_since_input(&mut self) -> anyhow::Result<u32> {
|
||||
self.client.seconds_since_last_input()
|
||||
}
|
||||
}
|
||||
|
||||
impl Watcher for IdleWatcher {
|
||||
fn new() -> anyhow::Result<Self> {
|
||||
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<ReportClient>) {
|
||||
|
@ -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<Self> {
|
||||
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(),
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user