diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index e6500c2133..1a2ba0dec6 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -298,6 +298,21 @@ pub enum WindowEvent { phase: TouchPhase, }, + /// Multi-finger hold gesture. + /// + /// Sent when fingers are placed on a touchpad without significant movement + /// (`Started`) and again when they lift or the gesture is cancelled + /// (`Ended` / `Cancelled`). The canonical use is to stop kinetic + /// ("momentum") scrolling. + /// + /// ## Platform-specific + /// + /// - Only available on **Wayland** (via `zwp_pointer_gestures_v1`, v3+). + HoldGesture { + device_id: Option, + phase: TouchPhase, + }, + /// Double tap gesture. /// /// On a Mac, smart magnification is triggered by a double tap with two fingers diff --git a/winit-wayland/src/seat/mod.rs b/winit-wayland/src/seat/mod.rs index ddf0e061df..df2a51b2d7 100644 --- a/winit-wayland/src/seat/mod.rs +++ b/winit-wayland/src/seat/mod.rs @@ -12,6 +12,7 @@ use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3:: use sctk::seat::pointer::{ThemeSpec, ThemedPointer}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use tracing::warn; +use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1; use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1; use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2; use winit_core::event::WindowEvent; @@ -60,6 +61,9 @@ pub struct WinitSeatState { /// The pinch pointer gesture bound on the seat. pointer_gesture_pinch: Option, + /// The hold pointer gesture bound on the seat (v3+ compositors only). + pointer_gesture_hold: Option, + /// The keyboard bound on the seat. keyboard_state: Option, @@ -141,6 +145,19 @@ impl SeatHandler for WinitState { ) }); + // The hold gesture is only available from v3 of the manager. + seat_state.pointer_gesture_hold = self + .pointer_gestures + .as_ref() + .filter(|manager| manager.version() >= 3) + .map(|manager| { + manager.get_hold_gesture( + themed_pointer.pointer(), + queue_handle, + PointerGestureData::default(), + ) + }); + let themed_pointer = Arc::new(themed_pointer); // Register cursor surface. @@ -209,6 +226,10 @@ impl SeatHandler for WinitState { pointer_gesture_pinch.destroy(); } + if let Some(pointer_gesture_hold) = seat_state.pointer_gesture_hold.take() { + pointer_gesture_hold.destroy(); + } + if let Some(pointer) = seat_state.pointer.take() { let pointer_data = pointer.pointer().winit_data(); diff --git a/winit-wayland/src/seat/pointer/pointer_gesture.rs b/winit-wayland/src/seat/pointer/pointer_gesture.rs index 87b97d5d0c..a9d43ac15c 100644 --- a/winit-wayland/src/seat/pointer/pointer_gesture.rs +++ b/winit-wayland/src/seat/pointer/pointer_gesture.rs @@ -6,6 +6,9 @@ use sctk::compositor::SurfaceData; use sctk::globals::GlobalData; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch}; +use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::{ + Event as HoldEvent, ZwpPointerGestureHoldV1, +}; use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{ Event, ZwpPointerGesturePinchV1, }; @@ -27,7 +30,9 @@ impl PointerGesturesState { globals: &GlobalList, queue_handle: &QueueHandle, ) -> Result { - let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?; + // Bind up to v3 so the hold gesture (added in v3) is available where the + // compositor supports it; older compositors bind lower and keep pinch. + let pointer_gestures = globals.bind(queue_handle, 1..=3, GlobalData)?; Ok(Self { pointer_gestures }) } } @@ -153,5 +158,50 @@ impl Dispatch for Poin } } +impl Dispatch for PointerGesturesState { + fn event( + state: &mut WinitState, + _proxy: &ZwpPointerGestureHoldV1, + event: ::Event, + data: &PointerGestureData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + let mut pointer_gesture_data = data.inner.lock().unwrap(); + let (window_id, phase) = match event { + HoldEvent::Begin { surface, .. } => { + // Don't handle events from a subsurface. + if surface.data::().is_none_or(|data| data.parent_surface().is_some()) + { + return; + } + + let window_id = crate::make_wid(&surface); + pointer_gesture_data.window_id = Some(window_id); + + (window_id, TouchPhase::Started) + }, + HoldEvent::End { cancelled, .. } => { + let window_id = match pointer_gesture_data.window_id { + Some(window_id) => window_id, + _ => return, + }; + + // Reset the state. + *pointer_gesture_data = Default::default(); + + let phase = if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }; + (window_id, phase) + }, + _ => unreachable!("Unknown event {event:?}"), + }; + + state + .events_sink + .push_window_event(WindowEvent::HoldGesture { device_id: None, phase }, window_id); + } +} + delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState); delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState); +delegate_dispatch!(WinitState: [ZwpPointerGestureHoldV1: PointerGestureData] => PointerGesturesState); diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 729bfd639b..5544d2df0c 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -46,6 +46,7 @@ changelog entry. - On iOS, add Apple Pencil support with force, altitude, and azimuth data. - On Redox, add support for missing keyboard scancodes. - Implement `Send` and `Sync` for `OwnedDisplayHandle`. +- Add `WindowEvent::HoldGesture`, implemented on Wayland. - Use new macOS 15 cursors for resize icons. - On Android, added scancode conversions for more obscure key codes. - On Wayland, added ext-background-effect-v1 support.