Introduce anyhow errors

This commit is contained in:
Demmie 2023-04-25 01:25:14 -04:00
parent 11cfb7c54b
commit 3c51ad8685
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
16 changed files with 97 additions and 92 deletions

7
Cargo.lock generated
View File

@ -60,6 +60,12 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "anyhow"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
[[package]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.5.1" version = "0.5.1"
@ -216,6 +222,7 @@ dependencies = [
name = "awatcher" name = "awatcher"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"aw-client-rust", "aw-client-rust",
"chrono", "chrono",
"clap", "clap",

View File

@ -21,3 +21,4 @@ dirs = "5.0.0"
serde = { version = "1.0.160", features = ["derive"] } serde = { version = "1.0.160", features = ["derive"] }
serde_default = "0.1.0" serde_default = "0.1.0"
regex = "1.8.1" regex = "1.8.1"
anyhow = "1.0.70"

View File

@ -2,7 +2,6 @@ mod defaults;
mod file_config; mod file_config;
mod filters; mod filters;
use crate::BoxedError;
use clap::{arg, value_parser, ArgAction, Command}; use clap::{arg, value_parser, ArgAction, Command};
use file_config::FileConfig; use file_config::FileConfig;
use std::{path::PathBuf, time::Duration}; use std::{path::PathBuf, time::Duration};
@ -22,7 +21,7 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn from_cli() -> Result<Self, BoxedError> { pub fn from_cli() -> anyhow::Result<Self> {
let matches = Command::new("Activity Watcher") let matches = Command::new("Activity Watcher")
.version("0.1.0") .version("0.1.0")
.about("A set of ActivityWatch desktop watchers") .about("A set of ActivityWatch desktop watchers")

View File

@ -1,3 +1,4 @@
use anyhow::{anyhow, Context};
use clap::{parser::ValueSource, ArgMatches}; use clap::{parser::ValueSource, ArgMatches};
use serde::Deserialize; use serde::Deserialize;
use serde_default::DefaultFromSerde; use serde_default::DefaultFromSerde;
@ -7,7 +8,7 @@ use std::{
time::Duration, time::Duration,
}; };
use crate::{config::defaults, BoxedError}; use crate::config::defaults;
use super::filters::Filter; use super::filters::Filter;
@ -56,8 +57,9 @@ pub struct FileConfig {
} }
impl FileConfig { impl FileConfig {
pub fn new(matches: &ArgMatches) -> Result<Self, BoxedError> { pub fn new(matches: &ArgMatches) -> anyhow::Result<Self> {
let mut config_path: PathBuf = dirs::config_dir().ok_or("Config directory is unknown")?; let mut config_path: PathBuf =
dirs::config_dir().ok_or(anyhow!("Config directory is unknown"))?;
config_path.push("awatcher"); config_path.push("awatcher");
config_path.push("config.toml"); config_path.push("config.toml");
if matches.contains_id("config") { if matches.contains_id("config") {
@ -73,8 +75,9 @@ impl FileConfig {
let mut config = if config_path.exists() { let mut config = if config_path.exists() {
debug!("Reading config at {}", config_path.display()); debug!("Reading config at {}", config_path.display());
let config_content = std::fs::read_to_string(config_path) let config_content = std::fs::read_to_string(&config_path).with_context(|| {
.map_err(|e| format!("Impossible to read config file: {e}"))?; format!("Impossible to read config file {}", config_path.display())
})?;
toml::from_str(&config_content)? toml::from_str(&config_content)?
} else { } else {

View File

@ -11,12 +11,10 @@ use config::Config;
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use report_client::ReportClient; use report_client::ReportClient;
use std::env; use std::env;
use std::{error::Error, str::FromStr, sync::Arc, thread}; use std::{sync::Arc, thread};
use crate::watchers::ConstructorFilter; use crate::watchers::ConstructorFilter;
type BoxedError = Box<dyn Error>;
fn setup_logger() -> Result<(), fern::InitError> { fn setup_logger() -> Result<(), fern::InitError> {
let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string()); let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string());
@ -37,14 +35,14 @@ fn setup_logger() -> Result<(), fern::InitError> {
.level(log::LevelFilter::Warn) .level(log::LevelFilter::Warn)
.level_for( .level_for(
"awatcher", "awatcher",
FromStr::from_str(&log_setting).unwrap_or(log::LevelFilter::Info), log_setting.parse().unwrap_or(log::LevelFilter::Info),
) )
.chain(std::io::stdout()) .chain(std::io::stdout())
.apply()?; .apply()?;
Ok(()) Ok(())
} }
fn main() -> Result<(), BoxedError> { fn main() -> anyhow::Result<()> {
setup_logger()?; setup_logger()?;
let client = ReportClient::new(Config::from_cli()?)?; let client = ReportClient::new(Config::from_cli()?)?;

View File

@ -1,7 +1,5 @@
use std::error::Error;
use super::BoxedError;
use super::Config; use super::Config;
use anyhow::Context;
use aw_client_rust::{AwClient, Event as AwEvent}; use aw_client_rust::{AwClient, Event as AwEvent};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use serde_json::{Map, Value}; use serde_json::{Map, Value};
@ -12,7 +10,7 @@ pub struct ReportClient {
} }
impl ReportClient { impl ReportClient {
pub fn new(config: Config) -> Result<Self, BoxedError> { pub fn new(config: Config) -> anyhow::Result<Self> {
let client = AwClient::new(&config.host, &config.port.to_string(), "awatcher"); let client = AwClient::new(&config.host, &config.port.to_string(), "awatcher");
if !config.mock_server { if !config.mock_server {
@ -28,7 +26,7 @@ impl ReportClient {
is_idle: bool, is_idle: bool,
timestamp: DateTime<Utc>, timestamp: DateTime<Utc>,
duration: Duration, duration: Duration,
) -> Result<(), Box<dyn Error>> { ) -> anyhow::Result<()> {
let mut data = Map::new(); let mut data = Map::new();
data.insert( data.insert(
"status".to_string(), "status".to_string(),
@ -49,10 +47,10 @@ impl ReportClient {
let pulsetime = (self.config.idle_timeout + self.config.poll_time_idle).as_secs_f64(); let pulsetime = (self.config.idle_timeout + self.config.poll_time_idle).as_secs_f64();
self.client self.client
.heartbeat(&self.config.idle_bucket_name, &event, pulsetime) .heartbeat(&self.config.idle_bucket_name, &event, pulsetime)
.map_err(|_| "Failed to send heartbeat".into()) .with_context(|| "Failed to send heartbeat")
} }
pub fn send_active_window(&self, app_id: &str, title: &str) -> Result<(), BoxedError> { pub fn send_active_window(&self, app_id: &str, title: &str) -> anyhow::Result<()> {
let mut data = Map::new(); let mut data = Map::new();
let mut data_insert = |k: &str, v: String| data.insert(k.to_string(), Value::String(v)); let mut data_insert = |k: &str, v: String| data.insert(k.to_string(), Value::String(v));
@ -89,16 +87,16 @@ impl ReportClient {
&event, &event,
interval_margin, interval_margin,
) )
.map_err(|_| "Failed to send heartbeat for active window".into()) .with_context(|| "Failed to send heartbeat for active window")
} }
fn create_bucket( fn create_bucket(
client: &AwClient, client: &AwClient,
bucket_name: &str, bucket_name: &str,
bucket_type: &str, bucket_type: &str,
) -> Result<(), Box<dyn Error>> { ) -> anyhow::Result<()> {
client client
.create_bucket_simple(bucket_name, bucket_type) .create_bucket_simple(bucket_name, bucket_type)
.map_err(|e| format!("Failed to create bucket {bucket_name}: {e}").into()) .with_context(|| format!("Failed to create bucket {bucket_name}"))
} }
} }

View File

@ -9,11 +9,11 @@ mod x11_connection;
mod x11_screensaver_idle; mod x11_screensaver_idle;
mod x11_window; mod x11_window;
use crate::{report_client::ReportClient, BoxedError}; use crate::report_client::ReportClient;
use std::sync::Arc; use std::sync::Arc;
pub trait Watcher: Send { pub trait Watcher: Send {
fn new() -> Result<Self, BoxedError> fn new() -> anyhow::Result<Self>
where where
Self: Sized; Self: Sized;
fn watch(&mut self, client: &Arc<ReportClient>); fn watch(&mut self, client: &Arc<ReportClient>);
@ -21,7 +21,7 @@ pub trait Watcher: Send {
type BoxedWatcher = Box<dyn Watcher>; type BoxedWatcher = Box<dyn Watcher>;
type WatcherConstructor = (&'static str, fn() -> Result<BoxedWatcher, BoxedError>); type WatcherConstructor = (&'static str, fn() -> anyhow::Result<BoxedWatcher>);
type WatcherConstructors = [WatcherConstructor]; type WatcherConstructors = [WatcherConstructor];
pub trait ConstructorFilter { pub trait ConstructorFilter {

View File

@ -1,5 +1,6 @@
use super::{idle, Watcher}; use super::{idle, Watcher};
use crate::{report_client::ReportClient, BoxedError}; use crate::report_client::ReportClient;
use anyhow::Context;
use std::{sync::Arc, thread}; use std::{sync::Arc, thread};
use zbus::blocking::Connection; use zbus::blocking::Connection;
@ -8,7 +9,7 @@ pub struct IdleWatcher {
} }
impl idle::SinceLastInput for IdleWatcher { impl idle::SinceLastInput for IdleWatcher {
fn seconds_since_input(&self) -> Result<u32, BoxedError> { fn seconds_since_input(&self) -> anyhow::Result<u32> {
let ms = self let ms = self
.dbus_connection .dbus_connection
.call_method( .call_method(
@ -19,12 +20,12 @@ impl idle::SinceLastInput for IdleWatcher {
&(), &(),
)? )?
.body::<u64>()?; .body::<u64>()?;
u32::try_from(ms / 1000).map_err(|_| format!("Number {ms} is invalid").into()) u32::try_from(ms / 1000).with_context(|| format!("Number {ms} is invalid"))
} }
} }
impl Watcher for IdleWatcher { impl Watcher for IdleWatcher {
fn new() -> Result<Self, crate::BoxedError> { fn new() -> anyhow::Result<Self> {
let watcher = Self { let watcher = Self {
dbus_connection: Connection::session()?, dbus_connection: Connection::session()?,
}; };

View File

@ -1,16 +1,16 @@
use crate::{report_client::ReportClient, BoxedError}; use crate::report_client::ReportClient;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use std::sync::Arc; use std::sync::Arc;
pub trait SinceLastInput { pub trait SinceLastInput {
fn seconds_since_input(&self) -> Result<u32, BoxedError>; fn seconds_since_input(&self) -> anyhow::Result<u32>;
} }
pub fn ping_since_last_input( pub fn ping_since_last_input(
watcher: &impl SinceLastInput, watcher: &impl SinceLastInput,
is_idle: bool, is_idle: bool,
client: &Arc<ReportClient>, client: &Arc<ReportClient>,
) -> Result<bool, BoxedError> { ) -> anyhow::Result<bool> {
// The logic is rewritten from the original Python code: // The logic is rewritten from the original Python code:
// https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73 // https://github.com/ActivityWatch/aw-watcher-afk/blob/ef531605cd8238e00138bbb980e5457054e05248/aw_watcher_afk/afk.py#L73
let duration_1ms: Duration = Duration::milliseconds(1); let duration_1ms: Duration = Duration::milliseconds(1);

View File

@ -3,8 +3,9 @@
* For the moment of writing, KWin doesn't implement the appropriate protocols to get a top level window. * For the moment of writing, KWin doesn't implement the appropriate protocols to get a top level window.
* Inspired by https://github.com/k0kubun/xremap/ * Inspired by https://github.com/k0kubun/xremap/
*/ */
use super::{BoxedError, Watcher}; use super::Watcher;
use crate::report_client::ReportClient; use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::env::temp_dir; use std::env::temp_dir;
use std::path::Path; use std::path::Path;
use std::sync::{mpsc::channel, Arc, Mutex}; use std::sync::{mpsc::channel, Arc, Mutex};
@ -28,7 +29,7 @@ impl KWinScript {
} }
} }
fn load(&mut self) -> Result<(), BoxedError> { fn load(&mut self) -> anyhow::Result<()> {
let path = temp_dir().join("kwin_window.js"); let path = temp_dir().join("kwin_window.js");
std::fs::write(&path, KWIN_SCRIPT).unwrap(); std::fs::write(&path, KWIN_SCRIPT).unwrap();
@ -41,7 +42,7 @@ impl KWinScript {
result result
} }
fn is_loaded(&self) -> Result<bool, BoxedError> { fn is_loaded(&self) -> anyhow::Result<bool> {
self.dbus_connection self.dbus_connection
.call_method( .call_method(
Some("org.kde.KWin"), Some("org.kde.KWin"),
@ -54,10 +55,10 @@ impl KWinScript {
.map_err(std::convert::Into::into) .map_err(std::convert::Into::into)
} }
fn get_registered_number(&self, path: &Path) -> Result<i32, BoxedError> { fn get_registered_number(&self, path: &Path) -> anyhow::Result<i32> {
let temp_path = path let temp_path = path
.to_str() .to_str()
.ok_or::<BoxedError>("Temporary file path is not valid".into())?; .ok_or(anyhow!("Temporary file path is not valid"))?;
self.dbus_connection self.dbus_connection
.call_method( .call_method(
@ -72,7 +73,7 @@ impl KWinScript {
.map_err(std::convert::Into::into) .map_err(std::convert::Into::into)
} }
fn unload(&self) -> Result<bool, BoxedError> { fn unload(&self) -> anyhow::Result<bool> {
self.dbus_connection self.dbus_connection
.call_method( .call_method(
Some("org.kde.KWin"), Some("org.kde.KWin"),
@ -85,7 +86,7 @@ impl KWinScript {
.map_err(std::convert::Into::into) .map_err(std::convert::Into::into)
} }
fn start(&self, script_number: i32) -> Result<(), BoxedError> { fn start(&self, script_number: i32) -> anyhow::Result<()> {
debug!("Starting KWin script {script_number}"); debug!("Starting KWin script {script_number}");
self.dbus_connection self.dbus_connection
.call_method( .call_method(
@ -95,8 +96,8 @@ impl KWinScript {
"run", "run",
&(), &(),
) )
.map_err(|e| format!("Error on starting the script {e}").into()) .with_context(|| "Error on starting the script")?;
.map(|_| ()) Ok(())
} }
} }
@ -114,12 +115,12 @@ impl Drop for KWinScript {
fn send_active_window( fn send_active_window(
client: &ReportClient, client: &ReportClient,
active_window: &Arc<Mutex<ActiveWindow>>, active_window: &Arc<Mutex<ActiveWindow>>,
) -> Result<(), BoxedError> { ) -> anyhow::Result<()> {
let active_window = active_window.lock().map_err(|e| format!("{e}"))?; let active_window = active_window.lock().expect("Lock cannot be acquired");
client client
.send_active_window(&active_window.resource_class, &active_window.caption) .send_active_window(&active_window.resource_class, &active_window.caption)
.map_err(|_| "Failed to send heartbeat for active window".into()) .with_context(|| "Failed to send heartbeat for active window")
} }
struct ActiveWindow { struct ActiveWindow {
@ -153,7 +154,7 @@ pub struct WindowWatcher {
} }
impl Watcher for WindowWatcher { impl Watcher for WindowWatcher {
fn new() -> Result<Self, BoxedError> { fn new() -> anyhow::Result<Self> {
let kwin_script = KWinScript::new(Connection::session()?); let kwin_script = KWinScript::new(Connection::session()?);
if kwin_script.is_loaded()? { if kwin_script.is_loaded()? {
warn!("KWin script is already loaded, unloading"); warn!("KWin script is already loaded, unloading");

View File

@ -1,5 +1,5 @@
use super::wl_bindings; use super::wl_bindings;
use crate::BoxedError; use anyhow::Context;
use wayland_client::{ use wayland_client::{
globals::{registry_queue_init, GlobalList, GlobalListContents}, globals::{registry_queue_init, GlobalList, GlobalListContents},
protocol::{wl_registry, wl_seat::WlSeat}, protocol::{wl_registry, wl_seat::WlSeat},
@ -38,9 +38,9 @@ where
+ Dispatch<wl_registry::WlRegistry, ()> + Dispatch<wl_registry::WlRegistry, ()>
+ 'static, + 'static,
{ {
pub fn connect() -> Result<Self, BoxedError> { pub fn connect() -> anyhow::Result<Self> {
let connection = let connection = Connection::connect_to_env()
Connection::connect_to_env().map_err(|_| "Unable to connect to Wayland compositor")?; .with_context(|| "Unable to connect to Wayland compositor")?;
let display = connection.display(); let display = connection.display();
let (globals, event_queue) = registry_queue_init::<T>(&connection)?; let (globals, event_queue) = registry_queue_init::<T>(&connection)?;
@ -55,7 +55,7 @@ where
}) })
} }
pub fn get_foreign_toplevel_manager(&self) -> Result<ZwlrForeignToplevelManagerV1, BoxedError> pub fn get_foreign_toplevel_manager(&self) -> anyhow::Result<ZwlrForeignToplevelManagerV1>
where where
T: Dispatch<ZwlrForeignToplevelManagerV1, ()>, T: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
{ {
@ -68,7 +68,7 @@ where
.map_err(std::convert::Into::into) .map_err(std::convert::Into::into)
} }
pub fn get_kwin_idle(&self) -> Result<OrgKdeKwinIdle, BoxedError> pub fn get_kwin_idle(&self) -> anyhow::Result<OrgKdeKwinIdle>
where where
T: Dispatch<OrgKdeKwinIdle, ()>, T: Dispatch<OrgKdeKwinIdle, ()>,
{ {
@ -81,7 +81,7 @@ where
.map_err(std::convert::Into::into) .map_err(std::convert::Into::into)
} }
pub fn get_kwin_idle_timeout(&self, timeout: u32) -> Result<OrgKdeKwinIdleTimeout, BoxedError> pub fn get_kwin_idle_timeout(&self, timeout: u32) -> anyhow::Result<OrgKdeKwinIdleTimeout>
where where
T: Dispatch<OrgKdeKwinIdle, ()> T: Dispatch<OrgKdeKwinIdle, ()>
+ Dispatch<OrgKdeKwinIdleTimeout, ()> + Dispatch<OrgKdeKwinIdleTimeout, ()>

View File

@ -5,9 +5,9 @@ use super::wl_bindings::wlr_foreign_toplevel::zwlr_foreign_toplevel_manager_v1::
Event as ManagerEvent, ZwlrForeignToplevelManagerV1, EVT_TOPLEVEL_OPCODE, Event as ManagerEvent, ZwlrForeignToplevelManagerV1, EVT_TOPLEVEL_OPCODE,
}; };
use super::wl_connection::WlEventConnection; use super::wl_connection::WlEventConnection;
use super::BoxedError;
use super::{wl_connection::subscribe_state, Watcher}; use super::{wl_connection::subscribe_state, Watcher};
use crate::report_client::ReportClient; use crate::report_client::ReportClient;
use anyhow::{anyhow, Context};
use std::collections::HashMap; use std::collections::HashMap;
use std::{sync::Arc, thread}; use std::{sync::Arc, thread};
use wayland_client::{ use wayland_client::{
@ -114,18 +114,18 @@ impl Dispatch<ZwlrForeignToplevelHandleV1, ()> for ToplevelState {
} }
impl ToplevelState { impl ToplevelState {
fn send_active_window(&self) -> Result<(), BoxedError> { fn send_active_window(&self) -> anyhow::Result<()> {
let active_window_id = self let active_window_id = self
.current_window_id .current_window_id
.as_ref() .as_ref()
.ok_or("Current window is unknown")?; .ok_or(anyhow!("Current window is unknown"))?;
let active_window = self.windows.get(active_window_id).ok_or(format!( let active_window = self.windows.get(active_window_id).ok_or(anyhow!(
"Current window is not found by ID {active_window_id}" "Current window is not found by ID {active_window_id}"
))?; ))?;
self.client self.client
.send_active_window(&active_window.app_id, &active_window.title) .send_active_window(&active_window.app_id, &active_window.title)
.map_err(|_| "Failed to send heartbeat for active window".into()) .with_context(|| "Failed to send heartbeat for active window")
} }
} }
@ -134,7 +134,7 @@ pub struct WindowWatcher {
} }
impl Watcher for WindowWatcher { impl Watcher for WindowWatcher {
fn new() -> Result<Self, BoxedError> { fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?; let connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?;
connection.get_foreign_toplevel_manager()?; connection.get_foreign_toplevel_manager()?;

View File

@ -1,6 +1,5 @@
use super::wl_bindings; use super::wl_bindings;
use super::wl_connection::{subscribe_state, WlEventConnection}; use super::wl_connection::{subscribe_state, WlEventConnection};
use super::BoxedError;
use super::Watcher; use super::Watcher;
use crate::report_client::ReportClient; use crate::report_client::ReportClient;
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
@ -54,7 +53,7 @@ impl IdleState {
debug!("Resumed"); debug!("Resumed");
} }
fn send_ping(&mut self) -> Result<(), BoxedError> { fn send_ping(&mut self) -> anyhow::Result<()> {
let now = Utc::now(); let now = Utc::now();
if !self.is_idle { if !self.is_idle {
self.last_input_time = now; self.last_input_time = now;
@ -122,7 +121,7 @@ pub struct IdleWatcher {
} }
impl Watcher for IdleWatcher { impl Watcher for IdleWatcher {
fn new() -> Result<Self, BoxedError> { fn new() -> anyhow::Result<Self> {
let connection: WlEventConnection<IdleState> = WlEventConnection::connect()?; let connection: WlEventConnection<IdleState> = WlEventConnection::connect()?;
connection.get_kwin_idle()?; connection.get_kwin_idle()?;

View File

@ -1,13 +1,11 @@
use std::{env, str}; use anyhow::{anyhow, bail, Context};
use log::warn; use log::warn;
use std::{env, str};
use x11rb::connection::Connection; use x11rb::connection::Connection;
use x11rb::protocol::screensaver::ConnectionExt as ScreensaverConnectionExt; use x11rb::protocol::screensaver::ConnectionExt as ScreensaverConnectionExt;
use x11rb::protocol::xproto::{Atom, AtomEnum, ConnectionExt, GetPropertyReply, Window}; use x11rb::protocol::xproto::{Atom, AtomEnum, ConnectionExt, GetPropertyReply, Window};
use x11rb::rust_connection::RustConnection; use x11rb::rust_connection::RustConnection;
use crate::BoxedError;
pub struct WindowData { pub struct WindowData {
pub title: String, pub title: String,
pub app_id: String, pub app_id: String,
@ -19,7 +17,7 @@ pub struct X11Connection {
} }
impl X11Connection { impl X11Connection {
pub fn new() -> Result<Self, BoxedError> { pub fn new() -> anyhow::Result<Self> {
if env::var("DISPLAY").is_err() { if env::var("DISPLAY").is_err() {
warn!("DISPLAY is not set, setting to the default value \":0\""); warn!("DISPLAY is not set, setting to the default value \":0\"");
env::set_var("DISPLAY", ":0"); env::set_var("DISPLAY", ":0");
@ -34,7 +32,7 @@ impl X11Connection {
}) })
} }
pub fn seconds_since_last_input(&self) -> Result<u32, BoxedError> { pub fn seconds_since_last_input(&self) -> anyhow::Result<u32> {
let reply = self let reply = self
.connection .connection
.screensaver_query_info(self.screen_root)? .screensaver_query_info(self.screen_root)?
@ -43,7 +41,7 @@ impl X11Connection {
Ok(reply.ms_since_user_input / 1000) Ok(reply.ms_since_user_input / 1000)
} }
pub fn active_window_data(&self) -> Result<WindowData, BoxedError> { pub fn active_window_data(&self) -> anyhow::Result<WindowData> {
let focus: Window = self.find_active_window()?; let focus: Window = self.find_active_window()?;
let name = self.get_property( let name = self.get_property(
@ -61,7 +59,7 @@ impl X11Connection {
u32::MAX, u32::MAX,
)?; )?;
let title = str::from_utf8(&name.value).map_err(|e| format!("Invalid title UTF: {e}"))?; let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?;
Ok(WindowData { Ok(WindowData {
title: title.to_string(), title: title.to_string(),
@ -76,25 +74,25 @@ impl X11Connection {
property_name: &str, property_name: &str,
property_type: Atom, property_type: Atom,
long_length: u32, long_length: u32,
) -> Result<GetPropertyReply, BoxedError> { ) -> anyhow::Result<GetPropertyReply> {
self.connection self.connection
.get_property(false, window, property, property_type, 0, long_length) .get_property(false, window, property, property_type, 0, long_length)
.map_err(|e| format!("GetPropertyRequest[{property_name}] failed: {e}"))? .with_context(|| format!("GetPropertyRequest[{property_name}] failed"))?
.reply() .reply()
.map_err(|e| format!("GetPropertyReply[{property_name}] failed: {e}").into()) .with_context(|| format!("GetPropertyReply[{property_name}] failed"))
} }
fn intern_atom(&self, name: &str) -> Result<Atom, BoxedError> { fn intern_atom(&self, name: &str) -> anyhow::Result<Atom> {
Ok(self Ok(self
.connection .connection
.intern_atom(false, name.as_bytes()) .intern_atom(false, name.as_bytes())
.map_err(|_| format!("InternAtomRequest[{name}] failed"))? .with_context(|| format!("InternAtomRequest[{name}] failed"))?
.reply() .reply()
.map_err(|_| format!("InternAtomReply[{name}] failed"))? .with_context(|| format!("InternAtomReply[{name}] failed"))?
.atom) .atom)
} }
fn find_active_window(&self) -> Result<Window, BoxedError> { fn find_active_window(&self) -> anyhow::Result<Window> {
let window: Atom = AtomEnum::WINDOW.into(); let window: Atom = AtomEnum::WINDOW.into();
let net_active_window = self.intern_atom("_NET_ACTIVE_WINDOW")?; let net_active_window = self.intern_atom("_NET_ACTIVE_WINDOW")?;
let active_window = self.get_property( let active_window = self.get_property(
@ -108,25 +106,25 @@ impl X11Connection {
if active_window.format == 32 && active_window.length == 1 { if active_window.format == 32 && active_window.length == 1 {
active_window active_window
.value32() .value32()
.ok_or("Invalid message. Expected value with format = 32")? .ok_or(anyhow!("Invalid message. Expected value with format = 32"))?
.next() .next()
.ok_or("Active window is not found".into()) .ok_or(anyhow!("Active window is not found"))
} else { } else {
// Query the input focus // Query the input focus
Ok(self Ok(self
.connection .connection
.get_input_focus() .get_input_focus()
.map_err(|e| format!("Failed to get input focus: {e}"))? .with_context(|| "Failed to get input focus")?
.reply() .reply()
.map_err(|e| format!("Failed to read input focus from reply: {e}"))? .with_context(|| "Failed to read input focus from reply")?
.focus) .focus)
} }
} }
} }
fn parse_wm_class(property: &GetPropertyReply) -> Result<&str, BoxedError> { fn parse_wm_class(property: &GetPropertyReply) -> anyhow::Result<&str> {
if property.format != 8 { if property.format != 8 {
return Err("Malformed property: wrong format".into()); bail!("Malformed property: wrong format");
} }
let value = &property.value; let value = &property.value;
// The property should contain two null-terminated strings. Find them. // The property should contain two null-terminated strings. Find them.
@ -140,6 +138,6 @@ fn parse_wm_class(property: &GetPropertyReply) -> Result<&str, BoxedError> {
} }
Ok(std::str::from_utf8(class)?) Ok(std::str::from_utf8(class)?)
} else { } else {
Err("Missing null byte".into()) bail!("Missing null byte")
} }
} }

View File

@ -1,20 +1,19 @@
use std::{sync::Arc, thread}; use super::{idle, x11_connection::X11Connection, Watcher};
use super::{idle, x11_connection::X11Connection, BoxedError, Watcher};
use crate::report_client::ReportClient; use crate::report_client::ReportClient;
use std::{sync::Arc, thread};
pub struct IdleWatcher { pub struct IdleWatcher {
connection: X11Connection, connection: X11Connection,
} }
impl idle::SinceLastInput for IdleWatcher { impl idle::SinceLastInput for IdleWatcher {
fn seconds_since_input(&self) -> Result<u32, BoxedError> { fn seconds_since_input(&self) -> anyhow::Result<u32> {
self.connection.seconds_since_last_input() self.connection.seconds_since_last_input()
} }
} }
impl Watcher for IdleWatcher { impl Watcher for IdleWatcher {
fn new() -> Result<Self, BoxedError> { fn new() -> anyhow::Result<Self> {
let connection = X11Connection::new()?; let connection = X11Connection::new()?;
// Check if screensaver extension is supported // Check if screensaver extension is supported

View File

@ -1,5 +1,6 @@
use super::{x11_connection::X11Connection, BoxedError, Watcher}; use super::{x11_connection::X11Connection, Watcher};
use crate::report_client::ReportClient; use crate::report_client::ReportClient;
use anyhow::Context;
use std::thread; use std::thread;
pub struct WindowWatcher { pub struct WindowWatcher {
@ -9,7 +10,7 @@ pub struct WindowWatcher {
} }
impl WindowWatcher { impl WindowWatcher {
fn send_active_window(&mut self, client: &ReportClient) -> Result<(), BoxedError> { fn send_active_window(&mut self, client: &ReportClient) -> anyhow::Result<()> {
let data = self.connection.active_window_data()?; let data = self.connection.active_window_data()?;
if data.app_id != self.last_app_id || data.title != self.last_title { if data.app_id != self.last_app_id || data.title != self.last_title {
@ -23,12 +24,12 @@ impl WindowWatcher {
client client
.send_active_window(&self.last_app_id, &self.last_title) .send_active_window(&self.last_app_id, &self.last_title)
.map_err(|_| "Failed to send heartbeat for active window".into()) .with_context(|| "Failed to send heartbeat for active window")
} }
} }
impl Watcher for WindowWatcher { impl Watcher for WindowWatcher {
fn new() -> Result<Self, crate::BoxedError> { fn new() -> anyhow::Result<Self> {
let connection = X11Connection::new()?; let connection = X11Connection::new()?;
connection.active_window_data()?; connection.active_window_data()?;