From 990843bb05bd91f45b76115f760e1b4d1b9b5c57 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:07:10 +0200 Subject: [PATCH 1/3] x11: include wm_instance --- watchers/src/report_client.rs | 14 ++++++++++++++ watchers/src/watchers/x11_connection.rs | 18 +++++++++++++----- watchers/src/watchers/x11_window.rs | 17 ++++++++++------- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/watchers/src/report_client.rs b/watchers/src/report_client.rs index 153500d..24d8eb6 100644 --- a/watchers/src/report_client.rs +++ b/watchers/src/report_client.rs @@ -94,6 +94,15 @@ impl ReportClient { } pub async fn send_active_window(&self, app_id: &str, title: &str) -> anyhow::Result<()> { + self.send_active_window_with_instance(app_id, title, None).await + } + + pub async fn send_active_window_with_instance( + &self, + app_id: &str, + title: &str, + wm_instance: Option<&str>, + ) -> anyhow::Result<()> { let mut data = Map::new(); let replacement = self.config.window_data_replacement(app_id, title); @@ -116,6 +125,11 @@ impl ReportClient { ); data.insert("app".to_string(), Value::String(inserted_app_id)); data.insert("title".to_string(), Value::String(inserted_title)); + + if let Some(instance) = wm_instance { + data.insert("wm_instance".to_string(), Value::String(instance.to_string())); + } + let event = AwEvent { id: None, timestamp: Utc::now(), 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..a6f91ad 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -6,25 +6,27 @@ 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 { 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) + .send_active_window_with_instance(&self.last_app_id, &self.last_title, Some(&self.last_wm_instance)) .await .with_context(|| "Failed to send heartbeat for active window") } @@ -40,6 +42,7 @@ impl Watcher for WindowWatcher { client, last_title: String::new(), last_app_id: String::new(), + last_wm_instance: String::new(), }) } From bd780e1cbbc8e0bde2669ab1c6977995b154630d Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sun, 6 Oct 2024 12:49:45 +0200 Subject: [PATCH 2/3] x11: log aw-none if _NET_ACTIVE_WINDOW == 0 --- watchers/src/watchers/x11_connection.rs | 68 +++++++++++++++---------- watchers/src/watchers/x11_window.rs | 25 ++++++--- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/watchers/src/watchers/x11_connection.rs b/watchers/src/watchers/x11_connection.rs index 173591d..5a045f9 100644 --- a/watchers/src/watchers/x11_connection.rs +++ b/watchers/src/watchers/x11_connection.rs @@ -67,33 +67,38 @@ impl X11Client { }) } - pub fn active_window_data(&mut self) -> anyhow::Result { + pub fn active_window_data(&mut self) -> anyhow::Result> { self.execute_with_reconnect(|client| { - let focus: Window = client.find_active_window()?; + let focus = 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, - )?; + match focus { + Some(window) => { + let name = client.get_property( + window, + client.intern_atom("_NET_WM_NAME")?, + "_NET_WM_NAME", + client.intern_atom("UTF8_STRING")?, + u32::MAX, + )?; + let class = client.get_property( + window, + AtomEnum::WM_CLASS.into(), + "WM_CLASS", + AtomEnum::STRING.into(), + u32::MAX, + )?; - let title = str::from_utf8(&name.value).with_context(|| "Invalid title UTF")?; - let (instance, class) = parse_wm_class(&class)?; + 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: class, - wm_instance: instance, - }) + Ok(Some(WindowData { + title: title.to_string(), + app_id: class, + wm_instance: instance, + })) + } + None => Ok(None), + } }) } @@ -122,7 +127,7 @@ impl X11Client { .atom) } - fn find_active_window(&self) -> anyhow::Result { + fn find_active_window(&self) -> anyhow::Result> { let window: Atom = AtomEnum::WINDOW.into(); let net_active_window = self.intern_atom("_NET_ACTIVE_WINDOW")?; let active_window = self.get_property( @@ -134,20 +139,27 @@ impl X11Client { )?; if active_window.format == 32 && active_window.length == 1 { - active_window + let window_id = active_window .value32() .ok_or(anyhow!("Invalid message. Expected value with format = 32"))? .next() - .ok_or(anyhow!("Active window is not found")) + .ok_or(anyhow!("Active window is not found"))?; + + // Check if the window_id is 0 (no active window) + if window_id == 0 { + return Ok(None); + } + + Ok(Some(window_id)) } else { // Query the input focus - Ok(self + Ok(Some(self .connection .get_input_focus() .with_context(|| "Failed to get input focus")? .reply() .with_context(|| "Failed to read input focus from reply")? - .focus) + .focus)) } } } diff --git a/watchers/src/watchers/x11_window.rs b/watchers/src/watchers/x11_window.rs index a6f91ad..8e06f49 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -15,18 +15,31 @@ impl WindowWatcher { 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 || data.wm_instance != self.last_wm_instance { + let (app_id, title, wm_instance) = match data { + Some(window_data) => ( + window_data.app_id, + window_data.title, + window_data.wm_instance, + ), + None => { + // No active window, set all values to "aw-none" + ("aw-none".to_string(), "aw-none".to_string(), "aw-none".to_string()) + } + }; + + if app_id != self.last_app_id || title != self.last_title || wm_instance != self.last_wm_instance { debug!( r#"Changed window app_id="{}", title="{}", wm_instance="{}""#, - data.app_id, data.title, data.wm_instance + app_id, title, wm_instance ); - self.last_app_id = data.app_id.clone(); - self.last_title = data.title.clone(); - self.last_wm_instance = data.wm_instance.clone(); + self.last_app_id = app_id.clone(); + self.last_title = title.clone(); + self.last_wm_instance = wm_instance.clone(); + } client - .send_active_window_with_instance(&self.last_app_id, &self.last_title, Some(&self.last_wm_instance)) + .send_active_window_with_instance(&app_id, &title, Some(&wm_instance)) .await .with_context(|| "Failed to send heartbeat for active window") } From a20f09ef58d390e3dbe6f24b641cacb36caafcd2 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:07:48 +0200 Subject: [PATCH 3/3] x11: send end of previous active window --- watchers/src/report_client.rs | 20 ++++++++++---------- watchers/src/watchers/x11_window.rs | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/watchers/src/report_client.rs b/watchers/src/report_client.rs index 24d8eb6..6f5edd3 100644 --- a/watchers/src/report_client.rs +++ b/watchers/src/report_client.rs @@ -41,18 +41,18 @@ impl ReportClient { Fut: Future>, E: std::error::Error + Send + Sync + 'static, { - for (attempt, &secs) in [1, 2].iter().enumerate() { + for (attempt, secs) in [0.01, 0.1, 1., 2.].iter().enumerate() { match f().await { - Ok(val) => return Ok(val), - Err(e) - if e.to_string() - .contains("tcp connect error: Connection refused") => - { - warn!("Failed to connect on attempt #{attempt}, retrying: {}", e); - - tokio::time::sleep(tokio::time::Duration::from_secs(secs)).await; + Ok(val) => { + if attempt > 0 { + debug!("OK at attempt #{}", attempt + 1); + } + return Ok(val); + } + Err(e) => { + warn!("Failed on attempt #{}, retrying in {:.1}s: {}", attempt + 1, secs, e); + tokio::time::sleep(tokio::time::Duration::from_secs_f64(*secs)).await; } - Err(e) => return Err(e), } } diff --git a/watchers/src/watchers/x11_window.rs b/watchers/src/watchers/x11_window.rs index 8e06f49..02a16fd 100644 --- a/watchers/src/watchers/x11_window.rs +++ b/watchers/src/watchers/x11_window.rs @@ -32,6 +32,10 @@ impl WindowWatcher { r#"Changed window app_id="{}", title="{}", wm_instance="{}""#, app_id, title, wm_instance ); + client + .send_active_window_with_instance(&self.last_app_id, &self.last_title, Some(&self.last_wm_instance)) + .await + .with_context(|| "Failed to send heartbeat for previous window")?; self.last_app_id = app_id.clone(); self.last_title = title.clone(); self.last_wm_instance = wm_instance.clone();