//! Touch-related types for smithay's input abstraction use std::collections::HashMap; use std::fmt; use std::sync::{Arc, Mutex}; use tracing::{info_span, instrument}; #[cfg(feature = "wayland_frontend")] use wayland_server::Weak; use crate::backend::input::TouchSlot; use crate::utils::{IsAlive, Logical, Point, Serial, SerialCounter}; pub use grab::{DefaultGrab, GrabStartData, TouchDownGrab, TouchGrab}; use super::{GrabStatus, Seat, SeatHandler}; mod grab; /// An handle to a touch handler /// /// It can be cloned and all clones manipulate the same internal state. /// /// This handle gives you access to an interface to send touch events to your /// clients. /// /// When sending events using this handle, they will be intercepted by a touch /// grab if any is active. See the [`TouchGrab`] trait for details. pub struct TouchHandle { pub(crate) inner: Arc>>, #[cfg(feature = "wayland_frontend")] pub(crate) known_instances: Arc, Option)>>>, pub(crate) span: tracing::Span, } #[cfg(not(feature = "wayland_frontend"))] impl fmt::Debug for TouchHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TouchHandle").field("inner", &self.inner).finish() } } #[cfg(feature = "wayland_frontend")] impl fmt::Debug for TouchHandle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TouchHandle") .field("inner", &self.inner) .field("known_instances", &self.known_instances) .finish() } } impl Clone for TouchHandle { #[inline] fn clone(&self) -> Self { Self { inner: self.inner.clone(), #[cfg(feature = "wayland_frontend")] known_instances: self.known_instances.clone(), span: self.span.clone(), } } } impl std::hash::Hash for TouchHandle { #[inline] fn hash(&self, state: &mut H) { Arc::as_ptr(&self.inner).hash(state) } } impl std::cmp::PartialEq for TouchHandle { #[inline] fn eq(&self, other: &Self) -> bool { Arc::ptr_eq(&self.inner, &other.inner) } } impl std::cmp::Eq for TouchHandle {} pub(crate) struct TouchInternal { focus: HashMap>, seq_counter: SerialCounter, default_grab: Box Box> + Send + 'static>, grab: GrabStatus>, } struct TouchSlotState { focus: Option<(::TouchFocus, Point)>, frame_pending: Option<::TouchFocus>, pending: Serial, current: Option, } impl fmt::Debug for TouchSlotState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TouchSlotState") .field("focus", &self.focus) .field("frame_pending", &self.frame_pending) .field("pending", &self.pending) .field("current", &self.current) .finish() } } // image_callback does not implement debug, so we have to impl Debug manually impl fmt::Debug for TouchInternal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TouchInternal") .field("focus", &self.focus) .field("grab", &self.grab) .finish() } } /// Pointer motion event #[derive(Debug, Clone)] pub struct DownEvent { /// Slot of this event pub slot: TouchSlot, /// Location of the touch in compositor space pub location: Point, /// Serial of the event pub serial: Serial, /// Timestamp of the event, with millisecond granularity pub time: u32, } /// Pointer motion event #[derive(Debug, Clone)] pub struct UpEvent { /// Slot of this event pub slot: TouchSlot, /// Serial of the event pub serial: Serial, /// Timestamp of the event, with millisecond granularity pub time: u32, } /// Pointer motion event #[derive(Debug, Clone)] pub struct MotionEvent { /// Slot of this event pub slot: TouchSlot, /// Location of the touch in compositor space pub location: Point, /// Timestamp of the event, with millisecond granularity pub time: u32, } /// Pointer motion event #[derive(Debug, Clone, Copy)] pub struct ShapeEvent { /// Slot of this event pub slot: TouchSlot, /// Length of the major axis in surface-local coordinates pub major: f64, /// Length of the minor axis in surface-local coordinates pub minor: f64, } /// Pointer motion event #[derive(Debug, Clone, Copy)] pub struct OrientationEvent { /// Slot of this event pub slot: TouchSlot, /// Angle between major axis and positive surface y-axis in degrees pub orientation: f64, } /// Trait representing object that can receive touch interactions pub trait TouchTarget: IsAlive + PartialEq + Clone + fmt::Debug + Send where D: SeatHandler, { /// A new touch point has appeared on the target. /// /// This touch point is assigned a unique ID. Future events from this touch point reference this ID. /// The ID ceases to be valid after a touch up event and may be reused in the future. fn down(&self, seat: &Seat, data: &mut D, event: &DownEvent, seq: Serial); /// The touch point has disappeared. /// /// No further events will be sent for this touch point and the touch point's ID /// is released and may be reused in a future touch down event. fn up(&self, seat: &Seat, data: &mut D, event: &UpEvent, seq: Serial); /// A touch point has changed coordinates. fn motion(&self, seat: &Seat, data: &mut D, event: &MotionEvent, seq: Serial); /// Indicates the end of a set of events that logically belong together. fn frame(&self, seat: &Seat, data: &mut D, seq: Serial); /// Touch session cancelled. /// /// Touch cancellation applies to all touch points currently active on this target. /// The client is responsible for finalizing the touch points, future touch points on /// this target may reuse the touch point ID. fn cancel(&self, seat: &Seat, data: &mut D, seq: Serial); /// Sent when a touch point has changed its shape. /// /// A touch point shape is approximated by an ellipse through the major and minor axis length. /// The major axis length describes the longer diameter of the ellipse, while the minor axis /// length describes the shorter diameter. Major and minor are orthogonal and both are specified /// in surface-local coordinates. The center of the ellipse is always at the touch point location /// as reported by [`TouchTarget::down`] or [`TouchTarget::motion`]. fn shape(&self, seat: &Seat, data: &mut D, event: &ShapeEvent, seq: Serial); /// Sent when a touch point has changed its orientation. /// /// The orientation describes the clockwise angle of a touch point's major axis to the positive surface /// y-axis and is normalized to the -180 to +180 degree range. The granularity of orientation depends /// on the touch device, some devices only support binary rotation values between 0 and 90 degrees. fn orientation(&self, seat: &Seat, data: &mut D, event: &OrientationEvent, seq: Serial); } impl TouchHandle { pub(crate) fn new(default_grab: F) -> TouchHandle where F: Fn() -> Box> + Send + 'static, { TouchHandle { inner: Arc::new(Mutex::new(TouchInternal::new(default_grab))), #[cfg(feature = "wayland_frontend")] known_instances: Arc::new(Mutex::new(Vec::new())), span: info_span!("input_touch"), } } /// Change the current grab on this touch to the provided grab /// /// Overwrites any current grab. #[instrument(level = "debug", parent = &self.span, skip(self, data, grab))] pub fn set_grab + 'static>(&self, data: &mut D, grab: G, serial: Serial) { let seat = self.get_seat(data); self.inner.lock().unwrap().set_grab(data, &seat, serial, grab); } /// Remove any current grab on this touch, resetting it to the default behavior #[instrument(level = "debug", parent = &self.span, skip(self, data))] pub fn unset_grab(&self, data: &mut D) { let seat = self.get_seat(data); self.inner.lock().unwrap().unset_grab(data, &seat); } /// Check if this touch is currently grabbed with this serial pub fn has_grab(&self, serial: Serial) -> bool { let guard = self.inner.lock().unwrap(); match guard.grab { GrabStatus::Active(s, _) => s == serial, _ => false, } } /// Check if this touch is currently being grabbed pub fn is_grabbed(&self) -> bool { let guard = self.inner.lock().unwrap(); !matches!(guard.grab, GrabStatus::None) } /// Returns the start data for the grab, if any. pub fn grab_start_data(&self) -> Option> { let guard = self.inner.lock().unwrap(); match &guard.grab { GrabStatus::Active(_, g) => Some(g.start_data().clone()), _ => None, } } /// Calls `f` with the active grab, if any. pub fn with_grab(&self, f: impl FnOnce(Serial, &dyn TouchGrab) -> T) -> Option { let guard = self.inner.lock().unwrap(); if let GrabStatus::Active(s, g) = &guard.grab { Some(f(*s, &**g)) } else { None } } /// Notify that a new touch point appeared /// /// You provide the location of the touch, in the form of: /// /// - The coordinates of the touch in the global compositor space /// - The surface on top of which the touch point is, and the coordinates of its /// origin in the global compositor space (or `None` of the touch is not /// on top of a client surface). pub fn down( &self, data: &mut D, focus: Option<(::TouchFocus, Point)>, event: &DownEvent, ) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.down(data, handle, focus, event, seq); }); } /// Notify that a touch point disappeared pub fn up(&self, data: &mut D, event: &UpEvent) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.up(data, handle, event, seq); }); } /// Notify that a touch point has changed coordinates. /// /// You provide the location of the touch, in the form of: /// /// - The coordinates of the touch in the global compositor space /// - The surface on top of which the touch point is, and the coordinates of its /// origin in the global compositor space (or `None` of the touch is not /// on top of a client surface). /// /// **Note** that this will **not** update the focus of the touch point, the focus /// is only set on [`TouchHandle::down`]. The focus provided to this function /// can be used to find DnD targets during touch motion. pub fn motion( &self, data: &mut D, focus: Option<(::TouchFocus, Point)>, event: &MotionEvent, ) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.motion(data, handle, focus, event, seq); }); } /// Notify about the end of a set of events that logically belong together. /// /// This needs to be called after one or move calls to [`TouchHandle::down`] or [`TouchHandle::motion`] pub fn frame(&self, data: &mut D) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.frame(data, handle, seq); }); } /// Notify that the touch session has been cancelled. /// /// Use in case you decide the touch stream is a global gesture. /// This will remove all current focus targets, and no further events will be sent /// until a new touch point appears. pub fn cancel(&self, data: &mut D) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.cancel(data, handle, seq); }); } /// Notify that a touch point has changed its shape. pub fn shape(&self, data: &mut D, event: &ShapeEvent) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.shape(data, handle, event, seq); }); } /// Notify that a touch point has changed its orientation. pub fn orientation(&self, data: &mut D, event: &OrientationEvent) { let mut inner = self.inner.lock().unwrap(); let seat = self.get_seat(data); let seq = inner.seq_counter.next_serial(); inner.with_grab(data, &seat, |data, handle, grab| { grab.orientation(data, handle, event, seq); }); } fn get_seat(&self, data: &mut D) -> Seat { let seat_state = data.seat_state(); seat_state .seats .iter() .find(|seat| seat.get_touch().map(|h| &h == self).unwrap_or(false)) .cloned() .unwrap() } } /// This inner handle is accessed from inside a pointer grab logic, and directly /// sends event to the client pub struct TouchInnerHandle<'a, D: SeatHandler> { inner: &'a mut TouchInternal, seat: &'a Seat, } impl fmt::Debug for TouchInnerHandle<'_, D> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TouchInnerHandle") .field("inner", &self.inner) .field("seat", &self.seat.arc.name) .finish() } } impl TouchInnerHandle<'_, D> { /// Change the current grab on this pointer to the provided grab /// /// Overwrites any current grab. pub fn set_grab + 'static>( &mut self, handler: &mut dyn TouchGrab, data: &mut D, serial: Serial, grab: G, ) { handler.unset(data); self.inner.set_grab(data, self.seat, serial, grab); } /// Remove any current grab on this pointer, resetting it to the default behavior /// /// This will also restore the focus of the underlying pointer if restore_focus /// is [`true`] pub fn unset_grab(&mut self, handler: &mut dyn TouchGrab, data: &mut D) { handler.unset(data); self.inner.unset_grab(data, self.seat); } /// Notify that a new touch point appeared /// /// You provide the location of the touch, in the form of: /// /// - The coordinates of the touch in the global compositor space /// - The surface on top of which the touch point is, and the coordinates of its /// origin in the global compositor space (or `None` of the touch is not /// on top of a client surface). pub fn down( &mut self, data: &mut D, focus: Option<(::TouchFocus, Point)>, event: &DownEvent, seq: Serial, ) { self.inner.down(data, self.seat, focus, event, seq) } /// Notify that a touch point disappeared pub fn up(&mut self, data: &mut D, event: &UpEvent, seq: Serial) { self.inner.up(data, self.seat, event, seq) } /// Notify that a touch point has changed coordinates. /// /// You provide the location of the touch, in the form of: /// /// - The coordinates of the touch in the global compositor space /// - The surface on top of which the touch point is, and the coordinates of its /// origin in the global compositor space (or `None` of the touch is not /// on top of a client surface). /// /// **Note** that this will **not** update the focus of the touch point, the focus /// is only set on [`TouchHandle::down`]. The focus provided to this function /// can be used to find DnD targets during touch motion. pub fn motion( &mut self, data: &mut D, focus: Option<(::TouchFocus, Point)>, event: &MotionEvent, seq: Serial, ) { self.inner.motion(data, self.seat, focus, event, seq) } /// Notify about the end of a set of events that logically belong together. /// /// This needs to be called after one or move calls to [`TouchHandle::down`] or [`TouchHandle::motion`] pub fn frame(&mut self, data: &mut D, seq: Serial) { self.inner.frame(data, self.seat, seq) } /// Notify that a touch point has changed its shape. pub fn shape(&mut self, data: &mut D, event: &ShapeEvent, seq: Serial) { self.inner.shape(data, self.seat, event, seq) } /// Notify that a touch point has changed its orientation. pub fn orientation(&mut self, data: &mut D, event: &OrientationEvent, seq: Serial) { self.inner.orientation(data, self.seat, event, seq) } /// Notify that the touch session has been cancelled. /// /// Use in case you decide the touch stream is a global gesture. /// This will remove all current focus targets, and no further events will be sent /// until a new touch point appears. pub fn cancel(&mut self, data: &mut D, seq: Serial) { self.inner.cancel(data, self.seat, seq) } } impl TouchInternal { fn new(default_grab: F) -> Self where F: Fn() -> Box> + Send + 'static, { Self { focus: Default::default(), seq_counter: SerialCounter::new(), default_grab: Box::new(default_grab), grab: GrabStatus::None, } } fn set_grab + 'static>( &mut self, data: &mut D, _seat: &Seat, serial: Serial, grab: G, ) { if let GrabStatus::Active(_, handler) = &mut self.grab { handler.unset(data); } self.grab = GrabStatus::Active(serial, Box::new(grab)); } fn unset_grab(&mut self, data: &mut D, _seat: &Seat) { if let GrabStatus::Active(_, handler) = &mut self.grab { handler.unset(data); } self.grab = GrabStatus::None; } fn down( &mut self, data: &mut D, seat: &Seat, focus: Option<(::TouchFocus, Point)>, event: &DownEvent, seq: Serial, ) { self.focus .entry(event.slot) .and_modify(|state| { state.pending = seq; state.frame_pending = None; state.focus.clone_from(&focus); }) .or_insert_with(|| TouchSlotState { focus, frame_pending: None, pending: seq, current: None, }); let state = self.focus.get(&event.slot).unwrap(); if let Some((focus, loc)) = state.focus.as_ref() { let mut new_event = event.clone(); new_event.location -= *loc; focus.down(seat, data, &new_event, seq); } } fn up(&mut self, data: &mut D, seat: &Seat, event: &UpEvent, seq: Serial) { let Some(state) = self.focus.get_mut(&event.slot) else { return; }; state.pending = seq; if let Some((focus, _)) = state.focus.take() { focus.up(seat, data, event, seq); // Keep the focus around to be able to send a frame event after up, but move // it out of the current focus to prevent sending other events. state.frame_pending = Some(focus); } } fn motion( &mut self, data: &mut D, seat: &Seat, _focus: Option<(::TouchFocus, Point)>, event: &MotionEvent, seq: Serial, ) { let Some(state) = self.focus.get_mut(&event.slot) else { return; }; state.pending = seq; if let Some((focus, loc)) = state.focus.as_ref() { let mut new_event = event.clone(); new_event.location -= *loc; focus.motion(seat, data, &new_event, seq); } } fn frame(&mut self, data: &mut D, seat: &Seat, seq: Serial) { for state in self.focus.values_mut() { if state.current.map(|c| c >= state.pending).unwrap_or(false) { continue; } state.current = Some(seq); // Send the frame event for any stored focus in the up handler if let Some(focus) = state.frame_pending.take() { focus.frame(seat, data, seq); } if let Some((focus, _)) = state.focus.as_ref() { focus.frame(seat, data, seq); } } } fn cancel(&mut self, data: &mut D, seat: &Seat, seq: Serial) { for state in self.focus.values_mut() { if state.current.map(|c| c >= state.pending).unwrap_or(false) { continue; } state.current = Some(seq); if let Some((focus, _)) = state.focus.take() { focus.cancel(seat, data, seq); } } } fn shape(&mut self, data: &mut D, seat: &Seat, event: &ShapeEvent, seq: Serial) { let Some(state) = self.focus.get_mut(&event.slot) else { return; }; state.pending = seq; if let Some((focus, _)) = state.focus.as_ref() { focus.shape(seat, data, event, seq); } } fn orientation(&mut self, data: &mut D, seat: &Seat, event: &OrientationEvent, seq: Serial) { let Some(state) = self.focus.get_mut(&event.slot) else { return; }; state.pending = seq; if let Some((focus, _)) = state.focus.as_ref() { focus.orientation(seat, data, event, seq); } } fn with_grab(&mut self, data: &mut D, seat: &Seat, f: F) where F: FnOnce(&mut D, &mut TouchInnerHandle<'_, D>, &mut dyn TouchGrab), { let mut grab = std::mem::replace(&mut self.grab, GrabStatus::Borrowed); match grab { GrabStatus::Borrowed => panic!("Accessed a touch grab from within a touch grab access."), GrabStatus::Active(_, ref mut handler) => { // If this grab is associated with a surface that is no longer alive, discard it if let Some((ref focus, _)) = handler.start_data().focus { if !focus.alive() { handler.unset(data); self.grab = GrabStatus::None; let mut default_grab = (self.default_grab)(); f( data, &mut TouchInnerHandle { inner: self, seat }, &mut *default_grab, ); return; } } f(data, &mut TouchInnerHandle { inner: self, seat }, &mut **handler); } GrabStatus::None => { let mut default_grab = (self.default_grab)(); f( data, &mut TouchInnerHandle { inner: self, seat }, &mut *default_grab, ); } } if let GrabStatus::Borrowed = self.grab { // the grab has not been ended nor replaced, put it back in place self.grab = grab; } } }