diff --git a/src/config.rs b/src/config.rs index 0bf6c64..70386d9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ pub struct Config { pub poll_time_window: Duration, pub idle_bucket_name: String, pub active_window_bucket_name: String, - pub mock_server: bool, + pub no_server: bool, filters: Vec, } @@ -42,7 +42,7 @@ impl Config { arg!(--"poll-time-window" "Period between sending heartbeats to the server for idle activity") .value_parser(value_parser!(u32)) .default_value(defaults::poll_time_window_seconds().to_string()), - arg!(--"mock-server" "Don't communicate to the ActivityWatch server") + arg!(--"no-server" "Don't communicate to the ActivityWatch server") .value_parser(value_parser!(bool)) .action(ArgAction::SetTrue), ]) @@ -63,7 +63,7 @@ impl Config { idle_bucket_name, active_window_bucket_name, filters: config.client.filters, - mock_server: *matches.get_one("mock-server").unwrap(), + no_server: *matches.get_one("no-server").unwrap(), }) } diff --git a/src/main.rs b/src/main.rs index 268d5ed..f3ab9c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> { let client = ReportClient::new(Config::from_cli()?)?; let client = Arc::new(client); - if client.config.mock_server { + if client.config.no_server { warn!( "Not sending to server {}:{}", client.config.host, client.config.port diff --git a/src/report_client.rs b/src/report_client.rs index 809b234..c654bb9 100644 --- a/src/report_client.rs +++ b/src/report_client.rs @@ -13,7 +13,7 @@ impl ReportClient { pub fn new(config: Config) -> anyhow::Result { let client = AwClient::new(&config.host, &config.port.to_string(), "awatcher"); - if !config.mock_server { + if !config.no_server { Self::create_bucket(&client, &config.idle_bucket_name, "afkstatus")?; Self::create_bucket(&client, &config.active_window_bucket_name, "currentwindow")?; } @@ -40,7 +40,7 @@ impl ReportClient { data, }; - if self.config.mock_server { + if self.config.no_server { return Ok(()); } @@ -76,7 +76,7 @@ impl ReportClient { data, }; - if self.config.mock_server { + if self.config.no_server { return Ok(()); } diff --git a/src/watchers.rs b/src/watchers.rs index 5dcae18..5f3c0f3 100644 --- a/src/watchers.rs +++ b/src/watchers.rs @@ -1,4 +1,5 @@ mod gnome_idle; +mod gnome_window; mod idle; mod kwin_window; mod wl_bindings; @@ -56,6 +57,7 @@ pub const IDLE: &WatcherConstructors = &[ pub const ACTIVE_WINDOW: &WatcherConstructors = &[ watcher!(wl_foreign_toplevel::WindowWatcher), - watcher!(kwin_window::WindowWatcher), watcher!(x11_window::WindowWatcher), + watcher!(kwin_window::WindowWatcher), + watcher!(gnome_window::WindowWatcher), ]; diff --git a/src/watchers/gnome_window.rs b/src/watchers/gnome_window.rs new file mode 100644 index 0000000..1fa78b9 --- /dev/null +++ b/src/watchers/gnome_window.rs @@ -0,0 +1,88 @@ +use crate::report_client::ReportClient; +use anyhow::Context; +use serde::Deserialize; +use std::{sync::Arc, thread}; +use zbus::blocking::Connection; + +use super::Watcher; + +pub struct WindowWatcher { + dbus_connection: Connection, + last_title: String, + last_app_id: String, +} + +#[derive(Deserialize, Default)] +struct WindowData { + title: String, + wm_class: String, +} + +impl WindowWatcher { + fn get_window_data(&self) -> anyhow::Result { + let call_response = self.dbus_connection.call_method( + Some("org.gnome.Shell"), + "/org/gnome/shell/extensions/FocusedWindow", + Some("org.gnome.shell.extensions.FocusedWindow"), + "Get", + &(), + ); + + match call_response { + Ok(json) => { + let json = json + .body::() + .with_context(|| "DBus interface cannot be parsed as string")?; + serde_json::from_str(&json).with_context(|| { + "DBus interface org.gnome.shell.extensions.FocusedWindow returned wrong JSON" + }) + } + Err(e) => { + if e.to_string().contains("No window in focus") { + Ok(WindowData::default()) + } else { + Err(e.into()) + } + } + } + } + + fn send_active_window(&mut self, client: &ReportClient) -> anyhow::Result<()> { + let data = self.get_window_data()?; + + if data.wm_class != self.last_app_id || data.title != self.last_title { + debug!( + r#"Changed window app_id="{}", title="{}""#, + data.wm_class, data.title + ); + self.last_app_id = data.wm_class; + self.last_title = data.title; + } + + client + .send_active_window(&self.last_app_id, &self.last_title) + .with_context(|| "Failed to send heartbeat for active window") + } +} + +impl Watcher for WindowWatcher { + fn new() -> anyhow::Result { + let watcher = Self { + dbus_connection: Connection::session()?, + last_app_id: String::new(), + last_title: String::new(), + }; + + Ok(watcher) + } + + fn watch(&mut self, client: &Arc) { + info!("Starting active window watcher"); + loop { + if let Err(error) = self.send_active_window(client) { + error!("Error on active window: {error}"); + } + thread::sleep(client.config.poll_time_window); + } + } +} diff --git a/src/watchers/x11_window.rs b/src/watchers/x11_window.rs index 6aab7f9..367c62b 100644 --- a/src/watchers/x11_window.rs +++ b/src/watchers/x11_window.rs @@ -15,7 +15,7 @@ impl WindowWatcher { if data.app_id != self.last_app_id || data.title != self.last_title { debug!( - "Changed window app_id=\"{}\", title=\"{}\"", + r#"Changed window app_id="{}", title="{}""#, data.app_id, data.title ); self.last_app_id = data.app_id;