From 6645695149874482df50eb2aaef5a31e5e1b2d3b Mon Sep 17 00:00:00 2001 From: Demmie <2e3s19@gmail.com> Date: Sat, 15 Apr 2023 23:34:53 -0400 Subject: [PATCH] Implement wlr foreign toplevel v1 protocol This watches active windows. --- src/config.rs | 8 + src/main.rs | 66 +++-- ...oreign-toplevel-management-unstable-v1.xml | 259 ++++++++++++++++++ src/report_client.rs | 31 ++- src/wl_bindings.rs | 19 ++ src/wl_connection.rs | 31 +++ src/wl_foreign_toplevel.rs | 175 ++++++++++++ src/wl_kwin_idle.rs | 83 ++---- src/wl_kwin_window.rs | 32 +-- 9 files changed, 581 insertions(+), 123 deletions(-) create mode 100644 src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml create mode 100644 src/wl_foreign_toplevel.rs diff --git a/src/config.rs b/src/config.rs index 0db62bb..64553e5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,6 +8,8 @@ pub struct Config { pub idle_timeout: u32, pub poll_time_idle: u32, pub poll_time_window: u32, + pub idle_bucket_name: String, + pub active_window_bucket_name: String, } impl Config { @@ -35,12 +37,18 @@ impl Config { ]) .get_matches(); + let hostname = gethostname::gethostname().into_string().unwrap(); + let idle_bucket_name = format!("aw-watcher-afk_{hostname}"); + let active_window_bucket_name = format!("aw-watcher-window_{hostname}"); + Self { port: *matches.get_one("port").unwrap(), host: String::clone(matches.get_one("host").unwrap()), idle_timeout: *matches.get_one("idle-timeout").unwrap(), poll_time_idle: *matches.get_one("poll-time-idle").unwrap(), poll_time_window: *matches.get_one("poll-time-window").unwrap(), + idle_bucket_name, + active_window_bucket_name, } } } diff --git a/src/main.rs b/src/main.rs index fa55dce..332822c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ mod config; mod report_client; mod wl_bindings; mod wl_connection; +mod wl_foreign_toplevel; mod wl_kwin_idle; mod wl_kwin_window; @@ -18,8 +19,45 @@ use std::{error::Error, str::FromStr, sync::Arc, thread}; use wl_kwin_idle::KwinIdleWatcher; use wl_kwin_window::KwinWindowWatcher; +use crate::wl_foreign_toplevel::WlrForeignToplevelWatcher; + type BoxedError = Box; +trait Watcher: Send { + fn new() -> Result + where + Self: Sized; + fn watch(&mut self, client: &Arc); +} + +type BoxedWatcher = Box; + +type WatcherConstructor = fn() -> Result; +type WatcherConstructors = [WatcherConstructor]; + +trait WatchersFilter { + fn filter_first_supported(&self) -> Option; +} + +impl WatchersFilter for WatcherConstructors { + fn filter_first_supported(&self) -> Option { + self.iter().find_map(|watcher| match watcher() { + Ok(watcher) => Some(watcher), + Err(e) => { + info!("Watcher cannot run: {e}"); + None + } + }) + } +} + +const IDLE_WATCHERS: [WatcherConstructor; 1] = [|| Ok(Box::new(KwinIdleWatcher::new()?))]; + +const ACTIVE_WINDOW_WATCHERS: [WatcherConstructor; 2] = [ + || Ok(Box::new(WlrForeignToplevelWatcher::new()?)), + || Ok(Box::new(KwinWindowWatcher::new()?)), +]; + fn setup_logger() -> Result<(), fern::InitError> { let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string()); @@ -47,14 +85,6 @@ fn setup_logger() -> Result<(), fern::InitError> { Ok(()) } -type WatcherConstructor = fn() -> Result, BoxedError>; -trait Watcher: Send { - fn new() -> Result - where - Self: Sized; - fn watch(&mut self, client: &Arc); -} - fn main() { setup_logger().unwrap(); @@ -69,20 +99,8 @@ fn main() { info!("Polling period: {} seconds", client.config.poll_time_idle); let mut thread_handlers = Vec::new(); - let idle_watchers: Vec = vec![|| Ok(Box::new(KwinIdleWatcher::new()?))]; - let window_watchers: Vec = vec![|| Ok(Box::new(KwinWindowWatcher::new()?))]; - let filter_watcher = |watchers: Vec| { - watchers.iter().find_map(|watcher| match watcher() { - Ok(watcher) => Some(watcher), - Err(e) => { - info!("Watcher cannot run: {e}"); - None - } - }) - }; - - let idle_watcher = filter_watcher(idle_watchers); + let idle_watcher = IDLE_WATCHERS.filter_first_supported(); if let Some(mut watcher) = idle_watcher { let thread_client = Arc::clone(&client); let idle_handler = thread::spawn(move || watcher.watch(&thread_client)); @@ -91,7 +109,7 @@ fn main() { warn!("No supported idle handler is found"); } - let window_watcher = filter_watcher(window_watchers); + let window_watcher = ACTIVE_WINDOW_WATCHERS.filter_first_supported(); if let Some(mut watcher) = window_watcher { let thread_client = Arc::clone(&client); let active_window_handler = thread::spawn(move || watcher.watch(&thread_client)); @@ -101,6 +119,8 @@ fn main() { } for handler in thread_handlers { - handler.join().unwrap(); + if handler.join().is_err() { + error!("Thread failed with error"); + } } } diff --git a/src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml b/src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml new file mode 100644 index 0000000..a97738f --- /dev/null +++ b/src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml @@ -0,0 +1,259 @@ + + + + Copyright © 2018 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + The purpose of this protocol is to enable the creation of taskbars + and docks by providing them with a list of opened applications and + letting them request certain actions on them, like maximizing, etc. + + After a client binds the zwlr_foreign_toplevel_manager_v1, each opened + toplevel window will be sent via the toplevel event + + + + + This event is emitted whenever a new toplevel window is created. It + is emitted for all toplevels, regardless of the app that has created + them. + + All initial details of the toplevel(title, app_id, states, etc.) will + be sent immediately after this event via the corresponding events in + zwlr_foreign_toplevel_handle_v1. + + + + + + + Indicates the client no longer wishes to receive events for new toplevels. + However the compositor may emit further toplevel_created events, until + the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + This event indicates that the compositor is done sending events to the + zwlr_foreign_toplevel_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + + A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel + window. Each app may have multiple opened toplevels. + + Each toplevel has a list of outputs it is visible on, conveyed to the + client with the output_enter and output_leave events. + + + + + This event is emitted whenever the title of the toplevel changes. + + + + + + + This event is emitted whenever the app-id of the toplevel changes. + + + + + + + This event is emitted whenever the toplevel becomes visible on + the given output. A toplevel may be visible on multiple outputs. + + + + + + + This event is emitted whenever the toplevel stops being visible on + the given output. It is guaranteed that an entered-output event + with the same output has been emitted before this event. + + + + + + + Requests that the toplevel be maximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unmaximized. If the maximized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be minimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Requests that the toplevel be unminimized. If the minimized state actually + changes, this will be indicated by the state event. + + + + + + Request that this toplevel be activated on the given seat. + There is no guarantee the toplevel will be actually activated. + + + + + + + The different states that a toplevel can have. These have the same meaning + as the states with the same names defined in xdg-toplevel + + + + + + + + + + + This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 + is created and each time the toplevel state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + + This event is sent after all changes in the toplevel state have been + sent. + + This allows changes to the zwlr_foreign_toplevel_handle_v1 properties + to be seen as atomic, even if they happen via multiple events. + + + + + + Send a request to the toplevel to close itself. The compositor would + typically use a shell-specific method to carry out this request, for + example by sending the xdg_toplevel.close event. However, this gives + no guarantees the toplevel will actually be destroyed. If and when + this happens, the zwlr_foreign_toplevel_handle_v1.closed event will + be emitted. + + + + + + The rectangle of the surface specified in this request corresponds to + the place where the app using this protocol represents the given toplevel. + It can be used by the compositor as a hint for some operations, e.g + minimizing. The client is however not required to set this, in which + case the compositor is free to decide some default value. + + If the client specifies more than one rectangle, only the last one is + considered. + + The dimensions are given in surface-local coordinates. + Setting width=height=0 removes the already-set rectangle. + + + + + + + + + + + + + + + + This event means the toplevel has been destroyed. It is guaranteed there + won't be any more events for this zwlr_foreign_toplevel_handle_v1. The + toplevel itself becomes inert so any requests will be ignored except the + destroy request. + + + + + + Destroys the zwlr_foreign_toplevel_handle_v1 object. + + This request should be called either when the client does not want to + use the toplevel anymore or after the closed event to finalize the + destruction of the object. + + + + + + + + Requests that the toplevel be fullscreened on the given output. If the + fullscreen state and/or the outputs the toplevel is visible on actually + change, this will be indicated by the state and output_enter/leave + events. + + The output parameter is only a hint to the compositor. Also, if output + is NULL, the compositor should decide which output the toplevel will be + fullscreened on, if at all. + + + + + + + Requests that the toplevel be unfullscreened. If the fullscreen state + actually changes, this will be indicated by the state event. + + + + diff --git a/src/report_client.rs b/src/report_client.rs index 9260907..663954a 100644 --- a/src/report_client.rs +++ b/src/report_client.rs @@ -16,6 +16,10 @@ impl ReportClient { let host = config.host.clone(); let port = config.port.to_string(); + let client = AwClient::new(&host, &port, "awatcher"); + Self::create_bucket(&client, &config.idle_bucket_name, "afkstatus").unwrap(); + Self::create_bucket(&client, &config.active_window_bucket_name, "currentwindow").unwrap(); + Self { config, client: AwClient::new(&host, &port, "awatcher"), @@ -24,7 +28,6 @@ impl ReportClient { pub fn ping( &self, - bucket_name: &str, is_idle: bool, timestamp: DateTime, duration: Duration, @@ -44,25 +47,39 @@ impl ReportClient { let pulsetime = f64::from(self.config.idle_timeout + self.config.poll_time_idle); self.client - .heartbeat(bucket_name, &event, pulsetime) + .heartbeat(&self.config.idle_bucket_name, &event, pulsetime) .map_err(|_| "Failed to send heartbeat")?; Ok(()) } - pub fn heartbeat(&self, bucket_name: &str, event: &AwEvent) -> Result<(), BoxedError> { + pub fn send_active_window(&self, app_id: &str, title: &str) -> Result<(), BoxedError> { + let mut data = Map::new(); + data.insert("app".to_string(), Value::String(app_id.to_string())); + data.insert("title".to_string(), Value::String(title.to_string())); + let event = AwEvent { + id: None, + timestamp: Utc::now(), + duration: Duration::zero(), + data, + }; + let interval_margin: f64 = f64::from(self.config.poll_time_idle + 1); self.client - .heartbeat(bucket_name, event, interval_margin) + .heartbeat( + &self.config.active_window_bucket_name, + &event, + interval_margin, + ) .map_err(|_| "Failed to send heartbeat for active window".into()) } - pub fn create_bucket( - &self, + fn create_bucket( + client: &AwClient, bucket_name: &str, bucket_type: &str, ) -> Result<(), Box> { - self.client + client .create_bucket_simple(bucket_name, bucket_type) .map_err(|e| format!("Failed to create bucket {bucket_name}: {e}").into()) } diff --git a/src/wl_bindings.rs b/src/wl_bindings.rs index 56bd244..9380179 100644 --- a/src/wl_bindings.rs +++ b/src/wl_bindings.rs @@ -20,3 +20,22 @@ pub mod idle { wayland_scanner::generate_client_code!("src/protocols/idle.xml"); } + +pub mod wlr_foreign_toplevel { + #![allow(dead_code,non_camel_case_types,unused_unsafe,unused_variables)] + #![allow(non_upper_case_globals,non_snake_case,unused_imports)] + #![allow(missing_docs, clippy::all)] + #![allow(clippy::wildcard_imports)] + + //! Client-side API of this protocol + use wayland_client; + use wayland_client::protocol::*; + + pub mod __interfaces { + use wayland_client::protocol::__interfaces::*; + wayland_scanner::generate_interfaces!("src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml"); + } + use self::__interfaces::*; + + wayland_scanner::generate_client_code!("src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml"); +} diff --git a/src/wl_connection.rs b/src/wl_connection.rs index 1c88c9d..16353b4 100644 --- a/src/wl_connection.rs +++ b/src/wl_connection.rs @@ -7,6 +7,24 @@ use wayland_client::{ }; use wl_bindings::idle::org_kde_kwin_idle::OrgKdeKwinIdle; use wl_bindings::idle::org_kde_kwin_idle_timeout::OrgKdeKwinIdleTimeout; +use wl_bindings::wlr_foreign_toplevel::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1; + +macro_rules! subscribe_state { + ($struct_name:ty, $data_name:ty, $state:ty) => { + impl Dispatch<$struct_name, $data_name> for $state { + fn event( + _: &mut Self, + _: &$struct_name, + _: <$struct_name as Proxy>::Event, + _: &$data_name, + _: &Connection, + _: &QueueHandle, + ) { + } + } + }; +} +pub(crate) use subscribe_state; pub struct WlEventConnection { pub globals: GlobalList, @@ -37,6 +55,19 @@ where }) } + pub fn get_foreign_toplevel_manager(&self) -> Result + where + T: Dispatch, + { + self.globals + .bind::( + &self.queue_handle, + 1..=OrgKdeKwinIdle::interface().version, + (), + ) + .map_err(std::convert::Into::into) + } + pub fn get_kwin_idle(&self) -> Result where T: Dispatch, diff --git a/src/wl_foreign_toplevel.rs b/src/wl_foreign_toplevel.rs new file mode 100644 index 0000000..36ac30a --- /dev/null +++ b/src/wl_foreign_toplevel.rs @@ -0,0 +1,175 @@ +use std::collections::HashMap; +use std::{sync::Arc, thread, time}; + +use crate::{wl_connection::subscribe_state, Watcher}; + +use super::report_client::ReportClient; +use super::wl_bindings; +use super::wl_connection::WlEventConnection; +use super::BoxedError; +use chrono::{DateTime, Utc}; +use wayland_client::{ + event_created_child, globals::GlobalListContents, protocol::wl_registry, Connection, Dispatch, + Proxy, QueueHandle, +}; +use wl_bindings::wlr_foreign_toplevel::zwlr_foreign_toplevel_handle_v1::{ + Event as HandleEvent, State as HandleState, ZwlrForeignToplevelHandleV1, +}; +use wl_bindings::wlr_foreign_toplevel::zwlr_foreign_toplevel_manager_v1::{ + Event as ManagerEvent, ZwlrForeignToplevelManagerV1, EVT_TOPLEVEL_OPCODE, +}; + +struct WindowData { + app_id: String, + title: String, +} + +struct ToplevelState { + windows: HashMap, + current_window_id: Option, + _last_input_time: DateTime, + _is_idle: bool, + _is_changed: bool, + client: Arc, +} + +impl ToplevelState { + fn new(client: Arc) -> Self { + Self { + windows: HashMap::new(), + current_window_id: None, + _last_input_time: Utc::now(), + _is_idle: false, + _is_changed: false, + client, + } + } +} + +impl Dispatch for ToplevelState { + fn event( + state: &mut Self, + _: &ZwlrForeignToplevelManagerV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + match event { + ManagerEvent::Toplevel { toplevel } => { + debug!("Toplevel handle is received {}", toplevel.id()); + state.windows.insert( + toplevel.id().to_string(), + WindowData { + app_id: "unknown".into(), + title: "unknown".into(), + }, + ); + } + ManagerEvent::Finished => { + error!("Toplevel manager is finished, the application may crash"); + } + }; + } + + event_created_child!(ToplevelState, ZwlrForeignToplevelManagerV1, [ + EVT_TOPLEVEL_OPCODE => (ZwlrForeignToplevelHandleV1, ()), + ]); +} + +subscribe_state!(wl_registry::WlRegistry, GlobalListContents, ToplevelState); +subscribe_state!(wl_registry::WlRegistry, (), ToplevelState); + +impl Dispatch for ToplevelState { + fn event( + toplevel_state: &mut Self, + handle: &ZwlrForeignToplevelHandleV1, + event: ::Event, + _: &(), + _: &Connection, + _: &QueueHandle, + ) { + let id = handle.id().to_string(); + let window = toplevel_state.windows.get_mut(&id); + if let Some(window) = window { + match event { + HandleEvent::Title { title } => { + trace!("Title is changed for {id}: {title}"); + window.title = title; + } + HandleEvent::AppId { app_id } => { + trace!("App ID is changed for {id}: {app_id}"); + window.app_id = app_id; + } + HandleEvent::State { state } => { + trace!("State is changed for {id}: {:?}", state); + if state.contains(&(HandleState::Activated as u8)) { + trace!("Window is activated: {id}"); + toplevel_state.current_window_id = Some(id); + } + } + HandleEvent::Done => trace!("Done: {id}"), + HandleEvent::Closed => { + trace!("Window is closed: {id}"); + if toplevel_state.windows.remove(&id).is_none() { + warn!("Window is already removed: {id}"); + } + } + _ => (), + }; + } else { + error!("Window is not found: {id}"); + } + } +} + +impl ToplevelState { + fn send_active_window(&self) -> Result<(), BoxedError> { + let active_window_id = self + .current_window_id + .as_ref() + .ok_or("Current window is unknown")?; + let active_window = self.windows.get(active_window_id).ok_or(format!( + "Current window is not found by ID {active_window_id}" + ))?; + + self.client + .send_active_window(&active_window.app_id, &active_window.title) + .map_err(|_| "Failed to send heartbeat for active window".into()) + } +} + +pub struct WlrForeignToplevelWatcher { + connection: WlEventConnection, +} + +impl Watcher for WlrForeignToplevelWatcher { + fn new() -> Result { + let connection: WlEventConnection = WlEventConnection::connect()?; + connection.get_foreign_toplevel_manager()?; + + Ok(Self { connection }) + } + + fn watch(&mut self, client: &Arc) { + let mut toplevel_state = ToplevelState::new(Arc::clone(client)); + + self.connection + .event_queue + .roundtrip(&mut toplevel_state) + .unwrap(); + + info!("Starting wlr foreign toplevel watcher"); + loop { + if let Err(e) = self.connection.event_queue.roundtrip(&mut toplevel_state) { + error!("Event queue is not processed: {e}"); + } else if let Err(e) = toplevel_state.send_active_window() { + error!("Error on idle iteration {e}"); + } + + thread::sleep(time::Duration::from_secs(u64::from( + client.config.poll_time_window, + ))); + } + } +} diff --git a/src/wl_kwin_idle.rs b/src/wl_kwin_idle.rs index 3fe60bb..b3bdf23 100644 --- a/src/wl_kwin_idle.rs +++ b/src/wl_kwin_idle.rs @@ -2,7 +2,7 @@ use crate::Watcher; use super::report_client::ReportClient; use super::wl_bindings; -use super::wl_connection::WlEventConnection; +use super::wl_connection::{subscribe_state, WlEventConnection}; use super::BoxedError; use chrono::{DateTime, Duration, Utc}; use std::{sync::Arc, thread, time}; @@ -22,7 +22,6 @@ struct IdleState { is_idle: bool, is_changed: bool, client: Arc, - bucket_name: String, } impl Drop for IdleState { @@ -33,18 +32,13 @@ impl Drop for IdleState { } impl IdleState { - fn new( - idle_timeout: OrgKdeKwinIdleTimeout, - client: Arc, - bucket_name: String, - ) -> Self { + fn new(idle_timeout: OrgKdeKwinIdleTimeout, client: Arc) -> Self { Self { idle_timeout, last_input_time: Utc::now(), is_idle: false, is_changed: false, client, - bucket_name, } } @@ -61,8 +55,7 @@ impl IdleState { debug!("Resumed"); } - fn run_loop(&mut self, connection: &mut WlEventConnection) -> Result<(), BoxedError> { - connection.event_queue.roundtrip(self).unwrap(); + fn send_ping(&mut self) -> Result<(), BoxedError> { let now = Utc::now(); if !self.is_idle { self.last_input_time = now; @@ -71,14 +64,9 @@ impl IdleState { if self.is_changed { let result = if self.is_idle { debug!("Reporting as changed to idle"); + self.client + .ping(false, self.last_input_time, Duration::zero())?; self.client.ping( - &self.bucket_name, - false, - self.last_input_time, - Duration::zero(), - )?; - self.client.ping( - &self.bucket_name, true, self.last_input_time + Duration::milliseconds(1), Duration::zero(), @@ -86,14 +74,9 @@ impl IdleState { } else { debug!("Reporting as no longer idle"); + self.client + .ping(true, self.last_input_time, Duration::zero())?; self.client.ping( - &self.bucket_name, - true, - self.last_input_time, - Duration::zero(), - )?; - self.client.ping( - &self.bucket_name, false, self.last_input_time + Duration::milliseconds(1), Duration::zero(), @@ -103,44 +86,20 @@ impl IdleState { result } else if self.is_idle { trace!("Reporting as idle"); - self.client.ping( - &self.bucket_name, - true, - self.last_input_time, - now - self.last_input_time, - ) + self.client + .ping(true, self.last_input_time, now - self.last_input_time) } else { trace!("Reporting as not idle"); - self.client.ping( - &self.bucket_name, - false, - self.last_input_time, - Duration::zero(), - ) + self.client + .ping(false, self.last_input_time, Duration::zero()) } } } -macro_rules! subscribe_state { - ($struct_name:ty, $data_name:ty) => { - impl Dispatch<$struct_name, $data_name> for IdleState { - fn event( - _: &mut Self, - _: &$struct_name, - _: <$struct_name as Proxy>::Event, - _: &$data_name, - _: &Connection, - _: &QueueHandle, - ) { - } - } - }; -} - -subscribe_state!(wl_registry::WlRegistry, ()); -subscribe_state!(WlSeat, ()); -subscribe_state!(OrgKdeKwinIdle, ()); -subscribe_state!(wl_registry::WlRegistry, GlobalListContents); +subscribe_state!(wl_registry::WlRegistry, GlobalListContents, IdleState); +subscribe_state!(wl_registry::WlRegistry, (), IdleState); +subscribe_state!(WlSeat, (), IdleState); +subscribe_state!(OrgKdeKwinIdle, (), IdleState); impl Dispatch for IdleState { fn event( @@ -172,19 +131,11 @@ impl Watcher for KwinIdleWatcher { } fn watch(&mut self, client: &Arc) { - let bucket_name = format!( - "aw-watcher-afk_{}", - gethostname::gethostname().into_string().unwrap() - ); - - client.create_bucket(&bucket_name, "afkstatus").unwrap(); - let mut idle_state = IdleState::new( self.connection .get_kwin_idle_timeout(client.config.idle_timeout * 1000) .unwrap(), Arc::clone(client), - bucket_name, ); self.connection .event_queue @@ -193,7 +144,9 @@ impl Watcher for KwinIdleWatcher { info!("Starting idle watcher"); loop { - if let Err(e) = idle_state.run_loop(&mut self.connection) { + if let Err(e) = self.connection.event_queue.roundtrip(&mut idle_state) { + error!("Event queue is not processed: {e}"); + } else if let Err(e) = idle_state.send_ping() { error!("Error on idle iteration {e}"); } thread::sleep(time::Duration::from_secs(u64::from( diff --git a/src/wl_kwin_window.rs b/src/wl_kwin_window.rs index b3995ba..cdd48ff 100644 --- a/src/wl_kwin_window.rs +++ b/src/wl_kwin_window.rs @@ -7,9 +7,6 @@ use crate::Watcher; */ use super::report_client::ReportClient; use super::BoxedError; -use aw_client_rust::Event as AwEvent; -use chrono::{Duration, Utc}; -use serde_json::{Map, Value}; use std::env::temp_dir; use std::path::Path; use std::sync::{mpsc::channel, Arc, Mutex}; @@ -110,32 +107,14 @@ impl Drop for KWinScript { } } -fn send_heartbeat( +fn send_active_window( client: &ReportClient, - bucket_name: &str, active_window: &Arc>, ) -> Result<(), BoxedError> { - let event = { - let active_window = active_window.lock().map_err(|e| format!("{e}"))?; - let mut data = Map::new(); - data.insert( - "app".to_string(), - Value::String(active_window.resource_class.clone()), - ); - data.insert( - "title".to_string(), - Value::String(active_window.caption.clone()), - ); - AwEvent { - id: None, - timestamp: Utc::now(), - duration: Duration::zero(), - data, - } - }; + let active_window = active_window.lock().map_err(|e| format!("{e}"))?; client - .heartbeat(bucket_name, &event) + .send_active_window(&active_window.resource_class, &active_window.caption) .map_err(|_| "Failed to send heartbeat for active window".into()) } @@ -181,9 +160,6 @@ impl Watcher for KwinWindowWatcher { } fn watch(&mut self, client: &Arc) { - let hostname = gethostname::gethostname().into_string().unwrap(); - let bucket_name = format!("aw-watcher-window_{hostname}"); - self.kwin_script.load().unwrap(); let active_window = Arc::new(Mutex::new(ActiveWindow { @@ -217,7 +193,7 @@ impl Watcher for KwinWindowWatcher { info!("Starting active window watcher"); loop { - if let Err(error) = send_heartbeat(client, &bucket_name, &active_window) { + if let Err(error) = send_active_window(client, &active_window) { error!("Error on sending active window heartbeat: {error}"); } thread::sleep(time::Duration::from_secs(u64::from(