🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
//! This interface allows clients to monitor user idle status.
//!
//! ```
//! # extern crate wayland_server;
//! # #[macro_use] extern crate smithay;
//! use smithay::delegate_idle_notify;
//! use smithay::wayland::idle_notify::{IdleNotifierState, IdleNotifierHandler};
//! # use smithay::input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus};
//! # use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
//!
//! struct State { idle_notifier: IdleNotifierState<Self> }
//! # let mut event_loop = smithay::reexports::calloop::EventLoop::<State>::try_new().unwrap();
//! # let mut display = wayland_server::Display::<State>::new().unwrap();
//! // Create the idle_notifier state
//! let idle_notifier = IdleNotifierState::<State>::new(
//! &display.handle(),
//! event_loop.handle(),
//! );
//!
//! let state = State { idle_notifier };
//!
//! // Implement the necessary trait
//! # impl SeatHandler for State {
//! # type KeyboardFocus = WlSurface;
//! # type PointerFocus = WlSurface;
//! # type TouchFocus = WlSurface;
//! # fn seat_state(&mut self) -> &mut SeatState<Self> { unimplemented!() }
//! # fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) { unimplemented!() }
//! # fn cursor_image(&mut self, seat: &Seat<Self>, image: CursorImageStatus) { unimplemented!() }
//! # }
//! impl IdleNotifierHandler for State {
//! fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self> {
//! &mut self.idle_notifier
//! }
//! }
//! delegate_idle_notify!(State);
//!
//! // On input you should notify the idle_notifier
//! // state.idle_notifier.notify_activity(&seat);
//! ```
use std::{
collections::HashMap,
sync::{
atomic::{self, AtomicBool},
Mutex,
},
time::Duration,
};
use calloop::{timer::TimeoutAction, LoopHandle, RegistrationToken};
use wayland_protocols::ext::idle_notify::v1::server::{
ext_idle_notification_v1::{self, ExtIdleNotificationV1},
ext_idle_notifier_v1::{self, ExtIdleNotifierV1},
};
use wayland_server::{
backend::{ClientId, GlobalId},
protocol::wl_seat::WlSeat,
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
};
use crate::input::{Seat, SeatHandler};
/// Handler trait for ext-idle-notify
pub trait IdleNotifierHandler: Sized {
/// [`IdleNotifierState`] getter
fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self>;
}
/// User data of the [`ExtIdleNotificationV1`] resource
#[derive(Debug)]
pub struct IdleNotificationUserData {
seat: WlSeat,
is_idle: AtomicBool,
timeout: Duration,
timer_token: Mutex<Option<RegistrationToken>>,
/// If listener was created with `get_input_idle_notification`
ignore_inhibitor: bool,
}
impl IdleNotificationUserData {
fn take_timer_token(&self) -> Option<RegistrationToken> {
self.timer_token.lock().unwrap().take()
}
fn set_timer_token(&self, idle: Option<RegistrationToken>) {
*self.timer_token.lock().unwrap() = idle;
}
fn set_idle(&self, idle: bool) {
self.is_idle.store(idle, atomic::Ordering::Release);
}
fn is_idle(&self) -> bool {
self.is_idle.load(atomic::Ordering::Acquire)
}
}
/// State of ext-idle-notify module
#[derive(Debug)]
pub struct IdleNotifierState<D> {
global: GlobalId,
notifications: HashMap<WlSeat, Vec<ExtIdleNotificationV1>>,
loop_handle: LoopHandle<'static, D>,
is_inhibited: bool,
}
impl<D: IdleNotifierHandler> IdleNotifierState<D> {
/// Create new [`ExtIdleNotifierV1`] global.
pub fn new(display: &DisplayHandle, loop_handle: LoopHandle<'static, D>) -> Self
where
D: GlobalDispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotificationV1, IdleNotificationUserData>,
D: IdleNotifierHandler,
D: 'static,
{
let global = display.create_global::<D, ExtIdleNotifierV1, _>(2, ());
Self {
global,
notifications: HashMap::new(),
loop_handle,
is_inhibited: false,
}
}
/// Inhibit entering idle state, eg. by the idle-inhibit protocol
pub fn set_is_inhibited(&mut self, is_inhibited: bool) {
if self.is_inhibited == is_inhibited {
return;
}
self.is_inhibited = is_inhibited;
for notification in self.notifications() {
let data = notification.data::<IdleNotificationUserData>().unwrap();
if data.ignore_inhibitor {
continue;
}
if is_inhibited {
if data.is_idle() {
notification.resumed();
data.set_idle(false);
}
if let Some(token) = data.take_timer_token() {
self.loop_handle.remove(token);
}
} else {
self.reinsert_timer(notification);
}
}
}
/// Is idle state inhibited, eg. by the idle-inhibit protocol
pub fn is_inhibited(&mut self) -> bool {
self.is_inhibited
}
/// Should be called whenever activity occurs on a seat, eg. mouse/keyboard input.
///
/// You may want to use [`Self::notify_activity`] instead which accepts a [`Seat`].
pub fn notify_activity_for_wl_seat(&mut self, seat: &WlSeat) {
let Some(notifications) = self.notifications.get(seat) else {
return;
};
for notification in notifications {
let data = notification.data::<IdleNotificationUserData>().unwrap();
if data.is_idle() {
notification.resumed();
data.set_idle(false);
}
self.reinsert_timer(notification);
}
}
/// Returns the [`ExtIdleNotifierV1`] global.
pub fn global(&self) -> GlobalId {
self.global.clone()
}
fn notifications(&self) -> impl Iterator<Item = &ExtIdleNotificationV1> {
self.notifications.values().flatten()
}
fn reinsert_timer(&self, notification: &ExtIdleNotificationV1) {
let data = notification.data::<IdleNotificationUserData>().unwrap();
if let Some(token) = data.take_timer_token() {
self.loop_handle.remove(token);
}
if !data.ignore_inhibitor && self.is_inhibited {
return;
}
let token = self
.loop_handle
.insert_source(calloop::timer::Timer::from_duration(data.timeout), {
let idle_notification = notification.clone();
move |_, _, state| {
let data = idle_notification.data::<IdleNotificationUserData>().unwrap();
let is_inhibited = !data.ignore_inhibitor && state.idle_notifier_state().is_inhibited;
let is_idle_already = data.is_idle();
if !is_inhibited && !is_idle_already {
idle_notification.idled();
data.set_idle(true);
}
data.set_timer_token(None);
TimeoutAction::Drop
}
});
data.set_timer_token(token.ok());
}
}
impl<D: IdleNotifierHandler + SeatHandler> IdleNotifierState<D> {
/// Should be called whenever activity occurs on a seat, eg. mouse/keyboard input.
pub fn notify_activity(&mut self, seat: &Seat<D>) {
for seat in &seat.arc.inner.lock().unwrap().known_seats {
if let Ok(seat) = seat.upgrade() {
self.notify_activity_for_wl_seat(&seat);
}
}
}
}
impl<D> GlobalDispatch<ExtIdleNotifierV1, (), D> for IdleNotifierState<D>
where
D: GlobalDispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotificationV1, IdleNotificationUserData>,
D: IdleNotifierHandler,
D: 'static,
{
fn bind(
_state: &mut D,
_handle: &DisplayHandle,
_client: &Client,
resource: New<ExtIdleNotifierV1>,
_global_data: &(),
data_init: &mut wayland_server::DataInit<'_, D>,
) {
data_init.init(resource, ());
}
}
impl<D> Dispatch<ExtIdleNotifierV1, (), D> for IdleNotifierState<D>
where
D: GlobalDispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotifierV1, ()>,
D: Dispatch<ExtIdleNotificationV1, IdleNotificationUserData>,
D: IdleNotifierHandler,
D: 'static,
{
fn request(
state: &mut D,
_client: &Client,
_resource: &ExtIdleNotifierV1,
request: ext_idle_notifier_v1::Request,
_data: &(),
_dhandle: &DisplayHandle,
data_init: &mut DataInit<'_, D>,
) {
match request {
ext_idle_notifier_v1::Request::GetIdleNotification { id, timeout, seat } => {
let timeout = Duration::from_millis(timeout as u64);
let idle_notifier_state = state.idle_notifier_state();
let idle_notification = data_init.init(
id,
IdleNotificationUserData {
seat: seat.clone(),
is_idle: AtomicBool::new(false),
timeout,
timer_token: Mutex::new(None),
ignore_inhibitor: false,
},
);
idle_notifier_state.reinsert_timer(&idle_notification);
state
.idle_notifier_state()
.notifications
.entry(seat)
.or_default()
.push(idle_notification);
}
ext_idle_notifier_v1::Request::GetInputIdleNotification { id, timeout, seat } => {
let timeout = Duration::from_millis(timeout as u64);
let idle_notifier_state = state.idle_notifier_state();
let idle_notification = data_init.init(
id,
IdleNotificationUserData {
seat: seat.clone(),
is_idle: AtomicBool::new(false),
timeout,
timer_token: Mutex::new(None),
ignore_inhibitor: true,
},
);
idle_notifier_state.reinsert_timer(&idle_notification);
state
.idle_notifier_state()
.notifications
.entry(seat)
.or_default()
.push(idle_notification);
}
ext_idle_notifier_v1::Request::Destroy => {}
_ => unimplemented!(),
}
}
}
impl<D> Dispatch<ExtIdleNotificationV1, IdleNotificationUserData, D> for IdleNotifierState<D>
where
D: Dispatch<ExtIdleNotificationV1, IdleNotificationUserData>,
D: IdleNotifierHandler,
{
fn request(
_state: &mut D,
_client: &Client,
_resource: &ExtIdleNotificationV1,
request: ext_idle_notification_v1::Request,
_data: &IdleNotificationUserData,
_dhandle: &DisplayHandle,
_data_init: &mut DataInit<'_, D>,
) {
match request {
ext_idle_notification_v1::Request::Destroy => {}
_ => unimplemented!(),
}
}
fn destroyed(
state: &mut D,
_client: ClientId,
notification: &ExtIdleNotificationV1,
data: &IdleNotificationUserData,
) {
let state = state.idle_notifier_state();
if let Some(notifications) = state.notifications.get_mut(&data.seat) {
notifications.retain(|x| x != notification);
}
state
.notifications
.retain(|seat, notifications| !notifications.is_empty() && seat.is_alive());
}
}
/// Macro to delegate implementation of the ext idle notify protocol
#[macro_export]
macro_rules! delegate_idle_notify {
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
type __ExtIdleNotifierV1 =
$crate::reexports::wayland_protocols::ext::idle_notify::v1::server::ext_idle_notifier_v1::ExtIdleNotifierV1;
type __ExtIdleNotificationV1 =
$crate::reexports::wayland_protocols::ext::idle_notify::v1::server::ext_idle_notification_v1::ExtIdleNotificationV1;
$crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__ExtIdleNotifierV1: ()
] => $crate::wayland::idle_notify::IdleNotifierState<$ty>
);
$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__ExtIdleNotifierV1: ()
] => $crate::wayland::idle_notify::IdleNotifierState<$ty>
);
$crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
[
__ExtIdleNotificationV1: $crate::wayland::idle_notify::IdleNotificationUserData
] => $crate::wayland::idle_notify::IdleNotifierState<$ty>
);
};
}