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};
pub trait IdleNotifierHandler: Sized {
fn idle_notifier_state(&mut self) -> &mut IdleNotifierState<Self>;
}
#[derive(Debug)]
pub struct IdleNotificationUserData {
seat: WlSeat,
is_idle: AtomicBool,
timeout: Duration,
timer_token: Mutex<Option<RegistrationToken>>,
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)
}
}
#[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> {
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,
}
}
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);
}
}
}
pub fn is_inhibited(&mut self) -> bool {
self.is_inhibited
}
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);
}
}
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> {
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_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>
);
};
}