From 4e04741a359183cbed53db9a531ff0f79ab07b75 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 27 Apr 2024 13:32:05 +0200 Subject: [PATCH 1/2] wayland: Add support for PinchGesture and RotationGesture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These two events are synthesized from the same event from the same protocol, zwp_pointer_gestures_v1, and will always come together. I’ve left over the third value, which is a dx/dy from the center of the gesture, it could be exposed as a 2D scroll but I feel like this should be a different event than the normal AxisMotion. The documentation doesn’t indicate the unit of the rotation, so I’ve left it as degrees. I don’t have any Apple OS where I could test whether that event is also in degrees or in radians there. The other two gestures supported by this protocol, a multi-finger swipe, and a multi-finger hold, aren’t currently matched with any event in winit, would it make sense to expose them? I’ve only tested on GNOME, but I expect other compositors to expose the same protocol eventually. --- src/changelog/unreleased.md | 4 + src/event.rs | 4 +- src/platform_impl/linux/wayland/seat/mod.rs | 16 +++- .../linux/wayland/seat/pointer/mod.rs | 88 +++++++++++++++++++ src/platform_impl/linux/wayland/state.rs | 17 +++- 5 files changed, 124 insertions(+), 5 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index f3a0f6d2c3..4bc0eb2ef0 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -39,3 +39,7 @@ The migration guide could reference other migration examples in the current changelog entry. ## Unreleased + +### Added + +- On Wayland, add `PinchGesture` and `RotationGesture` diff --git a/src/event.rs b/src/event.rs index 5cd3877a26..d630c346ae 100644 --- a/src/event.rs +++ b/src/event.rs @@ -281,7 +281,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **macOS** and **iOS**. + /// - Only available on **macOS**, **iOS** and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. PinchGesture { device_id: DeviceId, @@ -333,7 +333,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **macOS** and **iOS**. + /// - Only available on **macOS**, **iOS** and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. RotationGesture { device_id: DeviceId, diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 103cf6fce0..6c69926afe 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -8,6 +8,7 @@ use sctk::reexports::client::backend::ObjectId; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::protocol::wl_touch::WlTouch; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; +use sctk::reexports::protocols::wp::pointer_gestures::zv1::client::zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1; use sctk::reexports::protocols::wp::relative_pointer::zv1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1; use sctk::reexports::protocols::wp::text_input::zv3::client::zwp_text_input_v3::ZwpTextInputV3; @@ -24,7 +25,9 @@ mod text_input; mod touch; pub use pointer::relative_pointer::RelativePointerState; -pub use pointer::{PointerConstraintsState, WinitPointerData, WinitPointerDataExt}; +pub use pointer::{ + PointerConstraintsState, PointerGesturesState, WinitPointerData, WinitPointerDataExt, +}; pub use text_input::{TextInputState, ZwpTextInputV3Ext}; use keyboard::{KeyboardData, KeyboardState}; @@ -48,6 +51,9 @@ pub struct WinitSeatState { /// The relative pointer bound on the seat. relative_pointer: Option, + /// The pinch gesture of the pointer bound on the seat. + pinch_gesture: Option, + /// The keyboard bound on the seat. keyboard_state: Option, @@ -111,6 +117,14 @@ impl SeatHandler for WinitState { ) }); + seat_state.pinch_gesture = self.pointer_gestures.as_ref().map(|gestures| { + gestures.get_pinch_gesture( + themed_pointer.pointer(), + queue_handle, + sctk::globals::GlobalData, + ) + }); + let themed_pointer = Arc::new(themed_pointer); // Register cursor surface. diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index f26c326e9c..35e5166797 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -14,6 +14,8 @@ use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_locked use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1; use sctk::reexports::protocols::wp::cursor_shape::v1::client::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1; use sctk::reexports::protocols::wp::pointer_constraints::zv1::client::zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1}; +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_gesture_pinch_v1::{ZwpPointerGesturePinchV1, Event}; use sctk::reexports::client::globals::{BindError, GlobalList}; use sctk::reexports::csd_frame::FrameClick; @@ -467,9 +469,95 @@ impl Dispatch for SeatState { } } +pub struct PointerGesturesState { + pointer_gestures: ZwpPointerGesturesV1, +} + +impl PointerGesturesState { + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + ) -> Result { + let pointer_gestures = globals.bind(queue_handle, 1..=1, GlobalData)?; + Ok(Self { pointer_gestures }) + } +} + +impl Deref for PointerGesturesState { + type Target = ZwpPointerGesturesV1; + + fn deref(&self) -> &Self::Target { + &self.pointer_gestures + } +} + +impl Dispatch for PointerGesturesState { + fn event( + _state: &mut WinitState, + _proxy: &ZwpPointerGesturesV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + unreachable!("zwp_pointer_gestures_v1 has no events") + } +} + +impl Dispatch for PointerGesturesState { + fn event( + state: &mut WinitState, + _proxy: &ZwpPointerGesturePinchV1, + event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); + let (phase, scale_delta, rotation_delta) = match event { + Event::Begin { time: _, serial: _, surface, fingers } => { + if fingers != 2 { + // We only support two fingers for now. + return; + } + // The parent surface. + let parent_surface = match surface.data::() { + Some(data) => data.parent_surface().unwrap_or(&surface), + None => return, + }; + + state.window_id = wayland::make_wid(parent_surface); + state.previous_scale = 1.; + + (TouchPhase::Started, 0., 0.) + }, + Event::Update { time: _, dx: _, dy: _, scale, rotation } => { + let scale_delta = scale - state.previous_scale; + state.previous_scale = scale; + (TouchPhase::Moved, scale_delta, -rotation as f32) + }, + Event::End { time: _, serial: _, cancelled } => { + state.previous_scale = 1.; + (if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }, 0., 0.) + }, + _ => unreachable!("Unknown event {event:?}"), + }; + state.events_sink.push_window_event( + WindowEvent::PinchGesture { device_id, delta: scale_delta, phase }, + state.window_id, + ); + state.events_sink.push_window_event( + WindowEvent::RotationGesture { device_id, delta: rotation_delta, phase }, + state.window_id, + ); + } +} + delegate_dispatch!(WinitState: [ WlPointer: WinitPointerData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeManagerV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ WpCursorShapeDeviceV1: GlobalData] => SeatState); delegate_dispatch!(WinitState: [ZwpPointerConstraintsV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpLockedPointerV1: GlobalData] => PointerConstraintsState); delegate_dispatch!(WinitState: [ZwpConfinedPointerV1: GlobalData] => PointerConstraintsState); +delegate_dispatch!(WinitState: [ZwpPointerGesturesV1: GlobalData] => PointerGesturesState); +delegate_dispatch!(WinitState: [ZwpPointerGesturePinchV1: GlobalData] => PointerGesturesState); diff --git a/src/platform_impl/linux/wayland/state.rs b/src/platform_impl/linux/wayland/state.rs index 8c021bb9fd..729ef222a4 100644 --- a/src/platform_impl/linux/wayland/state.rs +++ b/src/platform_impl/linux/wayland/state.rs @@ -26,8 +26,8 @@ use sctk::subcompositor::SubcompositorState; use crate::platform_impl::wayland::event_loop::sink::EventSink; use crate::platform_impl::wayland::output::MonitorHandle; use crate::platform_impl::wayland::seat::{ - PointerConstraintsState, RelativePointerState, TextInputState, WinitPointerData, - WinitPointerDataExt, WinitSeatState, + PointerConstraintsState, PointerGesturesState, RelativePointerState, TextInputState, + WinitPointerData, WinitPointerDataExt, WinitSeatState, }; use crate::platform_impl::wayland::types::kwin_blur::KWinBlurManager; use crate::platform_impl::wayland::types::wp_fractional_scaling::FractionalScalingManager; @@ -100,6 +100,15 @@ pub struct WinitState { /// Pointer constraints to handle pointer locking and confining. pub pointer_constraints: Option>, + /// Pointer gestures to handle swipe, pinch and hold. + pub pointer_gestures: Option, + + /// XXX: Is this really meant to stay here? + pub window_id: WindowId, + + /// XXX: Is this really meant to stay here? + pub previous_scale: f64, + /// Viewporter state on the given window. pub viewporter_state: Option, @@ -185,8 +194,12 @@ impl WinitState { pointer_constraints: PointerConstraintsState::new(globals, queue_handle) .map(Arc::new) .ok(), + pointer_gestures: PointerGesturesState::new(globals, queue_handle).ok(), pointer_surfaces: Default::default(), + window_id: WindowId(0), + previous_scale: 1., + monitors: Arc::new(Mutex::new(monitors)), events_sink: EventSink::new(), loop_handle, From 8815ac8fcda305cf0cf69067a12f6971c1ce191d Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 27 Apr 2024 17:30:28 +0200 Subject: [PATCH 2/2] wayland: Also synthesize PanGesture from a pinch This new event is exactly what was missing for the previous commit. --- src/changelog/unreleased.md | 2 +- src/event.rs | 2 +- .../linux/wayland/seat/pointer/mod.rs | 22 ++++++++++++++----- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 4bc0eb2ef0..5bac59f0a3 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -42,4 +42,4 @@ changelog entry. ### Added -- On Wayland, add `PinchGesture` and `RotationGesture` +- On Wayland, add `PinchGesture`, `PanGesture` and `RotationGesture`. diff --git a/src/event.rs b/src/event.rs index d630c346ae..f7b4645f12 100644 --- a/src/event.rs +++ b/src/event.rs @@ -297,7 +297,7 @@ pub enum WindowEvent { /// /// ## Platform-specific /// - /// - Only available on **iOS**. + /// - Only available on **iOS** and **Wayland**. /// - On iOS, not recognized by default. It must be enabled when needed. PanGesture { device_id: DeviceId, diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 35e5166797..983a95e3ff 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -514,7 +514,7 @@ impl Dispatch for PointerGestu _qhandle: &QueueHandle, ) { let device_id = crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(DeviceId)); - let (phase, scale_delta, rotation_delta) = match event { + let (phase, pan_delta, scale_delta, rotation_delta) = match event { Event::Begin { time: _, serial: _, surface, fingers } => { if fingers != 2 { // We only support two fingers for now. @@ -526,22 +526,34 @@ impl Dispatch for PointerGestu None => return, }; + let pan_delta = PhysicalPosition::new(0., 0.); state.window_id = wayland::make_wid(parent_surface); state.previous_scale = 1.; - (TouchPhase::Started, 0., 0.) + (TouchPhase::Started, pan_delta, 0., 0.) }, - Event::Update { time: _, dx: _, dy: _, scale, rotation } => { + Event::Update { time: _, dx, dy, scale, rotation } => { + let pan_delta = PhysicalPosition::new(dx as f32, dy as f32); let scale_delta = scale - state.previous_scale; state.previous_scale = scale; - (TouchPhase::Moved, scale_delta, -rotation as f32) + (TouchPhase::Moved, pan_delta, scale_delta, -rotation as f32) }, Event::End { time: _, serial: _, cancelled } => { + let pan_delta = PhysicalPosition::new(0., 0.); state.previous_scale = 1.; - (if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }, 0., 0.) + ( + if cancelled == 0 { TouchPhase::Ended } else { TouchPhase::Cancelled }, + pan_delta, + 0., + 0., + ) }, _ => unreachable!("Unknown event {event:?}"), }; + state.events_sink.push_window_event( + WindowEvent::PanGesture { device_id, delta: pan_delta, phase }, + state.window_id, + ); state.events_sink.push_window_event( WindowEvent::PinchGesture { device_id, delta: scale_delta, phase }, state.window_id,