diff --git a/src/backend.rs b/src/backend.rs index 318d8c4b..75410dd9 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -32,10 +32,6 @@ pub trait Backend { let _ = idle; } - fn supports_idle(&self) -> bool { - false - } - fn import_environment(&self) -> bool { false } diff --git a/src/backends/metal.rs b/src/backends/metal.rs index b3c3a212..0d323120 100644 --- a/src/backends/metal.rs +++ b/src/backends/metal.rs @@ -198,10 +198,6 @@ impl Backend for MetalBackend { } } - fn supports_idle(&self) -> bool { - true - } - fn import_environment(&self) -> bool { true } diff --git a/src/globals.rs b/src/globals.rs index 93430b82..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, @@ -166,6 +167,8 @@ impl Globals { add_singleton!(WpContentTypeManagerV1Global); add_singleton!(XdgActivationV1Global); add_singleton!(ExtForeignToplevelListV1Global); + add_singleton!(ZwpIdleInhibitManagerV1Global); + add_singleton!(ExtIdleNotifierV1Global); } pub fn add_backend_singletons(&self, backend: &Rc) { @@ -174,9 +177,6 @@ impl Globals { self.add_global_no_broadcast(&Rc::new($name::new(self.name()))); }; } - if backend.supports_idle() { - add_singleton!(ZwpIdleInhibitManagerV1Global); - } if backend.supports_presentation_feedback() { add_singleton!(WpPresentationGlobal); } 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/it/test_backend.rs b/src/it/test_backend.rs index d6f99a69..5391347e 100644 --- a/src/it/test_backend.rs +++ b/src/it/test_backend.rs @@ -206,10 +206,6 @@ impl Backend for TestBackend { fn set_idle(&self, _idle: bool) {} - fn supports_idle(&self) -> bool { - true - } - fn supports_presentation_feedback(&self) -> bool { true } diff --git a/src/tasks/idle.rs b/src/tasks/idle.rs index 936c0d47..e785f3ea 100644 --- a/src/tasks/idle.rs +++ b/src/tasks/idle.rs @@ -13,9 +13,6 @@ use { }; pub async fn idle(state: Rc, backend: Rc) { - if !backend.supports_idle() { - return; - } let timer = match TimerFd::new(c::CLOCK_MONOTONIC) { Ok(t) => t, Err(e) => { 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), +}