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 }