winit-wayland: add pointer gesture hold

This commit is contained in:
Martin Marmsoler
2026-06-12 19:36:23 +02:00
committed by GitHub
parent 81b2729765
commit 850d5f59a7
6 changed files with 93 additions and 16 deletions

View File

@@ -269,6 +269,20 @@ pub enum WindowEvent {
button: ButtonSource, button: ButtonSource,
}, },
/// Multi-finger hold gesture on the touchpad or touchscreen without movement.
///
/// The `phase` field indicates the lifecycle of the hold gesture:
/// - `Started`: One or more fingers are in contact with the touchpad/touchscreen.
/// - `Ended`: All fingers have been lifted from the touchpad/touchscreen.
/// - `Cancelled`: The hold gesture was interrupted, for example when another finger touches the
/// touchpad (causing a new `Started` event with more fingers), or when movement begins and
/// transitions to other gestures like pinch, pan, or rotation.
///
/// ## Platform-specific
///
/// - Only available on **Wayland**.
HoldGesture { device_id: Option<DeviceId>, phase: TouchPhase },
/// Two-finger pinch gesture, often used for magnification. /// Two-finger pinch gesture, often used for magnification.
/// ///
/// ## Platform-specific /// ## Platform-specific
@@ -1006,9 +1020,17 @@ pub enum Ime {
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TouchPhase { pub enum TouchPhase {
/// Initial touch contact or gesture start, for example when one or more fingers touch the
/// screen or touchpad.
Started, Started,
/// The touch contact point changed, for example without lifting the finger.
Moved, Moved,
/// All touch contact points have been lifted from the touchscreen or touchpad.
///
/// This event is important as it should clear any state or event in flight that was
/// generated by the preceding `Started` and `Moved` events.
Ended, Ended,
/// The event was cancelled and should cancel any event in flight and clear state.
Cancelled, Cancelled,
} }

View File

@@ -40,7 +40,7 @@ sctk = { package = "smithay-client-toolkit", version = "0.20.0", default-feature
sctk-adwaita = { version = "0.11.0", default-features = false, optional = true } sctk-adwaita = { version = "0.11.0", default-features = false, optional = true }
wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] } wayland-backend = { version = "0.3.10", default-features = false, features = ["client_system"] }
wayland-client = "0.31.10" wayland-client = "0.31.10"
wayland-protocols = { version = "0.32.8", features = ["staging"] } wayland-protocols = { version = "0.32.11", features = ["staging", "unstable"] }
wayland-protocols-plasma = { version = "0.3.8", features = ["client"] } wayland-protocols-plasma = { version = "0.3.8", features = ["client"] }
winit-common = { workspace = true, features = ["xkb", "wayland"] } winit-common = { workspace = true, features = ["xkb", "wayland"] }

View File

@@ -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::pointer::{ThemeSpec, ThemedPointer};
use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState}; use sctk::seat::{Capability as SeatCapability, SeatHandler, SeatState};
use tracing::warn; 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::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1;
use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2; use wayland_protocols::wp::tablet::zv2::client::zwp_tablet_seat_v2::ZwpTabletSeatV2;
use winit_core::event::WindowEvent; use winit_core::event::WindowEvent;
@@ -60,6 +61,9 @@ pub struct WinitSeatState {
/// The pinch pointer gesture bound on the seat. /// The pinch pointer gesture bound on the seat.
pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>, pointer_gesture_pinch: Option<ZwpPointerGesturePinchV1>,
/// The hold pointer gesture bound on the seat.
pointer_gesture_hold: Option<ZwpPointerGestureHoldV1>,
/// The keyboard bound on the seat. /// The keyboard bound on the seat.
keyboard_state: Option<KeyboardState>, keyboard_state: Option<KeyboardState>,
@@ -141,6 +145,14 @@ impl SeatHandler for WinitState {
) )
}); });
seat_state.pointer_gesture_hold = self.pointer_gestures.as_ref().map(|manager| {
manager.get_hold_gesture(
themed_pointer.pointer(),
queue_handle,
PointerGestureData::default(),
)
});
let themed_pointer = Arc::new(themed_pointer); let themed_pointer = Arc::new(themed_pointer);
// Register cursor surface. // Register cursor surface.
@@ -209,6 +221,10 @@ impl SeatHandler for WinitState {
pointer_gesture_pinch.destroy(); 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() { if let Some(pointer) = seat_state.pointer.take() {
let pointer_data = pointer.pointer().winit_data(); let pointer_data = pointer.pointer().winit_data();

View File

@@ -7,9 +7,12 @@ use sctk::globals::GlobalData;
use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::client::globals::{BindError, GlobalList};
use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch}; use sctk::reexports::client::{Connection, Dispatch, Proxy, QueueHandle, delegate_dispatch};
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{ use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::{
Event, ZwpPointerGesturePinchV1, Event as PinchEvent, ZwpPointerGesturePinchV1,
}; };
use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1; use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gestures_v1::ZwpPointerGesturesV1;
use wayland_protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_hold_v1::{
Event as HoldEvent, ZwpPointerGestureHoldV1,
};
use winit_core::event::{TouchPhase, WindowEvent}; use winit_core::event::{TouchPhase, WindowEvent};
use winit_core::window::WindowId; use winit_core::window::WindowId;
@@ -27,7 +30,7 @@ impl PointerGesturesState {
globals: &GlobalList, globals: &GlobalList,
queue_handle: &QueueHandle<WinitState>, queue_handle: &QueueHandle<WinitState>,
) -> Result<Self, BindError> { ) -> Result<Self, BindError> {
let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?; let pointer_gestures = globals.bind(queue_handle, 3..=3, GlobalData)?;
Ok(Self { pointer_gestures }) Ok(Self { pointer_gestures })
} }
} }
@@ -70,6 +73,49 @@ impl Dispatch<ZwpPointerGesturesV1, GlobalData, WinitState> for PointerGesturesS
} }
} }
impl Dispatch<ZwpPointerGestureHoldV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event(
state: &mut WinitState,
_proxy: &ZwpPointerGestureHoldV1,
event: <ZwpPointerGestureHoldV1 as wayland_client::Proxy>::Event,
data: &PointerGestureData,
_conn: &Connection,
_qhandle: &QueueHandle<WinitState>,
) {
let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase) = match event {
HoldEvent::Begin { surface, fingers, .. } => {
if fingers < 2 {
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)
},
_ => return,
};
state
.events_sink
.push_window_event(WindowEvent::HoldGesture { device_id: None, phase }, window_id);
}
}
impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState { impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for PointerGesturesState {
fn event( fn event(
state: &mut WinitState, state: &mut WinitState,
@@ -81,7 +127,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
) { ) {
let mut pointer_gesture_data = data.inner.lock().unwrap(); let mut pointer_gesture_data = data.inner.lock().unwrap();
let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event { let (window_id, phase, pan_delta, pinch_delta, rotation_delta) = match event {
Event::Begin { surface, fingers, .. } => { PinchEvent::Begin { surface, fingers, .. } => {
// We only support two fingers for now. // We only support two fingers for now.
if fingers != 2 { if fingers != 2 {
return; return;
@@ -100,7 +146,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
(window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.) (window_id, TouchPhase::Started, PhysicalPosition::new(0., 0.), 0., 0.)
}, },
Event::Update { dx, dy, scale: pinch, rotation, .. } => { PinchEvent::Update { dx, dy, scale: pinch, rotation, .. } => {
let window_id = match pointer_gesture_data.window_id { let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id, Some(window_id) => window_id,
_ => return, _ => return,
@@ -121,7 +167,7 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
let rotation_delta = -rotation as f32; let rotation_delta = -rotation as f32;
(window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta) (window_id, TouchPhase::Moved, pan_delta, pinch_delta, rotation_delta)
}, },
Event::End { cancelled, .. } => { PinchEvent::End { cancelled, .. } => {
let window_id = match pointer_gesture_data.window_id { let window_id = match pointer_gesture_data.window_id {
Some(window_id) => window_id, Some(window_id) => window_id,
_ => return, _ => return,
@@ -155,3 +201,4 @@ impl Dispatch<ZwpPointerGesturePinchV1, PointerGestureData, WinitState> for Poin
delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState); delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState); delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: PointerGestureData] => PointerGesturesState);
delegate_dispatch!(WinitState: [ZwpPointerGestureHoldV1: PointerGestureData] => PointerGesturesState);

View File

@@ -541,16 +541,7 @@ impl ApplicationHandler for Application {
WindowEvent::DoubleTapGesture { .. } => { WindowEvent::DoubleTapGesture { .. } => {
info!("Smart zoom"); info!("Smart zoom");
}, },
WindowEvent::TouchpadPressure { .. } _ => (),
| WindowEvent::DragLeft { .. }
| WindowEvent::KeyboardInput { .. }
| WindowEvent::PointerEntered { .. }
| WindowEvent::DragEntered { .. }
| WindowEvent::DragMoved { .. }
| WindowEvent::DragDropped { .. }
| WindowEvent::Destroyed
| WindowEvent::Ime(_)
| WindowEvent::Moved(_) => (),
} }
} }

View File

@@ -48,6 +48,7 @@ changelog entry.
- Implement `Send` and `Sync` for `OwnedDisplayHandle`. - Implement `Send` and `Sync` for `OwnedDisplayHandle`.
- Use new macOS 15 cursors for resize icons. - Use new macOS 15 cursors for resize icons.
- On Android, added scancode conversions for more obscure key codes. - On Android, added scancode conversions for more obscure key codes.
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
- On Wayland, added ext-background-effect-v1 support. - On Wayland, added ext-background-effect-v1 support.
### Changed ### Changed