mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-06 11:35:46 +00:00
Merge 9daebc85c0f81c4e450227d1b7b1ea2b86a2b8c9 into b4cf791a5efc3d561ecb96ac8cfaa831f9b76877
This commit is contained in:
commit
8a6a6c6549
@ -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(),
|
||||
|
@ -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 {
|
||||
@ -66,31 +67,38 @@ impl X11Client {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn active_window_data(&mut self) -> anyhow::Result<WindowData> {
|
||||
pub fn active_window_data(&mut self) -> anyhow::Result<Option<WindowData>> {
|
||||
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 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(),
|
||||
})
|
||||
Ok(Some(WindowData {
|
||||
title: title.to_string(),
|
||||
app_id: class,
|
||||
wm_instance: instance,
|
||||
}))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -119,7 +127,7 @@ impl X11Client {
|
||||
.atom)
|
||||
}
|
||||
|
||||
fn find_active_window(&self) -> anyhow::Result<Window> {
|
||||
fn find_active_window(&self) -> anyhow::Result<Option<Window>> {
|
||||
let window: Atom = AtomEnum::WINDOW.into();
|
||||
let net_active_window = self.intern_atom("_NET_ACTIVE_WINDOW")?;
|
||||
let active_window = self.get_property(
|
||||
@ -131,39 +139,51 @@ 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
@ -6,25 +6,40 @@ 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 {
|
||||
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="{}""#,
|
||||
data.app_id, data.title
|
||||
r#"Changed window app_id="{}", title="{}", wm_instance="{}""#,
|
||||
app_id, title, wm_instance
|
||||
);
|
||||
self.last_app_id = data.app_id;
|
||||
self.last_title = data.title;
|
||||
self.last_app_id = app_id.clone();
|
||||
self.last_title = title.clone();
|
||||
self.last_wm_instance = wm_instance.clone();
|
||||
|
||||
}
|
||||
|
||||
client
|
||||
.send_active_window(&self.last_app_id, &self.last_title)
|
||||
.send_active_window_with_instance(&app_id, &title, Some(&wm_instance))
|
||||
.await
|
||||
.with_context(|| "Failed to send heartbeat for active window")
|
||||
}
|
||||
@ -40,6 +55,7 @@ impl Watcher for WindowWatcher {
|
||||
client,
|
||||
last_title: String::new(),
|
||||
last_app_id: String::new(),
|
||||
last_wm_instance: String::new(),
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user