Implement window watcher for Gnome

This commit is contained in:
Demmie 2023-04-25 23:28:05 -04:00
parent 3c51ad8685
commit a841b69e47
No known key found for this signature in database
GPG Key ID: B06DAA3D432C6E9A
6 changed files with 99 additions and 9 deletions

View File

@ -16,7 +16,7 @@ pub struct Config {
pub poll_time_window: Duration, pub poll_time_window: Duration,
pub idle_bucket_name: String, pub idle_bucket_name: String,
pub active_window_bucket_name: String, pub active_window_bucket_name: String,
pub mock_server: bool, pub no_server: bool,
filters: Vec<Filter>, filters: Vec<Filter>,
} }
@ -42,7 +42,7 @@ impl Config {
arg!(--"poll-time-window" <SECONDS> "Period between sending heartbeats to the server for idle activity") arg!(--"poll-time-window" <SECONDS> "Period between sending heartbeats to the server for idle activity")
.value_parser(value_parser!(u32)) .value_parser(value_parser!(u32))
.default_value(defaults::poll_time_window_seconds().to_string()), .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)) .value_parser(value_parser!(bool))
.action(ArgAction::SetTrue), .action(ArgAction::SetTrue),
]) ])
@ -63,7 +63,7 @@ impl Config {
idle_bucket_name, idle_bucket_name,
active_window_bucket_name, active_window_bucket_name,
filters: config.client.filters, filters: config.client.filters,
mock_server: *matches.get_one("mock-server").unwrap(), no_server: *matches.get_one("no-server").unwrap(),
}) })
} }

View File

@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> {
let client = ReportClient::new(Config::from_cli()?)?; let client = ReportClient::new(Config::from_cli()?)?;
let client = Arc::new(client); let client = Arc::new(client);
if client.config.mock_server { if client.config.no_server {
warn!( warn!(
"Not sending to server {}:{}", "Not sending to server {}:{}",
client.config.host, client.config.port client.config.host, client.config.port

View File

@ -13,7 +13,7 @@ impl ReportClient {
pub fn new(config: Config) -> anyhow::Result<Self> { 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.no_server {
Self::create_bucket(&client, &config.idle_bucket_name, "afkstatus")?; Self::create_bucket(&client, &config.idle_bucket_name, "afkstatus")?;
Self::create_bucket(&client, &config.active_window_bucket_name, "currentwindow")?; Self::create_bucket(&client, &config.active_window_bucket_name, "currentwindow")?;
} }
@ -40,7 +40,7 @@ impl ReportClient {
data, data,
}; };
if self.config.mock_server { if self.config.no_server {
return Ok(()); return Ok(());
} }
@ -76,7 +76,7 @@ impl ReportClient {
data, data,
}; };
if self.config.mock_server { if self.config.no_server {
return Ok(()); return Ok(());
} }

View File

@ -1,4 +1,5 @@
mod gnome_idle; mod gnome_idle;
mod gnome_window;
mod idle; mod idle;
mod kwin_window; mod kwin_window;
mod wl_bindings; mod wl_bindings;
@ -56,6 +57,7 @@ pub const IDLE: &WatcherConstructors = &[
pub const ACTIVE_WINDOW: &WatcherConstructors = &[ pub const ACTIVE_WINDOW: &WatcherConstructors = &[
watcher!(wl_foreign_toplevel::WindowWatcher), watcher!(wl_foreign_toplevel::WindowWatcher),
watcher!(kwin_window::WindowWatcher),
watcher!(x11_window::WindowWatcher), watcher!(x11_window::WindowWatcher),
watcher!(kwin_window::WindowWatcher),
watcher!(gnome_window::WindowWatcher),
]; ];

View File

@ -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<WindowData> {
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::<String>()
.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<Self> {
let watcher = Self {
dbus_connection: Connection::session()?,
last_app_id: String::new(),
last_title: String::new(),
};
Ok(watcher)
}
fn watch(&mut self, client: &Arc<ReportClient>) {
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);
}
}
}

View File

@ -15,7 +15,7 @@ impl WindowWatcher {
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 {
debug!( debug!(
"Changed window app_id=\"{}\", title=\"{}\"", r#"Changed window app_id="{}", title="{}""#,
data.app_id, data.title data.app_id, data.title
); );
self.last_app_id = data.app_id; self.last_app_id = data.app_id;