From 3d7f3c53debfdda822e88b5945a2f97fc31e162a Mon Sep 17 00:00:00 2001 From: "Pedro M. de Echanove Pasquin" Date: Tue, 21 Jul 2026 09:56:05 +0200 Subject: [PATCH] ltk: opt-in raw touch stream for apps that consume input themselves Add App::claims_raw_touch(), an opt-in hook that routes the primary finger through the raw on_touch_down/move/up callbacks instead of the built-in single-slot gesture machine. Until now the primary finger was consumed entirely by the widget gesture machine (taps, swipes, presses) and never reached the app, which made it impossible to drive a self-contained input consumer like an embedded WebView or a game canvas from a touchscreen: the widget tree saw the taps, the embedded content never did. With the hook active every finger surfaces verbatim through the raw stream, widget gestures never arm, and per-finger positions are cached in SurfaceState::touch_slots for all slots so wl_touch.up (which carries no coordinates) can still report a release point. The touch module doc and the on_touch_down docs are updated to describe the new contract; the default remains unchanged for every existing app. Pin the transitive dependency ignore to 0.4.23: rust-i18n pulls it via globwalk, 0.4.24+ requires a rustc newer than Debian's 1.85, and its manifest does not declare that MSRV, so a fresh resolution breaks the build on stable. --- Cargo.toml | 3 +++ src/app.rs | 12 ++++++++++++ src/input/touch/mod.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eda76da..b4b18ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ rustybuzz = "0.14" image = { version = "=0.25.9", default-features = false, features = ["png", "jpeg", "webp"] } resvg = "0.44" rust-i18n = "3" +# Transitive dep of rust-i18n (globwalk → ignore); 0.4.24+ needs a rustc +# newer than Debian's 1.85, and its manifest does not declare that MSRV. +ignore = "=0.4.23" wayland-protocols = { version = "0.32", features = ["client", "unstable", "staging"] } wayland-egl = "0.32" khronos-egl = { version = "6", features = ["dynamic"] } diff --git a/src/app.rs b/src/app.rs index f14adee..ea99b48 100644 --- a/src/app.rs +++ b/src/app.rs @@ -602,6 +602,10 @@ pub trait App: 'static /// built-in single-slot machine cannot model. `(x, y)` are in /// physical pixels, matching the coordinate space of /// [`Self::on_pointer_move`]. + /// + /// When [`Self::claims_raw_touch`] returns `true` the primary + /// finger arrives here as well, instead of driving the gesture + /// machine. fn on_touch_down( &mut self, _id: i64, _x: f32, _y: f32 ) {} /// See [`Self::on_touch_down`]. fn on_touch_move( &mut self, _id: i64, _x: f32, _y: f32 ) {} @@ -609,6 +613,14 @@ pub trait App: 'static /// position of the finger (`wl_touch.up` does not carry one). fn on_touch_up( &mut self, _id: i64, _x: f32, _y: f32 ) {} + /// Return `true` to receive the *primary* finger through the raw + /// [`Self::on_touch_down`] / move / up stream as well, bypassing + /// the built-in single-slot gesture machine entirely — widget + /// presses, taps and swipes stop working on touch. For apps whose + /// surface is one self-contained input consumer (an embedded + /// WebView, a game canvas) that needs every finger verbatim. + fn claims_raw_touch( &self ) -> bool { false } + /// Pointer of a cross-application drag-and-drop entered or moved /// inside the main surface. `(x, y)` is in logical pixels. fn on_drop_motion( &mut self, _x: f32, _y: f32 ) {} diff --git a/src/input/touch/mod.rs b/src/input/touch/mod.rs index 4e7b6b2..8c4df53 100644 --- a/src/input/touch/mod.rs +++ b/src/input/touch/mod.rs @@ -26,6 +26,13 @@ //! per touch id, so an auxiliary slot that landed on an overlay //! keeps reporting through that overlay's `App` callback even if //! the finger drifts over the main surface. +//! +//! Apps that return `true` from [`App::claims_raw_touch`] opt out of +//! the primary-slot gesture machine entirely: every finger, primary +//! included, surfaces through the raw `on_touch_*` stream, and +//! widget presses, taps and swipes never fire. Positions are cached +//! in `SurfaceState::touch_slots` for all fingers in that mode so +//! `up` can report a release point. use smithay_client_toolkit::seat::touch::TouchHandler; use smithay_client_toolkit::reexports::client:: @@ -58,6 +65,16 @@ impl TouchHandler for AppData let pos = self.surface( focus ).to_physical( position.0, position.1 ); self.pointer_pos = pos; + // Raw-touch apps take every finger verbatim; the gesture + // machine never arms. Slots are tracked so `up` (which carries + // no position in wl_touch) can report the release point. + if self.app.claims_raw_touch() + { + self.surface_mut( focus ).touch_slots.insert( id, pos ); + self.app.on_touch_down( id as i64, pos.x, pos.y ); + return; + } + // Compositor re-grab after the origin surface died mid-drag: this // slot is already our primary and the drag state was migrated here, // so treat the redundant `down` as a continuation, not a restart. @@ -178,6 +195,16 @@ impl TouchHandler for AppData ) { let focus = self.touch_focus.remove( &id ).unwrap_or( SurfaceFocus::Main ); + if self.app.claims_raw_touch() + { + let pos = + { + let ss = self.surface_mut( focus ); + ss.touch_slots.remove( &id ).unwrap_or( self.pointer_pos ) + }; + self.app.on_touch_up( id as i64, pos.x, pos.y ); + return; + } // Auxiliary release: not the primary slot → recover the last // recorded position for the up callback (Wayland's `wl_touch.up` // does not carry one) and bail before reaching the gesture @@ -227,6 +254,17 @@ impl TouchHandler for AppData let focus = *self.touch_focus.get( &id ).unwrap_or( &SurfaceFocus::Main ); let pp = self.surface( focus ).to_physical( position.0, position.1 ); + if self.app.claims_raw_touch() + { + self.pointer_pos = pp; + { + let ss = self.surface_mut( focus ); + ss.touch_slots.insert( id, pp ); + } + self.app.on_touch_move( id as i64, pp.x, pp.y ); + return; + } + let is_primary = { let ss = self.surface( focus ); @@ -296,7 +334,8 @@ impl TouchHandler for AppData impl AppData { /// Drop every in-flight touch gesture across all surfaces, notifying - /// the app of any auxiliary slots that were still down. Shared by the + /// the app of any slot still down that it was tracking raw (auxiliary + /// fingers, or every finger under `claims_raw_touch`). Shared by the /// `wl_touch.cancel` handler and the touch-capability add / remove path /// (suspend / resume on devices that power the touchscreen down): a /// yanked capability never delivers the pending `up` / `cancel`, so