diff --git a/src/globals.rs b/src/globals.rs index 3015ca95..555e4f65 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -4,6 +4,7 @@ use { client::Client, ifs::{ ext_foreign_toplevel_list_v1::ExtForeignToplevelListV1Global, + ext_idle_notifier_v1::ExtIdleNotifierV1Global, ext_session_lock_manager_v1::ExtSessionLockManagerV1Global, ipc::{ wl_data_device_manager::WlDataDeviceManagerGlobal, @@ -167,6 +168,7 @@ impl Globals { add_singleton!(XdgActivationV1Global); add_singleton!(ExtForeignToplevelListV1Global); add_singleton!(ZwpIdleInhibitManagerV1Global); + add_singleton!(ExtIdleNotifierV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { diff --git a/src/ifs.rs b/src/ifs.rs index 9c3d565f..24fdfd89 100644 --- a/src/ifs.rs +++ b/src/ifs.rs @@ -1,5 +1,7 @@ pub mod ext_foreign_toplevel_handle_v1; pub mod ext_foreign_toplevel_list_v1; +pub mod ext_idle_notification_v1; +pub mod ext_idle_notifier_v1; pub mod ext_session_lock_manager_v1; pub mod ext_session_lock_v1; pub mod ipc; diff --git a/src/ifs/ext_idle_notification_v1.rs b/src/ifs/ext_idle_notification_v1.rs new file mode 100644 index 00000000..ac985a7d --- /dev/null +++ b/src/ifs/ext_idle_notification_v1.rs @@ -0,0 +1,72 @@ +use { + crate::{ + async_engine::SpawnedFuture, + client::{Client, ClientError}, + ifs::wl_seat::WlSeatGlobal, + leaks::Tracker, + object::Object, + utils::{ + asyncevent::AsyncEvent, + buffd::{MsgParser, MsgParserError}, + }, + wire::{ext_idle_notification_v1::*, ExtIdleNotificationV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ExtIdleNotificationV1 { + pub id: ExtIdleNotificationV1Id, + pub client: Rc, + pub tracker: Tracker, + pub resume: AsyncEvent, + pub task: Cell>>, + pub seat: Rc, + pub duration_usec: u64, +} + +impl ExtIdleNotificationV1 { + fn detach(&self) { + self.seat.remove_idle_notification(self); + self.task.take(); + } + + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtIdleNotificationV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.detach(); + self.client.remove_obj(self)?; + Ok(()) + } + + pub fn send_idled(&self) { + self.client.event(Idled { self_id: self.id }); + } + + pub fn send_resumed(&self) { + self.client.event(Resumed { self_id: self.id }); + } +} + +object_base! { + self = ExtIdleNotificationV1; + + DESTROY => destroy, +} + +impl Object for ExtIdleNotificationV1 { + fn break_loops(&self) { + self.detach(); + } +} + +simple_add_obj!(ExtIdleNotificationV1); + +#[derive(Debug, Error)] +pub enum ExtIdleNotificationV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtIdleNotificationV1Error, MsgParserError); +efrom!(ExtIdleNotificationV1Error, ClientError); diff --git a/src/ifs/ext_idle_notifier_v1.rs b/src/ifs/ext_idle_notifier_v1.rs new file mode 100644 index 00000000..cfe8061b --- /dev/null +++ b/src/ifs/ext_idle_notifier_v1.rs @@ -0,0 +1,142 @@ +use { + crate::{ + client::{Client, ClientError}, + globals::{Global, GlobalName}, + ifs::ext_idle_notification_v1::ExtIdleNotificationV1, + leaks::Tracker, + object::Object, + time::now_usec, + utils::{ + buffd::{MsgParser, MsgParserError}, + errorfmt::ErrorFmt, + }, + wire::{ext_idle_notifier_v1::*, ExtIdleNotifierV1Id}, + }, + std::{cell::Cell, rc::Rc}, + thiserror::Error, +}; + +pub struct ExtIdleNotifierV1Global { + pub name: GlobalName, +} + +impl ExtIdleNotifierV1Global { + pub fn new(name: GlobalName) -> Self { + Self { name } + } + + fn bind_( + self: Rc, + id: ExtIdleNotifierV1Id, + client: &Rc, + _version: u32, + ) -> Result<(), ExtIdleNotifierV1Error> { + let obj = Rc::new(ExtIdleNotifierV1 { + id, + client: client.clone(), + tracker: Default::default(), + }); + track!(client, obj); + client.add_client_obj(&obj)?; + Ok(()) + } +} + +pub struct ExtIdleNotifierV1 { + pub id: ExtIdleNotifierV1Id, + pub client: Rc, + pub tracker: Tracker, +} + +impl ExtIdleNotifierV1 { + fn destroy(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtIdleNotifierV1Error> { + let _req: Destroy = self.client.parse(self, msg)?; + self.client.remove_obj(self)?; + Ok(()) + } + + fn get_idle_notification(&self, msg: MsgParser<'_, '_>) -> Result<(), ExtIdleNotifierV1Error> { + let req: GetIdleNotification = self.client.parse(self, msg)?; + let seat = self.client.lookup(req.seat)?; + let notification = Rc::new(ExtIdleNotificationV1 { + id: req.id, + client: self.client.clone(), + tracker: Default::default(), + resume: Default::default(), + task: Cell::new(None), + seat: seat.global.clone(), + duration_usec: (req.timeout as u64).max(1000).saturating_mul(1000), + }); + self.client.add_client_obj(¬ification)?; + let future = self.client.state.eng.spawn(run(notification.clone())); + notification.task.set(Some(future)); + Ok(()) + } +} + +async fn run(n: Rc) { + loop { + let now = now_usec(); + let elapsed = now.saturating_sub(n.seat.last_input()); + if elapsed < n.duration_usec { + let res = n + .client + .state + .wheel + .timeout((n.duration_usec - elapsed + 999) / 1000) + .await; + if let Err(e) = res { + log::error!("Could not wait for idle timeout to elapse: {}", ErrorFmt(e)); + return; + } + } else { + n.send_idled(); + n.seat.add_idle_notification(&n); + n.resume.triggered().await; + n.send_resumed(); + } + } +} + +global_base!( + ExtIdleNotifierV1Global, + ExtIdleNotifierV1, + ExtIdleNotifierV1Error +); + +impl Global for ExtIdleNotifierV1Global { + fn singleton(&self) -> bool { + true + } + + fn version(&self) -> u32 { + 1 + } + + fn secure(&self) -> bool { + true + } +} + +simple_add_global!(ExtIdleNotifierV1Global); + +object_base! { + self = ExtIdleNotifierV1; + + DESTROY => destroy, + GET_IDLE_NOTIFICATION => get_idle_notification, +} + +impl Object for ExtIdleNotifierV1 {} + +simple_add_obj!(ExtIdleNotifierV1); + +#[derive(Debug, Error)] +pub enum ExtIdleNotifierV1Error { + #[error("Parsing failed")] + MsgParserError(#[source] Box), + #[error(transparent)] + ClientError(Box), +} +efrom!(ExtIdleNotifierV1Error, MsgParserError); +efrom!(ExtIdleNotifierV1Error, ClientError); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 7347a17c..42552223 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -17,8 +17,9 @@ use { fixed::Fixed, globals::{Global, GlobalName}, ifs::{ - ipc, + ext_idle_notification_v1::ExtIdleNotificationV1, ipc::{ + self, wl_data_device::{ClipboardIpc, WlDataDevice}, wl_data_source::WlDataSource, zwp_primary_selection_device_v1::{ @@ -42,6 +43,7 @@ use { object::Object, rect::Rect, state::State, + time::now_usec, tree::{ generic_node_visitor, ContainerNode, ContainerSplit, Direction, FloatNode, FoundNode, Node, OutputNode, WorkspaceNode, @@ -57,8 +59,8 @@ use { rc_eq::rc_eq, }, wire::{ - wl_seat::*, WlDataDeviceId, WlKeyboardId, WlPointerId, WlSeatId, - ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, + wl_seat::*, ExtIdleNotificationV1Id, WlDataDeviceId, WlKeyboardId, WlPointerId, + WlSeatId, ZwpPrimarySelectionDeviceV1Id, ZwpRelativePointerV1Id, }, xkbcommon::{XkbKeymap, XkbState}, }, @@ -155,6 +157,8 @@ pub struct WlSeatGlobal { cursor_size: Cell, hardware_cursor: Cell, constraint: CloneCell>>, + idle_notifications: CopyHashMap<(ClientId, ExtIdleNotificationV1Id), Rc>, + last_input_usec: Cell, } const CHANGE_CURSOR_MOVED: u32 = 1 << 0; @@ -209,6 +213,8 @@ impl WlSeatGlobal { cursor_size: Cell::new(DEFAULT_CURSOR_SIZE), hardware_cursor: Cell::new(state.globals.seats.len() == 0), constraint: Default::default(), + idle_notifications: Default::default(), + last_input_usec: Cell::new(now_usec()), }); state.add_cursor_size(DEFAULT_CURSOR_SIZE); let seat = slf.clone(); @@ -922,6 +928,22 @@ impl WlSeatGlobal { } Ok(()) } + + pub fn add_idle_notification(&self, notification: &Rc) { + self.idle_notifications.set( + (notification.client.id, notification.id), + notification.clone(), + ); + } + + pub fn remove_idle_notification(&self, notification: &ExtIdleNotificationV1) { + self.idle_notifications + .remove(&(notification.client.id, notification.id)); + } + + pub fn last_input(&self) -> u64 { + self.last_input_usec.get() + } } global_base!(WlSeatGlobal, WlSeat, WlSeatError); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 2225ac18..d818c37c 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -173,6 +173,24 @@ impl NodeSeatState { impl WlSeatGlobal { pub fn event(self: &Rc, dev: &DeviceHandlerData, event: InputEvent) { + match event { + InputEvent::Key { time_usec, .. } + | InputEvent::ConnectorPosition { time_usec, .. } + | InputEvent::Motion { time_usec, .. } + | InputEvent::Button { time_usec, .. } + | InputEvent::AxisFrame { time_usec, .. } => { + self.last_input_usec.set(time_usec); + if self.idle_notifications.is_not_empty() { + for (_, notification) in self.idle_notifications.lock().drain() { + notification.resume.trigger(); + } + } + } + InputEvent::AxisPx { .. } + | InputEvent::AxisSource { .. } + | InputEvent::AxisStop { .. } + | InputEvent::Axis120 { .. } => {} + } match event { InputEvent::Key { time_usec, diff --git a/src/utils/copyhashmap.rs b/src/utils/copyhashmap.rs index fcaf2328..b56edbc2 100644 --- a/src/utils/copyhashmap.rs +++ b/src/utils/copyhashmap.rs @@ -81,6 +81,10 @@ impl CopyHashMap { unsafe { self.map.get().deref().is_empty() } } + pub fn is_not_empty(&self) -> bool { + !self.is_empty() + } + pub fn len(&self) -> usize { unsafe { self.map.get().deref().len() } } diff --git a/wire/ext_idle_notification_v1.txt b/wire/ext_idle_notification_v1.txt new file mode 100644 index 00000000..c302edcc --- /dev/null +++ b/wire/ext_idle_notification_v1.txt @@ -0,0 +1,12 @@ +# requests + +msg destroy = 0 { +} + +# events + +msg idled = 0 { +} + +msg resumed = 1 { +} diff --git a/wire/ext_idle_notifier_v1.txt b/wire/ext_idle_notifier_v1.txt new file mode 100644 index 00000000..2893149a --- /dev/null +++ b/wire/ext_idle_notifier_v1.txt @@ -0,0 +1,10 @@ +# requests + +msg destroy = 0 { +} + +msg get_idle_notification = 1 { + id: id(ext_idle_notification_v1), + timeout: u32, + seat: id(wl_seat), +}