mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-06 19:45:30 +00:00
Implement wlr foreign toplevel v1 protocol
This watches active windows.
This commit is contained in:
parent
32f0b5bccc
commit
6645695149
@ -8,6 +8,8 @@ pub struct Config {
|
|||||||
pub idle_timeout: u32,
|
pub idle_timeout: u32,
|
||||||
pub poll_time_idle: u32,
|
pub poll_time_idle: u32,
|
||||||
pub poll_time_window: u32,
|
pub poll_time_window: u32,
|
||||||
|
pub idle_bucket_name: String,
|
||||||
|
pub active_window_bucket_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
@ -35,12 +37,18 @@ impl Config {
|
|||||||
])
|
])
|
||||||
.get_matches();
|
.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 {
|
Self {
|
||||||
port: *matches.get_one("port").unwrap(),
|
port: *matches.get_one("port").unwrap(),
|
||||||
host: String::clone(matches.get_one("host").unwrap()),
|
host: String::clone(matches.get_one("host").unwrap()),
|
||||||
idle_timeout: *matches.get_one("idle-timeout").unwrap(),
|
idle_timeout: *matches.get_one("idle-timeout").unwrap(),
|
||||||
poll_time_idle: *matches.get_one("poll-time-idle").unwrap(),
|
poll_time_idle: *matches.get_one("poll-time-idle").unwrap(),
|
||||||
poll_time_window: *matches.get_one("poll-time-window").unwrap(),
|
poll_time_window: *matches.get_one("poll-time-window").unwrap(),
|
||||||
|
idle_bucket_name,
|
||||||
|
active_window_bucket_name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
src/main.rs
66
src/main.rs
@ -7,6 +7,7 @@ mod config;
|
|||||||
mod report_client;
|
mod report_client;
|
||||||
mod wl_bindings;
|
mod wl_bindings;
|
||||||
mod wl_connection;
|
mod wl_connection;
|
||||||
|
mod wl_foreign_toplevel;
|
||||||
mod wl_kwin_idle;
|
mod wl_kwin_idle;
|
||||||
mod wl_kwin_window;
|
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_idle::KwinIdleWatcher;
|
||||||
use wl_kwin_window::KwinWindowWatcher;
|
use wl_kwin_window::KwinWindowWatcher;
|
||||||
|
|
||||||
|
use crate::wl_foreign_toplevel::WlrForeignToplevelWatcher;
|
||||||
|
|
||||||
type BoxedError = Box<dyn Error>;
|
type BoxedError = Box<dyn Error>;
|
||||||
|
|
||||||
|
trait Watcher: Send {
|
||||||
|
fn new() -> Result<Self, BoxedError>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
fn watch(&mut self, client: &Arc<ReportClient>);
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoxedWatcher = Box<dyn Watcher>;
|
||||||
|
|
||||||
|
type WatcherConstructor = fn() -> Result<BoxedWatcher, BoxedError>;
|
||||||
|
type WatcherConstructors = [WatcherConstructor];
|
||||||
|
|
||||||
|
trait WatchersFilter {
|
||||||
|
fn filter_first_supported(&self) -> Option<BoxedWatcher>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WatchersFilter for WatcherConstructors {
|
||||||
|
fn filter_first_supported(&self) -> Option<BoxedWatcher> {
|
||||||
|
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> {
|
fn setup_logger() -> Result<(), fern::InitError> {
|
||||||
let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string());
|
let log_setting = env::var("AWATCHER_LOG").unwrap_or("info".to_string());
|
||||||
|
|
||||||
@ -47,14 +85,6 @@ fn setup_logger() -> Result<(), fern::InitError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
type WatcherConstructor = fn() -> Result<Box<dyn Watcher>, BoxedError>;
|
|
||||||
trait Watcher: Send {
|
|
||||||
fn new() -> Result<Self, BoxedError>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
fn watch(&mut self, client: &Arc<ReportClient>);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
setup_logger().unwrap();
|
setup_logger().unwrap();
|
||||||
|
|
||||||
@ -69,20 +99,8 @@ fn main() {
|
|||||||
info!("Polling period: {} seconds", client.config.poll_time_idle);
|
info!("Polling period: {} seconds", client.config.poll_time_idle);
|
||||||
|
|
||||||
let mut thread_handlers = Vec::new();
|
let mut thread_handlers = Vec::new();
|
||||||
let idle_watchers: Vec<WatcherConstructor> = vec![|| Ok(Box::new(KwinIdleWatcher::new()?))];
|
|
||||||
let window_watchers: Vec<WatcherConstructor> = vec![|| Ok(Box::new(KwinWindowWatcher::new()?))];
|
|
||||||
|
|
||||||
let filter_watcher = |watchers: Vec<WatcherConstructor>| {
|
let idle_watcher = IDLE_WATCHERS.filter_first_supported();
|
||||||
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);
|
|
||||||
if let Some(mut watcher) = idle_watcher {
|
if let Some(mut watcher) = idle_watcher {
|
||||||
let thread_client = Arc::clone(&client);
|
let thread_client = Arc::clone(&client);
|
||||||
let idle_handler = thread::spawn(move || watcher.watch(&thread_client));
|
let idle_handler = thread::spawn(move || watcher.watch(&thread_client));
|
||||||
@ -91,7 +109,7 @@ fn main() {
|
|||||||
warn!("No supported idle handler is found");
|
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 {
|
if let Some(mut watcher) = window_watcher {
|
||||||
let thread_client = Arc::clone(&client);
|
let thread_client = Arc::clone(&client);
|
||||||
let active_window_handler = thread::spawn(move || watcher.watch(&thread_client));
|
let active_window_handler = thread::spawn(move || watcher.watch(&thread_client));
|
||||||
@ -101,6 +119,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for handler in thread_handlers {
|
for handler in thread_handlers {
|
||||||
handler.join().unwrap();
|
if handler.join().is_err() {
|
||||||
|
error!("Thread failed with error");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
259
src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
259
src/protocols/wlr-foreign-toplevel-management-unstable-v1.xml
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="wlr_foreign_toplevel_management_unstable_v1">
|
||||||
|
<copyright>
|
||||||
|
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.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_manager_v1" version="2">
|
||||||
|
<description summary="list and control opened apps">
|
||||||
|
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
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="toplevel">
|
||||||
|
<description summary="a toplevel has been created">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
<arg name="toplevel" type="new_id" interface="zwlr_foreign_toplevel_handle_v1"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="stop">
|
||||||
|
<description summary="stop sending events">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="finished">
|
||||||
|
<description summary="the compositor has finished with the toplevel manager">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="zwlr_foreign_toplevel_handle_v1" version="2">
|
||||||
|
<description summary="an opened toplevel">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<event name="title">
|
||||||
|
<description summary="title change">
|
||||||
|
This event is emitted whenever the title of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="title" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="app_id">
|
||||||
|
<description summary="app-id change">
|
||||||
|
This event is emitted whenever the app-id of the toplevel changes.
|
||||||
|
</description>
|
||||||
|
<arg name="app_id" type="string"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_enter">
|
||||||
|
<description summary="toplevel entered an output">
|
||||||
|
This event is emitted whenever the toplevel becomes visible on
|
||||||
|
the given output. A toplevel may be visible on multiple outputs.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="output_leave">
|
||||||
|
<description summary="toplevel left an output">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="set_maximized">
|
||||||
|
<description summary="requests that the toplevel be maximized">
|
||||||
|
Requests that the toplevel be maximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_maximized">
|
||||||
|
<description summary="requests that the toplevel be unmaximized">
|
||||||
|
Requests that the toplevel be unmaximized. If the maximized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_minimized">
|
||||||
|
<description summary="requests that the toplevel be minimized">
|
||||||
|
Requests that the toplevel be minimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_minimized">
|
||||||
|
<description summary="requests that the toplevel be unminimized">
|
||||||
|
Requests that the toplevel be unminimized. If the minimized state actually
|
||||||
|
changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="activate">
|
||||||
|
<description summary="activate the toplevel">
|
||||||
|
Request that this toplevel be activated on the given seat.
|
||||||
|
There is no guarantee the toplevel will be actually activated.
|
||||||
|
</description>
|
||||||
|
<arg name="seat" type="object" interface="wl_seat"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="state">
|
||||||
|
<description summary="types of states on the toplevel">
|
||||||
|
The different states that a toplevel can have. These have the same meaning
|
||||||
|
as the states with the same names defined in xdg-toplevel
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<entry name="maximized" value="0" summary="the toplevel is maximized"/>
|
||||||
|
<entry name="minimized" value="1" summary="the toplevel is minimized"/>
|
||||||
|
<entry name="activated" value="2" summary="the toplevel is active"/>
|
||||||
|
<entry name="fullscreen" value="3" summary="the toplevel is fullscreen" since="2"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="state">
|
||||||
|
<description summary="the toplevel state changed">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="state" type="array"/>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<event name="done">
|
||||||
|
<description summary="all information about the toplevel has been sent">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="close">
|
||||||
|
<description summary="request that the toplevel be closed">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="set_rectangle">
|
||||||
|
<description summary="the rectangle which represents the toplevel">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
<arg name="x" type="int"/>
|
||||||
|
<arg name="y" type="int"/>
|
||||||
|
<arg name="width" type="int"/>
|
||||||
|
<arg name="height" type="int"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<enum name="error">
|
||||||
|
<entry name="invalid_rectangle" value="0"
|
||||||
|
summary="the provided rectangle is invalid"/>
|
||||||
|
</enum>
|
||||||
|
|
||||||
|
<event name="closed">
|
||||||
|
<description summary="this toplevel has been destroyed">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the zwlr_foreign_toplevel_handle_v1 object">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<!-- Version 2 additions -->
|
||||||
|
|
||||||
|
<request name="set_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be fullscreened">
|
||||||
|
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.
|
||||||
|
</description>
|
||||||
|
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="unset_fullscreen" since="2">
|
||||||
|
<description summary="request that the toplevel be unfullscreened">
|
||||||
|
Requests that the toplevel be unfullscreened. If the fullscreen state
|
||||||
|
actually changes, this will be indicated by the state event.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
@ -16,6 +16,10 @@ impl ReportClient {
|
|||||||
let host = config.host.clone();
|
let host = config.host.clone();
|
||||||
let port = config.port.to_string();
|
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 {
|
Self {
|
||||||
config,
|
config,
|
||||||
client: AwClient::new(&host, &port, "awatcher"),
|
client: AwClient::new(&host, &port, "awatcher"),
|
||||||
@ -24,7 +28,6 @@ impl ReportClient {
|
|||||||
|
|
||||||
pub fn ping(
|
pub fn ping(
|
||||||
&self,
|
&self,
|
||||||
bucket_name: &str,
|
|
||||||
is_idle: bool,
|
is_idle: bool,
|
||||||
timestamp: DateTime<Utc>,
|
timestamp: DateTime<Utc>,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
@ -44,25 +47,39 @@ impl ReportClient {
|
|||||||
|
|
||||||
let pulsetime = f64::from(self.config.idle_timeout + self.config.poll_time_idle);
|
let pulsetime = f64::from(self.config.idle_timeout + self.config.poll_time_idle);
|
||||||
self.client
|
self.client
|
||||||
.heartbeat(bucket_name, &event, pulsetime)
|
.heartbeat(&self.config.idle_bucket_name, &event, pulsetime)
|
||||||
.map_err(|_| "Failed to send heartbeat")?;
|
.map_err(|_| "Failed to send heartbeat")?;
|
||||||
|
|
||||||
Ok(())
|
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);
|
let interval_margin: f64 = f64::from(self.config.poll_time_idle + 1);
|
||||||
self.client
|
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())
|
.map_err(|_| "Failed to send heartbeat for active window".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_bucket(
|
fn create_bucket(
|
||||||
&self,
|
client: &AwClient,
|
||||||
bucket_name: &str,
|
bucket_name: &str,
|
||||||
bucket_type: &str,
|
bucket_type: &str,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
self.client
|
client
|
||||||
.create_bucket_simple(bucket_name, bucket_type)
|
.create_bucket_simple(bucket_name, bucket_type)
|
||||||
.map_err(|e| format!("Failed to create bucket {bucket_name}: {e}").into())
|
.map_err(|e| format!("Failed to create bucket {bucket_name}: {e}").into())
|
||||||
}
|
}
|
||||||
|
@ -20,3 +20,22 @@ pub mod idle {
|
|||||||
|
|
||||||
wayland_scanner::generate_client_code!("src/protocols/idle.xml");
|
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");
|
||||||
|
}
|
||||||
|
@ -7,6 +7,24 @@ use wayland_client::{
|
|||||||
};
|
};
|
||||||
use wl_bindings::idle::org_kde_kwin_idle::OrgKdeKwinIdle;
|
use wl_bindings::idle::org_kde_kwin_idle::OrgKdeKwinIdle;
|
||||||
use wl_bindings::idle::org_kde_kwin_idle_timeout::OrgKdeKwinIdleTimeout;
|
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<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use subscribe_state;
|
||||||
|
|
||||||
pub struct WlEventConnection<T> {
|
pub struct WlEventConnection<T> {
|
||||||
pub globals: GlobalList,
|
pub globals: GlobalList,
|
||||||
@ -37,6 +55,19 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_foreign_toplevel_manager(&self) -> Result<ZwlrForeignToplevelManagerV1, BoxedError>
|
||||||
|
where
|
||||||
|
T: Dispatch<ZwlrForeignToplevelManagerV1, ()>,
|
||||||
|
{
|
||||||
|
self.globals
|
||||||
|
.bind::<ZwlrForeignToplevelManagerV1, T, ()>(
|
||||||
|
&self.queue_handle,
|
||||||
|
1..=OrgKdeKwinIdle::interface().version,
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.map_err(std::convert::Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_kwin_idle(&self) -> Result<OrgKdeKwinIdle, BoxedError>
|
pub fn get_kwin_idle(&self) -> Result<OrgKdeKwinIdle, BoxedError>
|
||||||
where
|
where
|
||||||
T: Dispatch<OrgKdeKwinIdle, ()>,
|
T: Dispatch<OrgKdeKwinIdle, ()>,
|
||||||
|
175
src/wl_foreign_toplevel.rs
Normal file
175
src/wl_foreign_toplevel.rs
Normal file
@ -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<String, WindowData>,
|
||||||
|
current_window_id: Option<String>,
|
||||||
|
_last_input_time: DateTime<Utc>,
|
||||||
|
_is_idle: bool,
|
||||||
|
_is_changed: bool,
|
||||||
|
client: Arc<ReportClient>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelState {
|
||||||
|
fn new(client: Arc<ReportClient>) -> Self {
|
||||||
|
Self {
|
||||||
|
windows: HashMap::new(),
|
||||||
|
current_window_id: None,
|
||||||
|
_last_input_time: Utc::now(),
|
||||||
|
_is_idle: false,
|
||||||
|
_is_changed: false,
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch<ZwlrForeignToplevelManagerV1, ()> for ToplevelState {
|
||||||
|
fn event(
|
||||||
|
state: &mut Self,
|
||||||
|
_: &ZwlrForeignToplevelManagerV1,
|
||||||
|
event: <ZwlrForeignToplevelManagerV1 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
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<ZwlrForeignToplevelHandleV1, ()> for ToplevelState {
|
||||||
|
fn event(
|
||||||
|
toplevel_state: &mut Self,
|
||||||
|
handle: &ZwlrForeignToplevelHandleV1,
|
||||||
|
event: <ZwlrForeignToplevelHandleV1 as Proxy>::Event,
|
||||||
|
_: &(),
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
) {
|
||||||
|
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<ToplevelState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watcher for WlrForeignToplevelWatcher {
|
||||||
|
fn new() -> Result<Self, BoxedError> {
|
||||||
|
let connection: WlEventConnection<ToplevelState> = WlEventConnection::connect()?;
|
||||||
|
connection.get_foreign_toplevel_manager()?;
|
||||||
|
|
||||||
|
Ok(Self { connection })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch(&mut self, client: &Arc<ReportClient>) {
|
||||||
|
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,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ use crate::Watcher;
|
|||||||
|
|
||||||
use super::report_client::ReportClient;
|
use super::report_client::ReportClient;
|
||||||
use super::wl_bindings;
|
use super::wl_bindings;
|
||||||
use super::wl_connection::WlEventConnection;
|
use super::wl_connection::{subscribe_state, WlEventConnection};
|
||||||
use super::BoxedError;
|
use super::BoxedError;
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use std::{sync::Arc, thread, time};
|
use std::{sync::Arc, thread, time};
|
||||||
@ -22,7 +22,6 @@ struct IdleState {
|
|||||||
is_idle: bool,
|
is_idle: bool,
|
||||||
is_changed: bool,
|
is_changed: bool,
|
||||||
client: Arc<ReportClient>,
|
client: Arc<ReportClient>,
|
||||||
bucket_name: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for IdleState {
|
impl Drop for IdleState {
|
||||||
@ -33,18 +32,13 @@ impl Drop for IdleState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IdleState {
|
impl IdleState {
|
||||||
fn new(
|
fn new(idle_timeout: OrgKdeKwinIdleTimeout, client: Arc<ReportClient>) -> Self {
|
||||||
idle_timeout: OrgKdeKwinIdleTimeout,
|
|
||||||
client: Arc<ReportClient>,
|
|
||||||
bucket_name: String,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
idle_timeout,
|
idle_timeout,
|
||||||
last_input_time: Utc::now(),
|
last_input_time: Utc::now(),
|
||||||
is_idle: false,
|
is_idle: false,
|
||||||
is_changed: false,
|
is_changed: false,
|
||||||
client,
|
client,
|
||||||
bucket_name,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +55,7 @@ impl IdleState {
|
|||||||
debug!("Resumed");
|
debug!("Resumed");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_loop(&mut self, connection: &mut WlEventConnection<Self>) -> Result<(), BoxedError> {
|
fn send_ping(&mut self) -> Result<(), BoxedError> {
|
||||||
connection.event_queue.roundtrip(self).unwrap();
|
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
if !self.is_idle {
|
if !self.is_idle {
|
||||||
self.last_input_time = now;
|
self.last_input_time = now;
|
||||||
@ -71,14 +64,9 @@ impl IdleState {
|
|||||||
if self.is_changed {
|
if self.is_changed {
|
||||||
let result = if self.is_idle {
|
let result = if self.is_idle {
|
||||||
debug!("Reporting as changed to idle");
|
debug!("Reporting as changed to idle");
|
||||||
|
self.client
|
||||||
|
.ping(false, self.last_input_time, Duration::zero())?;
|
||||||
self.client.ping(
|
self.client.ping(
|
||||||
&self.bucket_name,
|
|
||||||
false,
|
|
||||||
self.last_input_time,
|
|
||||||
Duration::zero(),
|
|
||||||
)?;
|
|
||||||
self.client.ping(
|
|
||||||
&self.bucket_name,
|
|
||||||
true,
|
true,
|
||||||
self.last_input_time + Duration::milliseconds(1),
|
self.last_input_time + Duration::milliseconds(1),
|
||||||
Duration::zero(),
|
Duration::zero(),
|
||||||
@ -86,14 +74,9 @@ impl IdleState {
|
|||||||
} else {
|
} else {
|
||||||
debug!("Reporting as no longer idle");
|
debug!("Reporting as no longer idle");
|
||||||
|
|
||||||
|
self.client
|
||||||
|
.ping(true, self.last_input_time, Duration::zero())?;
|
||||||
self.client.ping(
|
self.client.ping(
|
||||||
&self.bucket_name,
|
|
||||||
true,
|
|
||||||
self.last_input_time,
|
|
||||||
Duration::zero(),
|
|
||||||
)?;
|
|
||||||
self.client.ping(
|
|
||||||
&self.bucket_name,
|
|
||||||
false,
|
false,
|
||||||
self.last_input_time + Duration::milliseconds(1),
|
self.last_input_time + Duration::milliseconds(1),
|
||||||
Duration::zero(),
|
Duration::zero(),
|
||||||
@ -103,44 +86,20 @@ impl IdleState {
|
|||||||
result
|
result
|
||||||
} else if self.is_idle {
|
} else if self.is_idle {
|
||||||
trace!("Reporting as idle");
|
trace!("Reporting as idle");
|
||||||
self.client.ping(
|
self.client
|
||||||
&self.bucket_name,
|
.ping(true, self.last_input_time, now - self.last_input_time)
|
||||||
true,
|
|
||||||
self.last_input_time,
|
|
||||||
now - self.last_input_time,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
trace!("Reporting as not idle");
|
trace!("Reporting as not idle");
|
||||||
self.client.ping(
|
self.client
|
||||||
&self.bucket_name,
|
.ping(false, self.last_input_time, Duration::zero())
|
||||||
false,
|
|
||||||
self.last_input_time,
|
|
||||||
Duration::zero(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! subscribe_state {
|
subscribe_state!(wl_registry::WlRegistry, GlobalListContents, IdleState);
|
||||||
($struct_name:ty, $data_name:ty) => {
|
subscribe_state!(wl_registry::WlRegistry, (), IdleState);
|
||||||
impl Dispatch<$struct_name, $data_name> for IdleState {
|
subscribe_state!(WlSeat, (), IdleState);
|
||||||
fn event(
|
subscribe_state!(OrgKdeKwinIdle, (), IdleState);
|
||||||
_: &mut Self,
|
|
||||||
_: &$struct_name,
|
|
||||||
_: <$struct_name as Proxy>::Event,
|
|
||||||
_: &$data_name,
|
|
||||||
_: &Connection,
|
|
||||||
_: &QueueHandle<Self>,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe_state!(wl_registry::WlRegistry, ());
|
|
||||||
subscribe_state!(WlSeat, ());
|
|
||||||
subscribe_state!(OrgKdeKwinIdle, ());
|
|
||||||
subscribe_state!(wl_registry::WlRegistry, GlobalListContents);
|
|
||||||
|
|
||||||
impl Dispatch<OrgKdeKwinIdleTimeout, ()> for IdleState {
|
impl Dispatch<OrgKdeKwinIdleTimeout, ()> for IdleState {
|
||||||
fn event(
|
fn event(
|
||||||
@ -172,19 +131,11 @@ impl Watcher for KwinIdleWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, client: &Arc<ReportClient>) {
|
fn watch(&mut self, client: &Arc<ReportClient>) {
|
||||||
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(
|
let mut idle_state = IdleState::new(
|
||||||
self.connection
|
self.connection
|
||||||
.get_kwin_idle_timeout(client.config.idle_timeout * 1000)
|
.get_kwin_idle_timeout(client.config.idle_timeout * 1000)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Arc::clone(client),
|
Arc::clone(client),
|
||||||
bucket_name,
|
|
||||||
);
|
);
|
||||||
self.connection
|
self.connection
|
||||||
.event_queue
|
.event_queue
|
||||||
@ -193,7 +144,9 @@ impl Watcher for KwinIdleWatcher {
|
|||||||
|
|
||||||
info!("Starting idle watcher");
|
info!("Starting idle watcher");
|
||||||
loop {
|
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}");
|
error!("Error on idle iteration {e}");
|
||||||
}
|
}
|
||||||
thread::sleep(time::Duration::from_secs(u64::from(
|
thread::sleep(time::Duration::from_secs(u64::from(
|
||||||
|
@ -7,9 +7,6 @@ use crate::Watcher;
|
|||||||
*/
|
*/
|
||||||
use super::report_client::ReportClient;
|
use super::report_client::ReportClient;
|
||||||
use super::BoxedError;
|
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::env::temp_dir;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{mpsc::channel, Arc, Mutex};
|
use std::sync::{mpsc::channel, Arc, Mutex};
|
||||||
@ -110,32 +107,14 @@ impl Drop for KWinScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_heartbeat(
|
fn send_active_window(
|
||||||
client: &ReportClient,
|
client: &ReportClient,
|
||||||
bucket_name: &str,
|
|
||||||
active_window: &Arc<Mutex<ActiveWindow>>,
|
active_window: &Arc<Mutex<ActiveWindow>>,
|
||||||
) -> Result<(), BoxedError> {
|
) -> Result<(), BoxedError> {
|
||||||
let event = {
|
|
||||||
let active_window = active_window.lock().map_err(|e| format!("{e}"))?;
|
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,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
client
|
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())
|
.map_err(|_| "Failed to send heartbeat for active window".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,9 +160,6 @@ impl Watcher for KwinWindowWatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&mut self, client: &Arc<ReportClient>) {
|
fn watch(&mut self, client: &Arc<ReportClient>) {
|
||||||
let hostname = gethostname::gethostname().into_string().unwrap();
|
|
||||||
let bucket_name = format!("aw-watcher-window_{hostname}");
|
|
||||||
|
|
||||||
self.kwin_script.load().unwrap();
|
self.kwin_script.load().unwrap();
|
||||||
|
|
||||||
let active_window = Arc::new(Mutex::new(ActiveWindow {
|
let active_window = Arc::new(Mutex::new(ActiveWindow {
|
||||||
@ -217,7 +193,7 @@ impl Watcher for KwinWindowWatcher {
|
|||||||
|
|
||||||
info!("Starting active window watcher");
|
info!("Starting active window watcher");
|
||||||
loop {
|
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}");
|
error!("Error on sending active window heartbeat: {error}");
|
||||||
}
|
}
|
||||||
thread::sleep(time::Duration::from_secs(u64::from(
|
thread::sleep(time::Duration::from_secs(u64::from(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user