Try to reconnect to X11 on error

X11 connection may sometimes be lost.
This commit is contained in:
Demmie 2023-05-10 00:01:46 -04:00
parent c8ca1debab
commit f1f22089a9
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
6 changed files with 81 additions and 53 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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> {

View File

@ -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,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<u32> {
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<WindowData> {
let focus: Window = self.find_active_window()?;
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)
}
}
}
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<u32> {
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<WindowData> {
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(),
})
})
}

View File

@ -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>) {

View File

@ -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(),
})