diff --git a/src/ei/ei_ifs/ei_device.rs b/src/ei/ei_ifs/ei_device.rs index e3e6cc6a..81e6c3d6 100644 --- a/src/ei/ei_ifs/ei_device.rs +++ b/src/ei/ei_ifs/ei_device.rs @@ -11,7 +11,7 @@ use { leaks::Tracker, rect::Rect, scale::Scale, - utils::syncqueue::SyncQueue, + utils::{copyhashmap::CopyHashMap, syncqueue::SyncQueue}, wire_ei::{ ei_device::{ ClientFrame, ClientStartEmulating, ClientStopEmulating, Destroyed, DeviceType, @@ -37,7 +37,7 @@ pub struct EiDevice { pub seat: Rc, pub button_changes: SyncQueue<(u32, KeyState)>, - pub touch_changes: SyncQueue<(u32, TouchChange)>, + pub touch_changes: CopyHashMap, pub scroll_px: [Cell>; 2], pub scroll_v120: [Cell>; 2], pub scroll_stop: [Cell>; 2], @@ -219,7 +219,7 @@ impl EiDeviceRequestHandler for EiDevice { } } if self.touch_changes.is_not_empty() { - while let Some((touch_id, change)) = self.touch_changes.pop() { + for (touch_id, change) in self.touch_changes.lock().drain() { let id = touch_id as i32; match change { TouchChange::Down(x, y) => { @@ -233,6 +233,7 @@ impl EiDeviceRequestHandler for EiDevice { seat.touch_motion_at(time, id, x, y); } TouchChange::Up => seat.touch_up(time, id), + TouchChange::Cancel => seat.touch_cancel(time, id), } } seat.touch_frame(time); diff --git a/src/ei/ei_ifs/ei_seat.rs b/src/ei/ei_ifs/ei_seat.rs index d8469b40..520b0eb2 100644 --- a/src/ei/ei_ifs/ei_seat.rs +++ b/src/ei/ei_ifs/ei_seat.rs @@ -197,6 +197,18 @@ impl EiSeat { } } + pub fn handle_touch_cancel(&self, id: u32) { + if self.is_sender() { + return; + } + if let Some(b) = self.touchscreen.get() { + if self.client.versions.ei_touchscreen() >= EiVersion(2) { + b.send_cancel(id); + } + b.send_up(id); + } + } + pub fn handle_touch_frame(&self, time_usec: u64) { if self.is_sender() { return; diff --git a/src/ei/ei_ifs/ei_touchscreen.rs b/src/ei/ei_ifs/ei_touchscreen.rs index 52c0d60d..e86a927d 100644 --- a/src/ei/ei_ifs/ei_touchscreen.rs +++ b/src/ei/ei_ifs/ei_touchscreen.rs @@ -7,15 +7,16 @@ use { }, fixed::Fixed, leaks::Tracker, + utils::clonecell::UnsafeCellCloneSafe, wire_ei::{ ei_touchscreen::{ - ClientDown, ClientMotion, ClientUp, EiTouchscreenRequestHandler, Release, - ServerDown, ServerMotion, ServerUp, + ClientCancel, ClientDown, ClientMotion, ClientUp, EiTouchscreenRequestHandler, + Release, ServerCancel, ServerDown, ServerMotion, ServerUp, }, EiTouchscreenId, }, }, - std::rc::Rc, + std::{collections::hash_map::Entry, rc::Rc}, thiserror::Error, }; @@ -32,8 +33,11 @@ pub enum TouchChange { Down(f32, f32), Motion(f32, f32), Up, + Cancel, } +unsafe impl UnsafeCellCloneSafe for TouchChange {} + ei_device_interface!(EiTouchscreen, ei_touchscreen, touchscreen); impl EiTouchscreen { @@ -61,6 +65,37 @@ impl EiTouchscreen { touchid, }); } + + pub fn send_cancel(&self, touchid: u32) { + self.client.event(ServerCancel { + self_id: self.id, + touchid, + }); + } + + fn set_client_event(&self, touchid: u32, event: TouchChange) -> Result<(), EiTouchscreenError> { + match self.device.touch_changes.lock().entry(touchid) { + Entry::Occupied(mut o) => { + use TouchChange::*; + match (o.get(), event) { + (Motion(_, _), Motion(_, _)) + | (Down(_, _), Down(_, _)) + | (Up, Up) + | (Cancel, Cancel) + | (Up, Cancel) => { + o.insert(event); + Ok(()) + } + (Cancel, Up) => Ok(()), + _ => Err(EiTouchscreenError::InvalidEventCombination), + } + } + Entry::Vacant(v) => { + v.insert(event); + Ok(()) + } + } + } } impl EiTouchscreenRequestHandler for EiTouchscreen { @@ -72,24 +107,19 @@ impl EiTouchscreenRequestHandler for EiTouchscreen { } fn client_down(&self, req: ClientDown, _slf: &Rc) -> Result<(), Self::Error> { - self.device - .touch_changes - .push((req.touchid, TouchChange::Down(req.x, req.y))); - Ok(()) + self.set_client_event(req.touchid, TouchChange::Down(req.x, req.y)) } fn client_motion(&self, req: ClientMotion, _slf: &Rc) -> Result<(), Self::Error> { - self.device - .touch_changes - .push((req.touchid, TouchChange::Motion(req.x, req.y))); - Ok(()) + self.set_client_event(req.touchid, TouchChange::Motion(req.x, req.y)) } fn client_up(&self, req: ClientUp, _slf: &Rc) -> Result<(), Self::Error> { - self.device - .touch_changes - .push((req.touchid, TouchChange::Up)); - Ok(()) + self.set_client_event(req.touchid, TouchChange::Up) + } + + fn client_cancel(&self, req: ClientCancel, _slf: &Rc) -> Result<(), Self::Error> { + self.set_client_event(req.touchid, TouchChange::Cancel) } } @@ -104,5 +134,7 @@ impl EiObject for EiTouchscreen {} pub enum EiTouchscreenError { #[error(transparent)] EiClientError(Box), + #[error("Touch frame contains an invalid combination of events")] + InvalidEventCombination, } efrom!(EiTouchscreenError, EiClientError); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 159dd676..e3778b95 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -761,9 +761,9 @@ impl WlSeatGlobal { self.touch_owner.motion(self, time_usec, id, x, y); } - fn touch_cancel(self: &Rc, time_usec: u64, id: i32) { + pub fn touch_cancel(self: &Rc, time_usec: u64, id: i32) { self.for_each_ei_seat(|ei_seat| { - ei_seat.handle_touch_up(id as _); + ei_seat.handle_touch_cancel(id as _); }); self.state.for_each_seat_tester(|t| { t.send_touch_cancel(self.id, time_usec, id); diff --git a/src/utils/syncqueue.rs b/src/utils/syncqueue.rs index 55d8a1ba..4ab3f24a 100644 --- a/src/utils/syncqueue.rs +++ b/src/utils/syncqueue.rs @@ -37,6 +37,7 @@ impl SyncQueue { unsafe { self.el.get().deref_mut().is_empty() } } + #[expect(dead_code)] pub fn is_not_empty(&self) -> bool { !self.is_empty() } diff --git a/wire-ei/ei_touchscreen.txt b/wire-ei/ei_touchscreen.txt index 2bca8286..3c732898 100644 --- a/wire-ei/ei_touchscreen.txt +++ b/wire-ei/ei_touchscreen.txt @@ -17,6 +17,10 @@ request client_up (sender) { touchid: u32, } +request client_cancel (sender, since = 2) { + touchid: u32, +} + event destroyed { serial: u32, } @@ -36,3 +40,7 @@ event server_motion (receiver) { event server_up (receiver) { touchid: u32, } + +event server_cancel (receiver, since = 2) { + touchid: u32, +}