From fb590c1645035cc2537f826f025ac7ea12f5e514 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Fri, 26 Jun 2026 11:10:05 +0300 Subject: [PATCH] wayland: one-finger touch drag scrolls the viewport Signed-off-by: NotAShelf Change-Id: I58396f87fbe47cb3a552bf53d45e7e836a6a6964 --- src/wayland/handlers.rs | 103 ++++++++++++++++++++++++++++++++++++++++ src/wayland/mod.rs | 21 +++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/src/wayland/handlers.rs b/src/wayland/handlers.rs index 4ca5233..e1c93c9 100644 --- a/src/wayland/handlers.rs +++ b/src/wayland/handlers.rs @@ -141,6 +141,12 @@ impl SeatHandler for App { Err(err) => tracing::warn!("get pointer: {err}"), } } + if capability == Capability::Touch && self.seats[i].touch.is_none() { + match self.seat_state.get_touch(qh, &seat) { + Ok(touch) => self.seats[i].touch = Some(touch), + Err(err) => tracing::warn!("get touch: {err}"), + } + } } fn remove_capability( @@ -165,6 +171,12 @@ impl SeatHandler for App { pointer.release(); } } + Capability::Touch => { + if let Some(touch) = s.touch.take() { + touch.release(); + } + self.touch_scroll = None; + } _ => {} } } @@ -437,6 +449,96 @@ impl PointerHandler for App { } } +// Touch drives one-finger drag-to-scroll of the scrollback viewport. Taps and +// multi-finger gestures are ignored; only the first touch point is tracked. +impl TouchHandler for App { + fn down( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_touch::WlTouch, + _serial: u32, + _time: u32, + _surface: wl_surface::WlSurface, + id: i32, + position: (f64, f64), + ) { + if self.touch_scroll.is_none() { + self.touch_scroll = Some(TouchScroll { + id, + last_y: position.1, + acc: 0.0, + }); + } + } + + fn up( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_touch::WlTouch, + _serial: u32, + _time: u32, + id: i32, + ) { + if self.touch_scroll.as_ref().is_some_and(|t| t.id == id) { + self.touch_scroll = None; + } + } + + fn motion( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_touch::WlTouch, + _time: u32, + id: i32, + position: (f64, f64), + ) { + let cell_h = self.renderer.metrics().height as f64; + let Some(touch) = self.touch_scroll.as_mut().filter(|t| t.id == id) else { + return; + }; + touch.acc += position.1 - touch.last_y; + touch.last_y = position.1; + // Dragging down (positive delta) reveals older lines, matching the + // viewport's "positive scrolls back" convention. + let lines = (touch.acc / cell_h) as isize; + if lines != 0 { + touch.acc -= lines as f64 * cell_h; + if let Some(session) = self.session.as_mut() { + session.term.scroll_view(lines); + self.needs_draw = true; + } + } + } + + fn shape( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_touch::WlTouch, + _: i32, + _: f64, + _: f64, + ) { + } + + fn orientation( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &wl_touch::WlTouch, + _: i32, + _: f64, + ) { + } + + fn cancel(&mut self, _: &Connection, _: &QueueHandle, _: &wl_touch::WlTouch) { + self.touch_scroll = None; + } +} + impl OutputHandler for App { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state @@ -757,6 +859,7 @@ delegate_shm!(App); delegate_seat!(App); delegate_keyboard!(App); delegate_pointer!(App); +delegate_touch!(App); delegate_xdg_shell!(App); delegate_xdg_window!(App); delegate_data_device!(App); diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index a20c7e7..446eb46 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -46,7 +46,7 @@ use smithay_client_toolkit::{ delegate_activation, delegate_compositor, delegate_data_device, delegate_keyboard, delegate_output, delegate_pointer, delegate_primary_selection, delegate_registry, delegate_seat, delegate_shm, - delegate_xdg_shell, delegate_xdg_window, + delegate_touch, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, primary_selection::{ PrimarySelectionManagerState, @@ -62,6 +62,7 @@ use smithay_client_toolkit::{ BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, PointerEvent, PointerEventKind, PointerHandler, cursor_shape::CursorShapeManager, }, + touch::TouchHandler, }, shell::{ WaylandSurface, @@ -81,7 +82,7 @@ use wayland_client::{ protocol::{ wl_data_device::WlDataDevice, wl_data_device_manager::DndAction, wl_data_source::WlDataSource, wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, - wl_surface, + wl_surface, wl_touch, }, }; use wayland_protocols::wp::content_type::v1::client::{ @@ -289,6 +290,7 @@ pub fn run(config: Config, config_path: Option) -> anyhow::R url_input: String::new(), unicode_input: None, keys_down: std::collections::HashSet::new(), + touch_scroll: None, focused: true, exit: false, exit_code: ExitCode::SUCCESS, @@ -394,6 +396,18 @@ struct SeatData { primary_device: Option, /// text-input-v3 handle for IME preedit/commit, if the compositor offers it. text_input: Option, + touch: Option, +} + +/// A single-finger touch drag in progress, used to scroll the viewport. +#[derive(Debug)] +struct TouchScroll { + /// Touch point id we are tracking (the first finger down). + id: i32, + /// Surface-local y of the last motion, to take per-event deltas. + last_y: f64, + /// Sub-cell pixel remainder carried between motions. + acc: f64, } /// Window + Wayland client state shared across all protocol handlers. @@ -514,6 +528,8 @@ struct App { /// Raw key codes currently held, to tell press from repeat for the kitty /// keyboard protocol's event-type reporting. keys_down: std::collections::HashSet, + /// A single-finger touch drag in progress (scrolls the viewport). + touch_scroll: Option, /// Whether the toplevel currently has keyboard focus (drives the cursor). focused: bool, exit: bool, @@ -1205,6 +1221,7 @@ impl App { data_device: None, primary_device: None, text_input: None, + touch: None, }); self.seats.len() - 1 }