mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-05 19:15:33 +00:00
Implement idle watcher
This commit is contained in:
commit
24254d5a51
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
1511
Cargo.lock
generated
Normal file
1511
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "aw-watcher"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
aw-client-rust = { git = "https://github.com/ActivityWatch/aw-server-rust" }
|
||||
gethostname = "0.4.1"
|
||||
wayland-client = "0.30.1"
|
||||
wayland-scanner = "0.30"
|
||||
wayland-backend = "0.1"
|
||||
chrono = "0.4.24"
|
||||
serde_json = "1.0.95"
|
17
src/config.rs
Normal file
17
src/config.rs
Normal file
@ -0,0 +1,17 @@
|
||||
pub struct Config {
|
||||
pub port: u32,
|
||||
pub host: String,
|
||||
pub timeout_ms: u32,
|
||||
pub poll_time: u32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: 5600,
|
||||
host: String::from("localhost"),
|
||||
timeout_ms: 3000,
|
||||
poll_time: 5,
|
||||
}
|
||||
}
|
||||
}
|
164
src/kwin_idle.rs
Normal file
164
src/kwin_idle.rs
Normal file
@ -0,0 +1,164 @@
|
||||
use super::config::Config;
|
||||
use super::wl_bindings;
|
||||
use aw_client_rust::{AwClient, Event as AwEvent};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use serde_json::{Map, Value};
|
||||
use std::{thread, time};
|
||||
use wayland_client::Proxy;
|
||||
use wayland_client::{
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
protocol::wl_registry,
|
||||
protocol::wl_seat::WlSeat,
|
||||
Connection, Dispatch, QueueHandle,
|
||||
};
|
||||
use wl_bindings::idle::org_kde_kwin_idle::OrgKdeKwinIdle;
|
||||
use wl_bindings::idle::org_kde_kwin_idle_timeout::Event as OrgKdeKwinIdleTimeoutEvent;
|
||||
use wl_bindings::idle::org_kde_kwin_idle_timeout::OrgKdeKwinIdleTimeout;
|
||||
|
||||
struct IdleState {
|
||||
idle_timeout: OrgKdeKwinIdleTimeout,
|
||||
start_time: DateTime<Utc>,
|
||||
is_idle: bool,
|
||||
}
|
||||
|
||||
impl Drop for IdleState {
|
||||
fn drop(&mut self) {
|
||||
println!("Releasing idle timeout");
|
||||
self.idle_timeout.release();
|
||||
}
|
||||
}
|
||||
|
||||
impl IdleState {
|
||||
fn idle(&mut self) {
|
||||
self.is_idle = true;
|
||||
self.start_time = Utc::now();
|
||||
}
|
||||
|
||||
fn resume(&mut self) {
|
||||
self.is_idle = false;
|
||||
self.start_time = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
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<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
subscribe_state!(wl_registry::WlRegistry, ());
|
||||
subscribe_state!(WlSeat, ());
|
||||
subscribe_state!(OrgKdeKwinIdle, ());
|
||||
subscribe_state!(wl_registry::WlRegistry, GlobalListContents);
|
||||
|
||||
impl Dispatch<OrgKdeKwinIdleTimeout, ()> for IdleState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &OrgKdeKwinIdleTimeout,
|
||||
event: <OrgKdeKwinIdleTimeout as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let OrgKdeKwinIdleTimeoutEvent::Idle = event {
|
||||
state.idle();
|
||||
println!("Idle");
|
||||
}
|
||||
if let OrgKdeKwinIdleTimeoutEvent::Resumed = event {
|
||||
state.resume();
|
||||
println!("Resumed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_heartbeat(client: &AwClient, state: &IdleState, bucket_name: &String, config: &Config) {
|
||||
let now = Utc::now();
|
||||
|
||||
let timestamp = match state.is_idle {
|
||||
true => now,
|
||||
false => {
|
||||
let last_guaranteed_activity = now - Duration::milliseconds(config.timeout_ms as i64);
|
||||
match last_guaranteed_activity > state.start_time {
|
||||
true => last_guaranteed_activity,
|
||||
false => state.start_time,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut data = Map::new();
|
||||
let json_afk_state = match state.is_idle {
|
||||
true => Value::String("afk".to_string()),
|
||||
false => Value::String("not-afk".to_string()),
|
||||
};
|
||||
data.insert("status".to_string(), json_afk_state);
|
||||
|
||||
let event = AwEvent {
|
||||
id: None,
|
||||
timestamp,
|
||||
duration: Duration::zero(),
|
||||
data,
|
||||
};
|
||||
|
||||
let interval_margin: f64 = (config.poll_time + 1) as f64 / 1.0;
|
||||
if client
|
||||
.heartbeat(&bucket_name, &event, interval_margin)
|
||||
.is_err()
|
||||
{
|
||||
println!("Failed to send heartbeat");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(client: &AwClient, conf: &Config) {
|
||||
let hostname = gethostname::gethostname().into_string().unwrap();
|
||||
let bucket_name = format!("aw-watcher-afk_{}", hostname);
|
||||
|
||||
client
|
||||
.create_bucket_simple(&bucket_name, "afkstatus")
|
||||
.expect("Failed to create afk bucket");
|
||||
|
||||
println!("Starting activity watcher");
|
||||
|
||||
let conn = Connection::connect_to_env().expect("Unable to connect to Wayland compositor");
|
||||
let display = conn.display();
|
||||
let (globals, mut event_queue) = registry_queue_init::<IdleState>(&conn).unwrap();
|
||||
|
||||
let qh = event_queue.handle();
|
||||
|
||||
let _registry = display.get_registry(&qh, ());
|
||||
|
||||
let seat: WlSeat = globals
|
||||
.bind(&event_queue.handle(), 1..=WlSeat::interface().version, ())
|
||||
.unwrap();
|
||||
let idle: OrgKdeKwinIdle = globals
|
||||
.bind(
|
||||
&event_queue.handle(),
|
||||
1..=OrgKdeKwinIdle::interface().version,
|
||||
(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let idle_timeout = idle.get_idle_timeout(&seat, 3000, &qh, ());
|
||||
|
||||
let mut app_state = IdleState {
|
||||
idle_timeout,
|
||||
is_idle: false,
|
||||
start_time: Utc::now(),
|
||||
};
|
||||
event_queue.roundtrip(&mut app_state).unwrap();
|
||||
|
||||
loop {
|
||||
event_queue.blocking_dispatch(&mut app_state).unwrap();
|
||||
send_heartbeat(client, &app_state, &bucket_name, conf);
|
||||
thread::sleep(time::Duration::from_secs(conf.poll_time as u64));
|
||||
}
|
||||
}
|
21
src/main.rs
Normal file
21
src/main.rs
Normal file
@ -0,0 +1,21 @@
|
||||
// extern crate wayland_client;
|
||||
|
||||
mod config;
|
||||
mod kwin_idle;
|
||||
mod wl_bindings;
|
||||
|
||||
use aw_client_rust::AwClient;
|
||||
use config::Config;
|
||||
use kwin_idle::run as run_kwin_idle;
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
let conf = Config::default();
|
||||
let client = AwClient::new(&conf.host, &conf.port.to_string(), "aw-watcher");
|
||||
|
||||
let idle_handler = thread::spawn(move || {
|
||||
run_kwin_idle(&client, &conf);
|
||||
});
|
||||
|
||||
idle_handler.join().expect("Error in the idle processing");
|
||||
}
|
49
src/protocols/idle.xml
Normal file
49
src/protocols/idle.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="idle">
|
||||
<copyright><![CDATA[
|
||||
Copyright (C) 2015 Martin Gräßlin
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
]]></copyright>
|
||||
<interface name="org_kde_kwin_idle" version="1">
|
||||
<description summary="User idle time manager">
|
||||
This interface allows to monitor user idle time on a given seat. The interface
|
||||
allows to register timers which trigger after no user activity was registered
|
||||
on the seat for a given interval. It notifies when user activity resumes.
|
||||
|
||||
This is useful for applications wanting to perform actions when the user is not
|
||||
interacting with the system, e.g. chat applications setting the user as away, power
|
||||
management features to dim screen, etc..
|
||||
</description>
|
||||
<request name="get_idle_timeout">
|
||||
<arg name="id" type="new_id" interface="org_kde_kwin_idle_timeout"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
<arg name="timeout" type="uint" summary="The idle timeout in msec"/>
|
||||
</request>
|
||||
</interface>
|
||||
<interface name="org_kde_kwin_idle_timeout" version="1">
|
||||
<request name="release" type="destructor">
|
||||
<description summary="release the timeout object"/>
|
||||
</request>
|
||||
<request name="simulate_user_activity">
|
||||
<description summary="Simulates user activity for this timeout, behaves just like real user activity on the seat"/>
|
||||
</request>
|
||||
<event name="idle">
|
||||
<description summary="Triggered when there has not been any user activity in the requested idle time interval"/>
|
||||
</event>
|
||||
<event name="resumed">
|
||||
<description summary="Triggered on the first user activity after an idle event"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
21
src/wl_bindings.rs
Normal file
21
src/wl_bindings.rs
Normal file
@ -0,0 +1,21 @@
|
||||
#![forbid(improper_ctypes, unsafe_op_in_unsafe_fn)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
pub mod idle {
|
||||
#![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)]
|
||||
|
||||
//! 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/idle.xml");
|
||||
}
|
||||
use self::__interfaces::*;
|
||||
|
||||
wayland_scanner::generate_client_code!("src/protocols/idle.xml");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user