Skip to content

Commit

Permalink
Add ext-idle-notify-v1 support
Browse files Browse the repository at this point in the history
  • Loading branch information
2e3s committed Nov 30, 2023
1 parent 70001c3 commit 06b30f4
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 3 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions watchers/src/watchers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")]
Expand Down
102 changes: 102 additions & 0 deletions watchers/src/watchers/wl-protocols/ext-idle-notify-v1.xml
Original file line number Diff line number Diff line change
@@ -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>
19 changes: 19 additions & 0 deletions watchers/src/watchers/wl_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
30 changes: 30 additions & 0 deletions watchers/src/watchers/wl_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, ()>
Expand Down
159 changes: 159 additions & 0 deletions watchers/src/watchers/wl_ext_idle_notify.rs
Original file line number Diff line number Diff line change
@@ -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
}
}

0 comments on commit 06b30f4

Please sign in to comment.