mirror of
https://github.com/2e3s/awatcher.git
synced 2025-06-06 11:35:46 +00:00
Add ext-idle-notify-v1 support
This commit is contained in:
parent
70001c3523
commit
06b30f4fc0
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -447,7 +447,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "awatcher"
|
||||
version = "0.2.2"
|
||||
version = "0.2.4-alpha1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aw-datastore",
|
||||
@ -3821,7 +3821,7 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||
|
||||
[[package]]
|
||||
name = "watchers"
|
||||
version = "0.2.2"
|
||||
version = "0.2.4-alpha1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
|
@ -18,7 +18,7 @@ image = { version = "0.24.6" }
|
||||
members = ["watchers"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.2"
|
||||
version = "0.2.4-alpha1"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.75"
|
||||
|
@ -7,6 +7,7 @@ mod idle;
|
||||
mod kwin_window;
|
||||
mod wl_bindings;
|
||||
mod wl_connection;
|
||||
mod wl_ext_idle_notify;
|
||||
mod wl_foreign_toplevel;
|
||||
mod wl_kwin_idle;
|
||||
mod x11_connection;
|
||||
@ -74,6 +75,7 @@ async fn filter_first_supported(
|
||||
) -> Option<Box<dyn Watcher>> {
|
||||
match watcher_type {
|
||||
WatcherType::Idle => {
|
||||
watch!(client, watcher_type, wl_ext_idle_notify::IdleWatcher);
|
||||
watch!(client, watcher_type, wl_kwin_idle::IdleWatcher);
|
||||
watch!(client, watcher_type, x11_screensaver_idle::IdleWatcher);
|
||||
#[cfg(feature = "gnome")]
|
||||
|
102
watchers/src/watchers/wl-protocols/ext-idle-notify-v1.xml
Normal file
102
watchers/src/watchers/wl-protocols/ext-idle-notify-v1.xml
Normal file
@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="ext_idle_notify_v1">
|
||||
<copyright>
|
||||
Copyright © 2015 Martin Gräßlin
|
||||
Copyright © 2022 Simon Ser
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next
|
||||
paragraph) shall be included in all copies or substantial portions of the
|
||||
Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="ext_idle_notifier_v1" version="1">
|
||||
<description summary="idle notification manager">
|
||||
This interface allows clients to monitor user idle status.
|
||||
|
||||
After binding to this global, clients can create ext_idle_notification_v1
|
||||
objects to get notified when the user is idle for a given amount of time.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the manager">
|
||||
Destroy the manager object. All objects created via this interface
|
||||
remain valid.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="get_idle_notification">
|
||||
<description summary="create a notification object">
|
||||
Create a new idle notification object.
|
||||
|
||||
The notification object has a minimum timeout duration and is tied to a
|
||||
seat. The client will be notified if the seat is inactive for at least
|
||||
the provided timeout. See ext_idle_notification_v1 for more details.
|
||||
|
||||
A zero timeout is valid and means the client wants to be notified as
|
||||
soon as possible when the seat is inactive.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="ext_idle_notification_v1"/>
|
||||
<arg name="timeout" type="uint" summary="minimum idle timeout in msec"/>
|
||||
<arg name="seat" type="object" interface="wl_seat"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="ext_idle_notification_v1" version="1">
|
||||
<description summary="idle notification">
|
||||
This interface is used by the compositor to send idle notification events
|
||||
to clients.
|
||||
|
||||
Initially the notification object is not idle. The notification object
|
||||
becomes idle when no user activity has happened for at least the timeout
|
||||
duration, starting from the creation of the notification object. User
|
||||
activity may include input events or a presence sensor, but is
|
||||
compositor-specific. If an idle inhibitor is active (e.g. another client
|
||||
has created a zwp_idle_inhibitor_v1 on a visible surface), the compositor
|
||||
must not make the notification object idle.
|
||||
|
||||
When the notification object becomes idle, an idled event is sent. When
|
||||
user activity starts again, the notification object stops being idle,
|
||||
a resumed event is sent and the timeout is restarted.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the notification object">
|
||||
Destroy the notification object.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="idled">
|
||||
<description summary="notification object is idle">
|
||||
This event is sent when the notification object becomes idle.
|
||||
|
||||
It's a compositor protocol error to send this event twice without a
|
||||
resumed event in-between.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<event name="resumed">
|
||||
<description summary="notification object is no longer idle">
|
||||
This event is sent when the notification object stops being idle.
|
||||
|
||||
It's a compositor protocol error to send this event twice without an
|
||||
idled event in-between. It's a compositor protocol error to send this
|
||||
event prior to any idled event.
|
||||
</description>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
@ -21,6 +21,25 @@ pub mod idle {
|
||||
wayland_scanner::generate_client_code!("src/watchers/wl-protocols/idle.xml");
|
||||
}
|
||||
|
||||
pub mod ext_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)]
|
||||
#![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/watchers/wl-protocols/ext-idle-notify-v1.xml");
|
||||
}
|
||||
use self::__interfaces::*;
|
||||
|
||||
wayland_scanner::generate_client_code!("src/watchers/wl-protocols/ext-idle-notify-v1.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)]
|
||||
|
@ -5,6 +5,9 @@ use wayland_client::{
|
||||
protocol::{wl_registry, wl_seat::WlSeat},
|
||||
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
|
||||
};
|
||||
|
||||
use wl_bindings::ext_idle::ext_idle_notification_v1::ExtIdleNotificationV1;
|
||||
use wl_bindings::ext_idle::ext_idle_notifier_v1::ExtIdleNotifierV1;
|
||||
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;
|
||||
@ -81,6 +84,33 @@ where
|
||||
.map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
pub fn get_ext_idle(&self) -> anyhow::Result<ExtIdleNotifierV1>
|
||||
where
|
||||
T: Dispatch<ExtIdleNotifierV1, ()>,
|
||||
{
|
||||
self.globals
|
||||
.bind::<ExtIdleNotifierV1, T, ()>(
|
||||
&self.queue_handle,
|
||||
1..=ExtIdleNotifierV1::interface().version,
|
||||
(),
|
||||
)
|
||||
.map_err(std::convert::Into::into)
|
||||
}
|
||||
|
||||
pub fn get_ext_idle_notification(&self, timeout: u32) -> anyhow::Result<ExtIdleNotificationV1>
|
||||
where
|
||||
T: Dispatch<ExtIdleNotifierV1, ()>
|
||||
+ Dispatch<WlSeat, ()>
|
||||
+ Dispatch<ExtIdleNotificationV1, ()>,
|
||||
{
|
||||
let seat: WlSeat =
|
||||
self.globals
|
||||
.bind(&self.queue_handle, 1..=WlSeat::interface().version, ())?;
|
||||
|
||||
let idle = self.get_ext_idle()?;
|
||||
Ok(idle.get_idle_notification(timeout, &seat, &self.queue_handle, ()))
|
||||
}
|
||||
|
||||
pub fn get_kwin_idle_timeout(&self, timeout: u32) -> anyhow::Result<OrgKdeKwinIdleTimeout>
|
||||
where
|
||||
T: Dispatch<OrgKdeKwinIdle, ()>
|
||||
|
159
watchers/src/watchers/wl_ext_idle_notify.rs
Normal file
159
watchers/src/watchers/wl_ext_idle_notify.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use super::wl_bindings;
|
||||
use super::wl_connection::{subscribe_state, WlEventConnection};
|
||||
use super::Watcher;
|
||||
use crate::report_client::ReportClient;
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use std::sync::Arc;
|
||||
use wayland_client::{
|
||||
globals::GlobalListContents,
|
||||
protocol::{wl_registry, wl_seat::WlSeat},
|
||||
Connection, Dispatch, Proxy, QueueHandle,
|
||||
};
|
||||
use wl_bindings::ext_idle::ext_idle_notification_v1::Event as ExtIdleNotificationV1Event;
|
||||
use wl_bindings::ext_idle::ext_idle_notification_v1::ExtIdleNotificationV1;
|
||||
use wl_bindings::ext_idle::ext_idle_notifier_v1::ExtIdleNotifierV1;
|
||||
|
||||
struct IdleState {
|
||||
idle_notification: ExtIdleNotificationV1,
|
||||
last_input_time: DateTime<Utc>,
|
||||
is_idle: bool,
|
||||
is_changed: bool,
|
||||
}
|
||||
|
||||
impl Drop for IdleState {
|
||||
fn drop(&mut self) {
|
||||
info!("Releasing idle notification");
|
||||
self.idle_notification.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl IdleState {
|
||||
fn new(idle_notification: ExtIdleNotificationV1) -> Self {
|
||||
Self {
|
||||
idle_notification,
|
||||
last_input_time: Utc::now(),
|
||||
is_idle: false,
|
||||
is_changed: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn idle(&mut self) {
|
||||
self.is_idle = true;
|
||||
self.is_changed = true;
|
||||
debug!("Idle");
|
||||
}
|
||||
|
||||
fn resume(&mut self) {
|
||||
self.is_idle = false;
|
||||
self.last_input_time = Utc::now();
|
||||
self.is_changed = true;
|
||||
debug!("Resumed");
|
||||
}
|
||||
|
||||
async fn send_ping(&mut self, client: &Arc<ReportClient>) -> anyhow::Result<()> {
|
||||
let now = Utc::now();
|
||||
if !self.is_idle {
|
||||
self.last_input_time = now;
|
||||
}
|
||||
|
||||
if self.is_changed {
|
||||
let result = if self.is_idle {
|
||||
debug!("Reporting as changed to idle");
|
||||
client
|
||||
.ping(false, self.last_input_time, Duration::zero())
|
||||
.await?;
|
||||
client
|
||||
.ping(
|
||||
true,
|
||||
self.last_input_time + Duration::milliseconds(1),
|
||||
Duration::zero(),
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
debug!("Reporting as no longer idle");
|
||||
|
||||
client
|
||||
.ping(true, self.last_input_time, Duration::zero())
|
||||
.await?;
|
||||
client
|
||||
.ping(
|
||||
false,
|
||||
self.last_input_time + Duration::milliseconds(1),
|
||||
Duration::zero(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
self.is_changed = false;
|
||||
result
|
||||
} else if self.is_idle {
|
||||
trace!("Reporting as idle");
|
||||
client
|
||||
.ping(true, self.last_input_time, now - self.last_input_time)
|
||||
.await
|
||||
} else {
|
||||
trace!("Reporting as not idle");
|
||||
client
|
||||
.ping(false, self.last_input_time, Duration::zero())
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscribe_state!(wl_registry::WlRegistry, GlobalListContents, IdleState);
|
||||
subscribe_state!(wl_registry::WlRegistry, (), IdleState);
|
||||
subscribe_state!(WlSeat, (), IdleState);
|
||||
subscribe_state!(ExtIdleNotifierV1, (), IdleState);
|
||||
|
||||
impl Dispatch<ExtIdleNotificationV1, ()> for IdleState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ExtIdleNotificationV1,
|
||||
event: <ExtIdleNotificationV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let ExtIdleNotificationV1Event::Idled = event {
|
||||
state.idle();
|
||||
} else if let ExtIdleNotificationV1Event::Resumed = event {
|
||||
state.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IdleWatcher {
|
||||
connection: WlEventConnection<IdleState>,
|
||||
idle_state: IdleState,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Watcher for IdleWatcher {
|
||||
async fn new(client: &Arc<ReportClient>) -> anyhow::Result<Self> {
|
||||
let mut connection: WlEventConnection<IdleState> = WlEventConnection::connect()?;
|
||||
connection.get_ext_idle()?;
|
||||
|
||||
let timeout = u32::try_from(client.config.idle_timeout.as_secs() * 1000);
|
||||
let mut idle_state = IdleState::new(
|
||||
connection
|
||||
.get_ext_idle_notification(timeout.unwrap())
|
||||
.unwrap(),
|
||||
);
|
||||
connection.event_queue.roundtrip(&mut idle_state).unwrap();
|
||||
|
||||
Ok(Self {
|
||||
connection,
|
||||
idle_state,
|
||||
})
|
||||
}
|
||||
|
||||
async fn run_iteration(&mut self, client: &Arc<ReportClient>) -> anyhow::Result<()> {
|
||||
self.connection
|
||||
.event_queue
|
||||
.roundtrip(&mut self.idle_state)
|
||||
.map_err(|e| anyhow!("Event queue is not processed: {e}"))?;
|
||||
|
||||
self.idle_state.send_ping(client).await
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user