diff --git a/watchers/src/report_client.rs b/watchers/src/report_client.rs index fb2fcf7..aa5fb44 100644 --- a/watchers/src/report_client.rs +++ b/watchers/src/report_client.rs @@ -4,6 +4,7 @@ use anyhow::Context; use aw_client_rust::{AwClient, Event as AwEvent}; use chrono::{DateTime, TimeDelta, Utc}; use serde_json::{Map, Value}; +use std::collections::HashMap; use std::error::Error; use std::future::Future; @@ -93,6 +94,16 @@ impl ReportClient { } pub async fn send_active_window(&self, app_id: &str, title: &str) -> anyhow::Result<()> { + self.send_active_window_with_extra(app_id, title, None) + .await + } + + pub async fn send_active_window_with_extra( + &self, + app_id: &str, + title: &str, + extra_data: Option>, + ) -> anyhow::Result<()> { let mut data = Map::new(); if let Some((inserted_app_id, inserted_title)) = self.get_filtered_data(app_id, title) { @@ -104,6 +115,12 @@ impl ReportClient { data.insert("app".to_string(), Value::String(inserted_app_id)); data.insert("title".to_string(), Value::String(inserted_title)); + + if let Some(extra) = extra_data { + for (key, value) in extra { + data.insert(key, Value::String(value)); + } + } } else { return Ok(()); } diff --git a/watchers/src/watchers/x11_connection.rs b/watchers/src/watchers/x11_connection.rs index 91244cc..173591d 100644 --- a/watchers/src/watchers/x11_connection.rs +++ b/watchers/src/watchers/x11_connection.rs @@ -9,6 +9,7 @@ use x11rb::rust_connection::RustConnection; pub struct WindowData { pub title: String, pub app_id: String, + pub wm_instance: String, } pub struct X11Client { @@ -86,10 +87,12 @@ impl X11Client { )?; let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?; + let (instance, class) = parse_wm_class(&class)?; Ok(WindowData { title: title.to_string(), - app_id: parse_wm_class(&class)?.to_string(), + app_id: class, + wm_instance: instance, }) }) } @@ -149,21 +152,26 @@ impl X11Client { } } -fn parse_wm_class(property: &GetPropertyReply) -> anyhow::Result<&str> { +fn parse_wm_class(property: &GetPropertyReply) -> anyhow::Result<(String, String)> { if property.format != 8 { bail!("Malformed property: wrong format"); } let value = &property.value; // The property should contain two null-terminated strings. Find them. if let Some(middle) = value.iter().position(|&b| b == 0) { - let (_, class) = value.split_at(middle); - // Skip the null byte at the beginning + let (instance, class) = value.split_at(middle); + // Remove the null byte at the end of the instance + let instance = &instance[..instance.len()]; + // Skip the null byte at the beginning of the class let mut class = &class[1..]; // Remove the last null byte from the class, if it is there. if class.last() == Some(&0) { class = &class[..class.len() - 1]; } - Ok(std::str::from_utf8(class)?) + Ok(( + std::str::from_utf8(instance)?.to_string(), + std::str::from_utf8(class)?.to_string(), + )) } else { bail!("Missing null byte") } diff --git a/watchers/src/watchers/x11_window.rs b/watchers/src/watchers/x11_window.rs index 3776a72..1534a6e 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -2,31 +2,55 @@ use super::{x11_connection::X11Client, Watcher}; use crate::report_client::ReportClient; use anyhow::Context; use async_trait::async_trait; +use std::collections::HashMap; use std::sync::Arc; pub struct WindowWatcher { client: X11Client, - last_title: String, last_app_id: String, + last_title: String, + last_wm_instance: String, } impl WindowWatcher { + pub async fn send_active_window_with_instance( + &self, + client: &ReportClient, + app_id: &str, + title: &str, + wm_instance: &str, + ) -> anyhow::Result<()> { + let mut extra_data = HashMap::new(); + extra_data.insert("wm_instance".to_string(), wm_instance.to_string()); + client + .send_active_window_with_extra(app_id, title, Some(extra_data)) + .await + } + async fn send_active_window(&mut self, client: &ReportClient) -> anyhow::Result<()> { let data = self.client.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 + || data.wm_instance != self.last_wm_instance + { debug!( - r#"Changed window app_id="{}", title="{}""#, - data.app_id, data.title + r#"Changed window app_id="{}", title="{}", wm_instance="{}""#, + data.app_id, data.title, data.wm_instance ); - self.last_app_id = data.app_id; - self.last_title = data.title; + self.last_app_id = data.app_id.clone(); + self.last_title = data.title.clone(); + self.last_wm_instance = data.wm_instance.clone(); } - client - .send_active_window(&self.last_app_id, &self.last_title) - .await - .with_context(|| "Failed to send heartbeat for active window") + self.send_active_window_with_instance( + client, + &self.last_app_id, + &self.last_title, + &self.last_wm_instance, + ) + .await + .with_context(|| "Failed to send heartbeat for active window") } } @@ -40,6 +64,7 @@ impl Watcher for WindowWatcher { client, last_title: String::new(), last_app_id: String::new(), + last_wm_instance: String::new(), }) }