git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

src/server/touch.rs (38.9K)

  1 //! Touchscreen input: where each finger goes, and the gestures the
  2 //! compositor keeps for itself.
  3 //!
  4 //! **Routing.** A finger is routed once, at touch-down (`TouchRoute`): a
  5 //! surface whose client bound `wl_touch` gets real touch events; anything
  6 //! else gets the pointer, a left button held where the finger is. The
  7 //! pointer's press waits until the finger has moved past `TAP_SLOP` (then
  8 //! it is pressed where the finger went down, and the drag follows) or has
  9 //! lifted (a tap: press and release). Nothing is lost by the wait — the
 10 //! press lands at the down point either way — and it is what lets a second
 11 //! or third finger turn the touch into a gesture with no half-made click
 12 //! to take back.
 13 //!
 14 //! **Gestures** (`Claim`). While one is live, every finger is the
 15 //! compositor's; fingers already given to clients are cancelled
 16 //! (`wl_touch.cancel`) when it begins.
 17 //!
 18 //! - **Edge swipe**: one finger landing within `EDGE_ZONE` of a screen edge
 19 //!   that has an `edge_<side>` bind in input.kdl, and moving `EDGE_FIRE`
 20 //!   inward, fires that bind once. A finger that goes along or back out
 21 //!   instead is handed to the normal route, from its down point; one that
 22 //!   lifts without moving is delivered as the tap it was.
 23 //! - **Desk pan and zoom**: a finger dragged on the bare desk (in normal
 24 //!   mode — in overview a one-finger drag there is the selection band)
 25 //!   pans the desk under it, and two fingers that start on the desk pan it
 26 //!   and pinch-zoom about their midpoint; the lift coasts, like a trackpad
 27 //!   pan's.
 28 //! - **Three and four fingers**, anywhere: once the fingers have moved
 29 //!   enough to tell (`decide`), a swipe runs through the touchpad's swipe
 30 //!   handling — the same `swipe3_*` / `swipe4_*` binds, lean, repeat steps
 31 //!   and focus aim — and a pinch fires the `pinch3_*` / `pinch4_*` binds.
 32 //!   **A touchscreen swipe is natural**: the desk follows the fingers, so
 33 //!   the bind fires for the direction the camera goes, which is opposite
 34 //!   the fingers — dragging the desk right reveals what is to the left, and
 35 //!   fires `swipe3_left`. On a touchpad the bind is the direction the
 36 //!   fingers move.
 37 
 38 use crate::cursor::{gesture_bind, gesture_mods, pinch_hits, run_gesture_action, Cursor};
 39 use crate::ffi;
 40 use crate::scene_node_data::SceneNodeDataVal;
 41 use crate::seat::Focus;
 42 
 43 /// Travel (layout px) a finger may wander and still be a tap.
 44 pub const TAP_SLOP: f64 = 10.0;
 45 /// How near a screen edge a finger must land to start an edge swipe.
 46 pub const EDGE_ZONE: f64 = 24.0;
 47 /// Inward travel that fires an edge swipe's bind.
 48 pub const EDGE_FIRE: f64 = 60.0;
 49 /// Centroid travel that decides three or more fingers are swiping.
 50 pub const DECIDE_TRAVEL: f64 = 24.0;
 51 /// Spread change (as a ratio) that decides they are pinching.
 52 pub const DECIDE_SCALE: f64 = 1.15;
 53 
 54 const BTN_LEFT: u32 = 0x110;
 55 
 56 /// One finger on a touchscreen (`Cursor::touch_points`).
 57 #[derive(Clone, Copy, Debug)]
 58 pub struct TouchPoint {
 59     pub lx: f64,
 60     pub ly: f64,
 61     pub route: TouchRoute,
 62 }
 63 
 64 /// Where a finger's events go, fixed at touch-down for its lifetime — a
 65 /// finger that slides onto another window keeps talking to the one it went
 66 /// down on, exactly like a held button's implicit grab.
 67 #[derive(Clone, Copy, Debug, PartialEq)]
 68 pub enum TouchRoute {
 69     /// A client that bound `wl_touch` gets real touch events. Positions are
 70     /// surface-local through the frame frozen at down: `origin` is the
 71     /// surface's layout origin and `scale` its surface-per-layout-pixel
 72     /// ratio, the same mapping the pointer's implicit grab uses
 73     /// (`grab_origin` / `grab_scale`).
 74     Client { origin: (f64, f64), scale: f64 },
 75     /// This finger drives the pointer. Everything that is not a
 76     /// touch-capable client goes this way — the desktop, and every press
 77     /// the compositor itself handles (overview, the adjust-mode handles,
 78     /// drag-selection). `pressed` stays false until the finger has moved
 79     /// past `TAP_SLOP` or lifted (see the module comment); `on_desk` is a
 80     /// down on the bare desk, which a drag or a second finger turns into
 81     /// a desk pan.
 82     Pointer { start: (f64, f64), pressed: bool, on_desk: bool },
 83     /// Owned by the live gesture (`Cursor::touch_claim`).
 84     Claimed,
 85     /// Swallowed until it lifts: a second finger while another drives the
 86     /// pointer, or a touch while a real button is held.
 87     Ignored,
 88 }
 89 
 90 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
 91 pub enum Edge {
 92     Left,
 93     Right,
 94     Top,
 95     Bottom,
 96 }
 97 
 98 impl Edge {
 99     /// The chord's spelling (`edge_left`) without the prefix.
100     pub fn name(self) -> &'static str {
101         match self {
102             Edge::Left => "left",
103             Edge::Right => "right",
104             Edge::Top => "top",
105             Edge::Bottom => "bottom",
106         }
107     }
108 
109     /// Unit vector pointing away from the edge, into the screen.
110     pub fn inward(self) -> (f64, f64) {
111         match self {
112             Edge::Left => (1.0, 0.0),
113             Edge::Right => (-1.0, 0.0),
114             Edge::Top => (0.0, 1.0),
115             Edge::Bottom => (0.0, -1.0),
116         }
117     }
118 }
119 
120 /// Where an edge swipe stands after the finger has moved by `(dx, dy)`.
121 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
122 pub enum EdgeProgress {
123     /// Not far enough to tell.
124     Undecided,
125     /// Inward past `EDGE_FIRE`, and more inward than along the edge.
126     Fire,
127     /// Past the slop, but along the edge or back out: not an edge swipe.
128     NotEdge,
129 }
130 
131 pub fn edge_progress(edge: Edge, dx: f64, dy: f64) -> EdgeProgress {
132     let (ix, iy) = edge.inward();
133     let inward = dx * ix + dy * iy;
134     let along = (dx * iy - dy * ix).abs();
135     if inward >= EDGE_FIRE && inward > along {
136         EdgeProgress::Fire
137     } else if dx.hypot(dy) >= TAP_SLOP && inward < along {
138         EdgeProgress::NotEdge
139     } else {
140         EdgeProgress::Undecided
141     }
142 }
143 
144 /// The fingers as one shape: their centroid and their spread (mean
145 /// distance from it), which is what a pan follows and a pinch measures.
146 #[derive(Clone, Copy, Debug, Default, PartialEq)]
147 pub struct Shape {
148     pub centroid: (f64, f64),
149     pub spread: f64,
150     pub count: usize,
151 }
152 
153 pub fn shape(points: &[(f64, f64)]) -> Shape {
154     if points.is_empty() {
155         return Shape::default();
156     }
157     let n = points.len() as f64;
158     let cx = points.iter().map(|p| p.0).sum::<f64>() / n;
159     let cy = points.iter().map(|p| p.1).sum::<f64>() / n;
160     let spread = points.iter().map(|p| (p.0 - cx).hypot(p.1 - cy)).sum::<f64>() / n;
161     Shape { centroid: (cx, cy), spread, count: points.len() }
162 }
163 
164 /// A gesture's motion, accumulated across finger sets: a finger landing or
165 /// lifting moves the centroid and the spread without anything having moved,
166 /// so a change of set rebases (`rebase`) instead of counting as travel.
167 #[derive(Clone, Copy, Debug, PartialEq)]
168 pub struct Track {
169     last: Shape,
170     /// Centroid travel since the gesture began.
171     pub travel: (f64, f64),
172     /// Product of the spread's ratios since the gesture began.
173     pub scale: f64,
174     /// The most fingers seen at once: what a swipe or pinch bind matches.
175     pub fingers: usize,
176 }
177 
178 impl Track {
179     pub fn new(s: Shape) -> Self {
180         Track { last: s, travel: (0.0, 0.0), scale: 1.0, fingers: s.count }
181     }
182 
183     pub fn rebase(&mut self, s: Shape) {
184         self.last = s;
185         self.fingers = self.fingers.max(s.count);
186     }
187 
188     /// One sample of the current fingers: the centroid's motion since the
189     /// last one. A different finger count is a rebase, not motion.
190     pub fn step(&mut self, s: Shape) -> (f64, f64) {
191         if s.count != self.last.count {
192             self.rebase(s);
193             return (0.0, 0.0);
194         }
195         let d = (s.centroid.0 - self.last.centroid.0, s.centroid.1 - self.last.centroid.1);
196         self.travel.0 += d.0;
197         self.travel.1 += d.1;
198         // A single finger has no spread to compare, and two fingers almost
199         // on top of each other would turn a pixel of jitter into a zoom.
200         if s.count >= 2 && self.last.spread > 4.0 && s.spread > 4.0 {
201             self.scale *= s.spread / self.last.spread;
202         }
203         self.last = s;
204         d
205     }
206 }
207 
208 /// What three or more fingers are doing, once it can be told.
209 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
210 pub enum MultiKind {
211     Undecided,
212     Swipe,
213     /// `fired`: its bind has run; the rest of the gesture is ignored.
214     Pinch { fired: bool },
215 }
216 
217 pub fn decide(track: &Track) -> MultiKind {
218     if track.scale >= DECIDE_SCALE || track.scale <= 1.0 / DECIDE_SCALE {
219         MultiKind::Pinch { fired: false }
220     } else if track.travel.0.hypot(track.travel.1) >= DECIDE_TRAVEL {
221         MultiKind::Swipe
222     } else {
223         MultiKind::Undecided
224     }
225 }
226 
227 /// The finger count a bind matches for a gesture tracked with `fingers`:
228 /// binds name two to four, and a fifth finger is still a four-finger swipe.
229 fn bind_fingers(fingers: usize) -> u32 {
230     fingers.clamp(3, 4) as u32
231 }
232 
233 /// The gesture the compositor holds (`Cursor::touch_claim`).
234 #[derive(Clone, Copy, Debug, PartialEq)]
235 pub enum Claim {
236     None,
237     /// One finger from a bound edge (see the module comment).
238     Edge { id: i32, edge: Edge, start: (f64, f64), fired: bool },
239     /// The desk follows the fingers. `start_zoom` is the zoom the pinch
240     /// scales; `vel` (virtual px/s per axis) and `last_ms` feed the coast.
241     Desk { track: Track, start_zoom: f64, vel: [f64; 2], last_ms: u32 },
242     /// Three or more fingers.
243     Multi { track: Track, kind: MultiKind },
244 }
245 
246 impl Default for Claim {
247     fn default() -> Self {
248         Claim::None
249     }
250 }
251 
252 impl Cursor {
253     /// Decide where a new finger at `lx, ly` goes. In window-adjust mode
254     /// (overview, or Super held) every press is the compositor's to spend
255     /// — the handles, a window body's drag, the rubber band — and those live
256     /// on the pointer path, so the finger becomes the pointer even over a
257     /// touch-capable window.
258     unsafe fn touch_route_at(&self, lx: f64, ly: f64) -> TouchRoute {
259         let seat = &*self.seat;
260         let server = seat.server;
261         let result = (*server).scene.at(lx, ly);
262         if let Some(ref result) = result {
263             // Locked, only the lock surface may hear a finger, as only it
264             // may hear the pointer (`passthrough`).
265             let locked = (*server).lock_manager.state != crate::lock_manager::LockState::Unlocked;
266             let lock_ok = !locked || matches!(result.data, SceneNodeDataVal::LockSurface(_));
267             if lock_ok
268                 && !result.surface.is_null()
269                 && !(*server).wm.window_adjust_active()
270                 && ffi::wlr_surface_accepts_touch(result.surface, seat.wlr_seat)
271             {
272                 // The implicit grab's frame (see the button path): the
273                 // surface's scene buffer may be drawn scaled, so map through
274                 // its destination size rather than assume 1:1.
275                 let mut scale = 1.0;
276                 if !result.node.is_null() {
277                     let dest_w = ffi::river_scene_buffer_get_dest_width(result.node as *mut ffi::wlr_scene_buffer);
278                     let surf_w = ffi::river_wlr_surface_get_width(result.surface);
279                     if dest_w > 0 && surf_w > 0 {
280                         scale = surf_w as f64 / dest_w as f64;
281                     }
282                 }
283                 let origin = (lx - result.sx / scale, ly - result.sy / scale);
284                 return TouchRoute::Client { origin, scale };
285             }
286         }
287         // The pointer is singular: a second finger cannot also drive it, and
288         // a real button held (or a seat op it started) owns it already.
289         let pointer_busy = self.touch_points.values().any(|p| matches!(p.route, TouchRoute::Pointer { .. }))
290             || !self.pressed.is_empty()
291             || seat.op.is_some();
292         if pointer_busy {
293             return TouchRoute::Ignored;
294         }
295         let on_desk = match result {
296             None => (*server).lock_manager.state == crate::lock_manager::LockState::Unlocked,
297             Some(r) => matches!(r.data, SceneNodeDataVal::Window(w) if (*w).is_wallpaper()),
298         };
299         TouchRoute::Pointer { start: (lx, ly), pressed: false, on_desk }
300     }
301 
302     pub unsafe fn touch_down(&mut self, id: i32, lx: f64, ly: f64, time_msec: u32) {
303         (*self.seat).handle_activity();
304         // A touch is deliberate input, like a press (see `Seat::focus`).
305         (*(*self.seat).server).wm.startup_input_seen = true;
306         self.hide_for_touch();
307         // A device that reuses a live id without lifting it first has lost
308         // the up; finish the old point so its button or client is released.
309         if self.touch_points.contains_key(&id) {
310             log::warn!("touch: down for live touch id {id}; lifting the old point first");
311             self.touch_up(id, time_msec);
312         }
313 
314         match self.touch_claim {
315             // A second finger is not an edge swipe: hand the first back
316             // before routing this one.
317             Claim::Edge { .. } => self.edge_release(time_msec),
318             Claim::Desk { track, .. } => {
319                 self.touch_points.insert(id, TouchPoint { lx, ly, route: TouchRoute::Claimed });
320                 // Three fingers on a desk that has not moved yet were a
321                 // three-finger gesture landing one finger at a time.
322                 let still = track.travel.0.hypot(track.travel.1) < TAP_SLOP && (track.scale - 1.0).abs() < 0.05;
323                 if still && self.touch_points.len() >= 3 {
324                     self.touch_claim = Claim::Multi { track: Track::new(self.claimed_shape()), kind: MultiKind::Undecided };
325                 } else {
326                     self.claim_rebase();
327                 }
328                 return;
329             }
330             Claim::Multi { .. } => {
331                 self.touch_points.insert(id, TouchPoint { lx, ly, route: TouchRoute::Claimed });
332                 self.claim_rebase();
333                 return;
334             }
335             Claim::None => {}
336         }
337 
338         let already = self.touch_points.len();
339         if already == 0 {
340             if let Some(edge) = self.bound_edge_at(lx, ly) {
341                 log::debug!("touch: down id={id} in the {} edge zone", edge.name());
342                 self.touch_points.insert(id, TouchPoint { lx, ly, route: TouchRoute::Claimed });
343                 self.touch_claim = Claim::Edge { id, edge, start: (lx, ly), fired: false };
344                 return;
345             }
346         }
347         let pointer_pressed = self.touch_points.values().any(|p| matches!(p.route, TouchRoute::Pointer { pressed: true, .. }));
348         if already + 1 >= 3 && !pointer_pressed {
349             self.claim_all();
350             self.touch_points.insert(id, TouchPoint { lx, ly, route: TouchRoute::Claimed });
351             self.touch_claim = Claim::Multi { track: Track::new(self.claimed_shape()), kind: MultiKind::Undecided };
352             log::debug!("touch: {} fingers claimed for a gesture", self.touch_points.len());
353             return;
354         }
355         if already == 1 {
356             let first_on_desk = self
357                 .touch_points
358                 .values()
359                 .any(|p| matches!(p.route, TouchRoute::Pointer { pressed: false, on_desk: true, .. }));
360             if first_on_desk {
361                 self.claim_all();
362                 self.touch_points.insert(id, TouchPoint { lx, ly, route: TouchRoute::Claimed });
363                 self.start_desk();
364                 return;
365             }
366         }
367         self.touch_begin(id, lx, ly, time_msec);
368     }
369 
370     /// Arm the on-screen keyboard (`osk.rs`) when the finger is on an
371     /// app's window — not the board itself, a layer surface, nor the bar.
372     unsafe fn note_touch_for_osk(&mut self, lx: f64, ly: f64) {
373         let server = (*self.seat).server;
374         let Some(result) = (*server).scene.at(lx, ly) else { return };
375         if let SceneNodeDataVal::Window(window) = result.data {
376             if !(*window).is_status_bar() && !(*window).is_wallpaper() {
377                 (*self.seat).relay.osk.note_touch();
378             }
379         }
380     }
381 
382     /// Route a finger normally (`touch_route_at`) and deliver its down.
383     unsafe fn touch_begin(&mut self, id: i32, lx: f64, ly: f64, time_msec: u32) {
384         let server = (*self.seat).server;
385         let route = self.touch_route_at(lx, ly);
386         self.touch_points.insert(id, TouchPoint { lx, ly, route });
387         log::debug!("touch: down id={id} at ({lx:.0}, {ly:.0}) -> {route:?}");
388 
389         match route {
390             TouchRoute::Client { origin, scale } => {
391                 crate::cursor::press_dismissals(server, lx, ly);
392                 let Some(result) = (*server).scene.at(lx, ly) else { return };
393                 // Focus as a click would (`handle_button`).
394                 let seat = &mut *self.seat;
395                 match result.data {
396                     SceneNodeDataVal::Window(window) => {
397                         if !(*window).is_status_bar() && !(*window).is_wallpaper() {
398                             seat.focus(Focus::Window(window));
399                             seat.relay.osk.note_touch();
400                         }
401                     }
402                     SceneNodeDataVal::LayerSurface(layer_surface) => {
403                         if crate::cursor::layer_takes_click_focus(layer_surface) {
404                             seat.focus(Focus::LayerSurface(result.surface));
405                         }
406                     }
407                     _ => {}
408                 }
409                 ffi::wlr_seat_touch_notify_down(
410                     seat.wlr_seat,
411                     result.surface,
412                     time_msec,
413                     id,
414                     (lx - origin.0) * scale,
415                     (ly - origin.1) * scale,
416                 );
417             }
418             // Hover now; the press waits (see `TouchRoute::Pointer`).
419             TouchRoute::Pointer { .. } => self.warp_to(lx, ly),
420             TouchRoute::Claimed | TouchRoute::Ignored => {}
421         }
422     }
423 
424     pub unsafe fn touch_motion(&mut self, id: i32, lx: f64, ly: f64, time_msec: u32) {
425         (*self.seat).handle_activity();
426         let Some(point) = self.touch_points.get_mut(&id) else { return };
427         point.lx = lx;
428         point.ly = ly;
429         let route = point.route;
430         match route {
431             TouchRoute::Claimed => self.claim_motion(time_msec),
432             TouchRoute::Client { origin, scale } => {
433                 ffi::wlr_seat_touch_notify_motion(
434                     (*self.seat).wlr_seat,
435                     time_msec,
436                     id,
437                     (lx - origin.0) * scale,
438                     (ly - origin.1) * scale,
439                 );
440                 // A touch drag's icon follows the finger.
441                 self.update_drag_icons();
442             }
443             TouchRoute::Pointer { start, pressed: false, on_desk } => {
444                 if (lx - start.0).hypot(ly - start.1) < TAP_SLOP {
445                     return;
446                 }
447                 let normal = (*(*self.seat).server).wm.mode == crate::window_manager::WindowManagerMode::Normal;
448                 if on_desk && normal && self.touch_points.len() == 1 {
449                     // A drag on the bare desk pans it. The track starts at
450                     // the down point, so the desk catches up with the
451                     // finger's whole travel, not just what follows the slop.
452                     self.touch_points.get_mut(&id).unwrap().route = TouchRoute::Claimed;
453                     let mut track = Track::new(shape(&[start]));
454                     track.fingers = 1;
455                     self.touch_claim = Claim::Desk {
456                         track,
457                         start_zoom: (*(*self.seat).server).wm.desk_zoom,
458                         vel: [0.0, 0.0],
459                         last_ms: 0,
460                     };
461                     (*(*self.seat).server).wm.stop_panning_animation();
462                     self.claim_motion(time_msec);
463                     return;
464                 }
465                 // A drag: press where the finger went down, then follow it.
466                 self.touch_points.get_mut(&id).unwrap().route = TouchRoute::Pointer { start, pressed: true, on_desk };
467                 self.warp_to(start.0, start.1);
468                 self.inject_button(BTN_LEFT, true);
469                 self.warp_to(lx, ly);
470             }
471             TouchRoute::Pointer { pressed: true, .. } => self.warp_to(lx, ly),
472             TouchRoute::Ignored => {}
473         }
474     }
475 
476     pub unsafe fn touch_up(&mut self, id: i32, time_msec: u32) {
477         (*self.seat).handle_activity();
478         let Some(point) = self.touch_points.remove(&id) else { return };
479         match point.route {
480             TouchRoute::Claimed => self.claim_lift(id, point, time_msec, false),
481             TouchRoute::Client { .. } => {
482                 // A field that starts editing on the release is still this
483                 // touch's (`osk.rs`).
484                 self.note_touch_for_osk(point.lx, point.ly);
485                 ffi::wlr_seat_touch_notify_up((*self.seat).wlr_seat, time_msec, id);
486             }
487             TouchRoute::Pointer { start, pressed: false, .. } => {
488                 // A tap.
489                 self.note_touch_for_osk(start.0, start.1);
490                 self.warp_to(start.0, start.1);
491                 self.inject_button(BTN_LEFT, true);
492                 self.inject_button(BTN_LEFT, false);
493             }
494             TouchRoute::Pointer { pressed: true, .. } => self.inject_button(BTN_LEFT, false),
495             TouchRoute::Ignored => {}
496         }
497     }
498 
499     /// The device gave up on a finger (a palm, a gesture the kernel took).
500     /// A client is told `wl_touch.cancel`, which voids its whole sequence; a
501     /// finger driving the pointer releases the button — a cancelled drag
502     /// still drops where it was, but a held button must never be left
503     /// behind — and one that never pressed clicks nothing.
504     pub unsafe fn touch_cancel(&mut self, id: i32) {
505         (*self.seat).handle_activity();
506         let Some(point) = self.touch_points.remove(&id) else { return };
507         match point.route {
508             TouchRoute::Claimed => self.claim_lift(id, point, crate::util::msec_timestamp(), true),
509             TouchRoute::Client { .. } => ffi::river_wlr_seat_touch_cancel_point((*self.seat).wlr_seat, id),
510             TouchRoute::Pointer { pressed: true, .. } => self.inject_button(BTN_LEFT, false),
511             TouchRoute::Pointer { pressed: false, .. } | TouchRoute::Ignored => {}
512         }
513     }
514 
515     pub unsafe fn touch_frame(&mut self) {
516         // The emulated pointer frames each of its own events; only touch
517         // clients are waiting on this one.
518         if self.touch_points.values().any(|p| matches!(p.route, TouchRoute::Client { .. }))
519             || ffi::wlr_seat_touch_num_points((*self.seat).wlr_seat) > 0
520         {
521             ffi::wlr_seat_touch_notify_frame((*self.seat).wlr_seat);
522         }
523     }
524 
525     // ── Gestures ────────────────────────────────────────────────────────
526 
527     /// Take every finger already down for a gesture: clients hear
528     /// `wl_touch.cancel`, and a pointer finger that never pressed is simply
529     /// dropped (the reason the press waits).
530     unsafe fn claim_all(&mut self) {
531         let seat = (*self.seat).wlr_seat;
532         let ids: Vec<i32> = self.touch_points.keys().copied().collect();
533         for id in ids {
534             let point = self.touch_points.get_mut(&id).unwrap();
535             let route = std::mem::replace(&mut point.route, TouchRoute::Claimed);
536             match route {
537                 TouchRoute::Client { .. } => ffi::river_wlr_seat_touch_cancel_point(seat, id),
538                 TouchRoute::Pointer { pressed: true, .. } => self.inject_button(BTN_LEFT, false),
539                 _ => {}
540             }
541         }
542     }
543 
544     unsafe fn claimed_shape(&self) -> Shape {
545         let points: Vec<(f64, f64)> = self
546             .touch_points
547             .values()
548             .filter(|p| p.route == TouchRoute::Claimed)
549             .map(|p| (p.lx, p.ly))
550             .collect();
551         shape(&points)
552     }
553 
554     unsafe fn claim_rebase(&mut self) {
555         let s = self.claimed_shape();
556         match &mut self.touch_claim {
557             Claim::Desk { track, .. } | Claim::Multi { track, .. } => track.rebase(s),
558             _ => {}
559         }
560     }
561 
562     unsafe fn start_desk(&mut self) {
563         let wm = &mut (*(*self.seat).server).wm;
564         wm.stop_panning_animation();
565         self.touch_claim = Claim::Desk {
566             track: Track::new(self.claimed_shape()),
567             start_zoom: wm.desk_zoom,
568             vel: [0.0, 0.0],
569             last_ms: 0,
570         };
571         log::debug!("touch: desk gesture with {} fingers", self.touch_points.len());
572     }
573 
574     unsafe fn claim_motion(&mut self, time_msec: u32) {
575         let s = self.claimed_shape();
576         let server = (*self.seat).server;
577         match self.touch_claim {
578             Claim::None => {}
579             Claim::Edge { id, edge, start, fired } => {
580                 if fired {
581                     return;
582                 }
583                 let Some(p) = self.touch_points.get(&id) else { return };
584                 match edge_progress(edge, p.lx - start.0, p.ly - start.1) {
585                     EdgeProgress::Undecided => {}
586                     EdgeProgress::NotEdge => self.edge_release(time_msec),
587                     EdgeProgress::Fire => {
588                         self.touch_claim = Claim::Edge { id, edge, start, fired: true };
589                         let mods = gesture_mods(&*self.seat);
590                         let wm = &mut (*server).wm;
591                         if let Some((action, command)) = gesture_bind(wm, "edge", 1, mods, |d| d == edge.name()) {
592                             log::info!("touch: edge_{} fired {action:?}", edge.name());
593                             run_gesture_action(wm, action, command.as_deref());
594                         }
595                     }
596                 }
597             }
598             Claim::Desk { mut track, start_zoom, mut vel, mut last_ms } => {
599                 let d = track.step(s);
600                 let wm = &mut (*server).wm;
601                 if d != (0.0, 0.0) {
602                     // The desk follows the fingers: the camera goes the
603                     // other way, in virtual units.
604                     let step = (-d.0 / wm.desk_zoom, -d.1 / wm.desk_zoom);
605                     wm.queue_pan(step.0, step.1);
606                     let dt = time_msec.wrapping_sub(last_ms).clamp(4, 100) as f64 / 1000.0;
607                     for (axis, v) in [step.0, step.1].into_iter().enumerate() {
608                         let sample = v / dt;
609                         vel[axis] = if last_ms == 0 { sample } else { vel[axis] * 0.65 + sample * 0.35 };
610                     }
611                     last_ms = time_msec;
612                     wm.pan_finger_v = vel;
613                 }
614                 if s.count >= 2 && track.scale != 1.0 {
615                     let zoom = crate::policy::camera::pinch_zoom(start_zoom, track.scale);
616                     let (ax, ay) = output_local(server, s.centroid);
617                     wm.queue_pinch(zoom, ax, ay);
618                 }
619                 self.touch_claim = Claim::Desk { track, start_zoom, vel, last_ms };
620             }
621             Claim::Multi { mut track, kind } => {
622                 let d = track.step(s);
623                 let fingers = bind_fingers(track.fingers);
624                 let kind = match kind {
625                     MultiKind::Undecided => match decide(&track) {
626                         MultiKind::Swipe => {
627                             log::info!("touch: {fingers}-finger swipe");
628                             // Natural: the camera goes against the fingers.
629                             self.touch_swipe("begin", fingers, 0.0, 0.0);
630                             self.touch_swipe("update", fingers, -track.travel.0, -track.travel.1);
631                             MultiKind::Swipe
632                         }
633                         MultiKind::Pinch { .. } => {
634                             log::info!("touch: {fingers}-finger pinch");
635                             self.touch_pinch(fingers, track.scale)
636                         }
637                         MultiKind::Undecided => MultiKind::Undecided,
638                     },
639                     MultiKind::Swipe => {
640                         if d != (0.0, 0.0) {
641                             self.touch_swipe("update", fingers, -d.0, -d.1);
642                         }
643                         MultiKind::Swipe
644                     }
645                     MultiKind::Pinch { fired: false } => self.touch_pinch(fingers, track.scale),
646                     fired @ MultiKind::Pinch { fired: true } => fired,
647                 };
648                 self.touch_claim = Claim::Multi { track, kind };
649             }
650         }
651     }
652 
653     /// A claimed finger lifted (or was cancelled). The gesture ends with
654     /// its last finger.
655     unsafe fn claim_lift(&mut self, id: i32, point: TouchPoint, time_msec: u32, cancelled: bool) {
656         match self.touch_claim {
657             Claim::None => {}
658             Claim::Edge { id: edge_id, start, fired, .. } => {
659                 if edge_id != id {
660                     return;
661                 }
662                 self.touch_claim = Claim::None;
663                 // Lifted where it landed: it was a tap in the edge zone (the
664                 // status bar's, say), so deliver it as one, late.
665                 if !fired && !cancelled && (point.lx - start.0).hypot(point.ly - start.1) < TAP_SLOP {
666                     self.touch_begin(id, start.0, start.1, time_msec);
667                     self.touch_up(id, time_msec);
668                 }
669             }
670             Claim::Desk { vel, last_ms, .. } => {
671                 if self.touch_points.values().any(|p| p.route == TouchRoute::Claimed) {
672                     self.claim_rebase();
673                     return;
674                 }
675                 self.touch_claim = Claim::None;
676                 let wm = &mut (*(*self.seat).server).wm;
677                 wm.pan_finger_v = [0.0, 0.0];
678                 // Fling on the last velocity unless the fingers had come to
679                 // rest first, as a trackpad pan's lift does (`handle_axis`).
680                 let resting = last_ms == 0 || time_msec.wrapping_sub(last_ms) > 80;
681                 if !cancelled && !resting && wm.kinetic_scroll() && vel != [0.0, 0.0] {
682                     wm.pan_coast_vx = vel[0];
683                     wm.pan_coast_vy = vel[1];
684                     wm.start_panning_animation();
685                 }
686             }
687             Claim::Multi { kind, track } => {
688                 if self.touch_points.values().any(|p| p.route == TouchRoute::Claimed) {
689                     self.claim_rebase();
690                     return;
691                 }
692                 self.touch_claim = Claim::None;
693                 if kind == MultiKind::Swipe {
694                     self.touch_swipe("end", bind_fingers(track.fingers), 0.0, 0.0);
695                 }
696             }
697         }
698     }
699 
700     /// The edge finger is not an edge swipe after all: route it normally
701     /// from where it went down, and catch it up to where it is.
702     unsafe fn edge_release(&mut self, time_msec: u32) {
703         let Claim::Edge { id, start, .. } = self.touch_claim else { return };
704         self.touch_claim = Claim::None;
705         let Some(now) = self.touch_points.get(&id).map(|p| (p.lx, p.ly)) else { return };
706         self.touch_begin(id, start.0, start.1, time_msec);
707         self.touch_motion(id, now.0, now.1, time_msec);
708     }
709 
710     /// The edge whose zone `lx, ly` is in, if an `edge_<side>` bind exists
711     /// for it under the current modifiers. An edge is a screen edge only
712     /// where no other output continues past it.
713     unsafe fn bound_edge_at(&self, lx: f64, ly: f64) -> Option<Edge> {
714         let server = (*self.seat).server;
715         let wm = &(*server).wm;
716         if !wm.gesture_binds.iter().any(|b| b.gesture_type == "edge") {
717             return None;
718         }
719         let layout = (*server).om.output_layout;
720         let output = ffi::wlr_output_layout_output_at(layout, lx, ly);
721         if output.is_null() {
722             return None;
723         }
724         let mut b = ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 };
725         ffi::wlr_output_layout_get_box(layout, output, &mut b);
726         let (x0, y0) = (b.x as f64, b.y as f64);
727         let (x1, y1) = (x0 + b.width as f64, y0 + b.height as f64);
728         let beyond_is_empty = |x: f64, y: f64| ffi::wlr_output_layout_output_at(layout, x, y).is_null();
729         let candidates = [
730             (Edge::Left, lx - x0 < EDGE_ZONE && beyond_is_empty(x0 - 1.0, ly)),
731             (Edge::Right, x1 - lx <= EDGE_ZONE && beyond_is_empty(x1 + 1.0, ly)),
732             (Edge::Top, ly - y0 < EDGE_ZONE && beyond_is_empty(lx, y0 - 1.0)),
733             (Edge::Bottom, y1 - ly <= EDGE_ZONE && beyond_is_empty(lx, y1 + 1.0)),
734         ];
735         let mods = gesture_mods(&*self.seat);
736         candidates
737             .into_iter()
738             .filter(|&(_, near)| near)
739             .map(|(edge, _)| edge)
740             .find(|edge| gesture_bind(wm, "edge", 1, mods, |d| d == edge.name()).is_some())
741     }
742 
743     /// One stage of a touchscreen swipe, through the touchpad's handling.
744     unsafe fn touch_swipe(&mut self, stage: &str, fingers: u32, dx: f64, dy: f64) {
745         self.gesture_from_touch = true;
746         self.inject_swipe_stage(stage, fingers, dx, dy);
747         self.gesture_from_touch = false;
748     }
749 
750     /// Fire a pinch bind if the spread has gone far enough; once per gesture.
751     unsafe fn touch_pinch(&mut self, fingers: u32, scale: f64) -> MultiKind {
752         let mods = gesture_mods(&*self.seat);
753         let wm = &mut (*(*self.seat).server).wm;
754         match gesture_bind(wm, "pinch", fingers, mods, |d| pinch_hits(d, scale)) {
755             Some((action, command)) => {
756                 log::info!("touch: pinch fired {action:?}");
757                 run_gesture_action(wm, action, command.as_deref());
758                 MultiKind::Pinch { fired: true }
759             }
760             None => MultiKind::Pinch { fired: false },
761         }
762     }
763 
764     /// Layout coordinates of a touch event's normalized `x, y`, through the
765     /// device's output mapping (`map_to_output` / `map_to_region`).
766     unsafe fn touch_layout_coords(&self, touch: *mut ffi::wlr_touch, x: f64, y: f64) -> (f64, f64) {
767         let wlr_device = if touch.is_null() {
768             std::ptr::null_mut()
769         } else {
770             &mut (*touch).base as *mut ffi::wlr_input_device
771         };
772         let mut lx = 0.0;
773         let mut ly = 0.0;
774         ffi::wlr_cursor_absolute_to_layout_coords(self.wlr_cursor, wlr_device, x, y, &mut lx, &mut ly);
775         (lx, ly)
776     }
777 }
778 
779 /// A layout point in its output's own coordinates, the anchor a pinch zoom
780 /// takes (`WindowManager::queue_pinch`).
781 unsafe fn output_local(server: *mut crate::server::Server, (x, y): (f64, f64)) -> (f64, f64) {
782     let wlr_output = (*server).om.output_at(x, y);
783     if wlr_output.is_null() {
784         return (x, y);
785     }
786     let mut b = ffi::wlr_box { x: 0, y: 0, width: 0, height: 0 };
787     ffi::wlr_output_layout_get_box((*server).om.output_layout, wlr_output, &mut b);
788     (x - b.x as f64, y - b.y as f64)
789 }
790 
791 pub(crate) unsafe extern "C" fn handle_touch_down(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
792     let cursor = &mut *crate::container_of!(listener, Cursor, touch_down_listener);
793     let event = data as *mut ffi::wlr_touch_down_event;
794     let (lx, ly) = cursor.touch_layout_coords((*event).touch, (*event).x, (*event).y);
795     cursor.touch_down((*event).touch_id, lx, ly, (*event).time_msec);
796 }
797 
798 pub(crate) unsafe extern "C" fn handle_touch_motion(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
799     let cursor = &mut *crate::container_of!(listener, Cursor, touch_motion_listener);
800     let event = data as *mut ffi::wlr_touch_motion_event;
801     let (lx, ly) = cursor.touch_layout_coords((*event).touch, (*event).x, (*event).y);
802     cursor.touch_motion((*event).touch_id, lx, ly, (*event).time_msec);
803 }
804 
805 pub(crate) unsafe extern "C" fn handle_touch_up(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
806     let cursor = &mut *crate::container_of!(listener, Cursor, touch_up_listener);
807     let event = data as *mut ffi::wlr_touch_up_event;
808     cursor.touch_up((*event).touch_id, (*event).time_msec);
809 }
810 
811 pub(crate) unsafe extern "C" fn handle_touch_cancel(listener: *mut ffi::wl_listener, data: *mut std::ffi::c_void) {
812     let cursor = &mut *crate::container_of!(listener, Cursor, touch_cancel_listener);
813     let event = data as *mut ffi::wlr_touch_cancel_event;
814     cursor.touch_cancel((*event).touch_id);
815 }
816 
817 pub(crate) unsafe extern "C" fn handle_touch_frame(listener: *mut ffi::wl_listener, _data: *mut std::ffi::c_void) {
818     let cursor = &mut *crate::container_of!(listener, Cursor, touch_frame_listener);
819     cursor.touch_frame();
820 }
821 
822 #[cfg(test)]
823 mod tests {
824     use super::*;
825 
826     #[test]
827     fn an_edge_swipe_fires_inward_and_lets_go_along_the_edge() {
828         // From the left edge, straight in.
829         assert_eq!(edge_progress(Edge::Left, 30.0, 2.0), EdgeProgress::Undecided);
830         assert_eq!(edge_progress(Edge::Left, EDGE_FIRE, 5.0), EdgeProgress::Fire);
831         // From the bottom, inward is up.
832         assert_eq!(edge_progress(Edge::Bottom, 0.0, -EDGE_FIRE), EdgeProgress::Fire);
833         assert_eq!(edge_progress(Edge::Bottom, 0.0, EDGE_FIRE), EdgeProgress::NotEdge);
834         // Along the top edge (a drag in the status bar) is not an edge swipe.
835         assert_eq!(edge_progress(Edge::Top, 40.0, 3.0), EdgeProgress::NotEdge);
836         // Diagonal but mostly inward still fires.
837         assert_eq!(edge_progress(Edge::Right, -70.0, 40.0), EdgeProgress::Fire);
838         // Jitter inside the slop decides nothing.
839         assert_eq!(edge_progress(Edge::Top, 5.0, -3.0), EdgeProgress::Undecided);
840     }
841 
842     #[test]
843     fn shape_is_centroid_and_mean_spread() {
844         let s = shape(&[(0.0, 0.0), (10.0, 0.0)]);
845         assert_eq!(s.centroid, (5.0, 0.0));
846         assert_eq!(s.spread, 5.0);
847         assert_eq!(s.count, 2);
848         assert_eq!(shape(&[]).count, 0);
849     }
850 
851     #[test]
852     fn a_track_rebases_when_a_finger_lands_or_lifts() {
853         let mut t = Track::new(shape(&[(0.0, 0.0), (100.0, 0.0)]));
854         assert_eq!(t.step(shape(&[(10.0, 0.0), (110.0, 0.0)])), (10.0, 0.0));
855         // A third finger lands far away: the centroid jumps, but nothing moved.
856         assert_eq!(t.step(shape(&[(10.0, 0.0), (110.0, 0.0), (60.0, 300.0)])), (0.0, 0.0));
857         assert_eq!(t.travel, (10.0, 0.0));
858         assert_eq!(t.fingers, 3);
859         // Spreading to twice the distance scales by two.
860         let mut p = Track::new(shape(&[(0.0, 0.0), (100.0, 0.0)]));
861         p.step(shape(&[(-50.0, 0.0), (150.0, 0.0)]));
862         assert!((p.scale - 2.0).abs() < 1e-9);
863         assert_eq!(p.travel, (0.0, 0.0));
864     }
865 
866     #[test]
867     fn three_fingers_decide_between_swipe_and_pinch() {
868         let fingers = [(0.0, 0.0), (60.0, 0.0), (30.0, 50.0)];
869         let mut t = Track::new(shape(&fingers));
870         let moved = |dx: f64, dy: f64| shape(&fingers.map(|(x, y)| (x + dx, y + dy)));
871         t.step(moved(10.0, 0.0));
872         assert_eq!(decide(&t), MultiKind::Undecided);
873         t.step(moved(DECIDE_TRAVEL, 0.0));
874         assert_eq!(decide(&t), MultiKind::Swipe);
875 
876         let mut p = Track::new(shape(&fingers));
877         p.step(shape(&fingers.map(|(x, y)| ((x - 30.0) * 0.6 + 30.0, (y - 16.0) * 0.6 + 16.0))));
878         assert_eq!(decide(&p), MultiKind::Pinch { fired: false });
879     }
880 
881     #[test]
882     fn a_fifth_finger_still_matches_four_finger_binds() {
883         assert_eq!(bind_fingers(3), 3);
884         assert_eq!(bind_fingers(4), 4);
885         assert_eq!(bind_fingers(5), 4);
886     }
887 }