GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat: implement native Wayland touchpad pinch gestures using zwp_pointer_gestures_v1
Cargo.toml | 1 +
src/backend/window_runner.rs | 85 +++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index bfba6ce..fa88b2f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ smithay-client-toolkit = { version = "0.19.2", features = ["calloop"] }
calloop = "0.13.0"
calloop-wayland-source = "0.3.0"
wayland-client = { version = "0.31", features = ["system"] }
+wayland-protocols = { version = "0.31", features = ["client", "unstable"] }
wayland-backend = "0.3"
wayland-scanner = "0.31"
bitflags = "2"
diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 7e92374..56604e2 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -24,6 +24,11 @@ use wayland_client::{
protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface, wl_registry, wl_region, wl_callback},
Connection, QueueHandle, Proxy,
};
+
+use wayland_protocols::wp::pointer_gestures::zv1::client::{
+ zwp_pointer_gesture_pinch_v1::{self, ZwpPointerGesturePinchV1},
+ zwp_pointer_gestures_v1::{self as zwp_pointer_gestures, ZwpPointerGesturesV1},
+};
use smithay_client_toolkit::shell::xdg::popup::{Popup, PopupHandler, PopupConfigure};
use smithay_client_toolkit::shell::xdg::{XdgPositioner, XdgSurface};
use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity, ConstraintAdjustment};
@@ -1409,6 +1414,10 @@ pub struct EngineState<A: Application> {
pub current_cursor_icon: Option<CursorIcon>,
pub qh: QueueHandle<EngineState<A>>,
pub just_configured: bool,
+ pub pointer_gestures: Option<ZwpPointerGesturesV1>,
+ pub pinch_gesture: Option<ZwpPointerGesturePinchV1>,
+ pub last_pinch_scale: f32,
+ pub cursor_pos: (f32, f32),
}
impl<A: Application> EngineState<A> {
@@ -1972,7 +1981,7 @@ impl<A: Application> SeatHandler for EngineState<A> {
capability: Capability,
) {
if capability == Capability::Pointer && self.pointer.is_none() {
- let surface = self.compositor_state.create_surface(qh);
+ let surface = self.compositor_state.create_surface::<Self>(qh);
let themed_pointer = self.seat_state.get_pointer_with_theme(
qh,
&seat,
@@ -1980,6 +1989,9 @@ impl<A: Application> SeatHandler for EngineState<A> {
surface,
ThemeSpec::System,
).unwrap();
+ if let Some(ref pg) = self.pointer_gestures {
+ self.pinch_gesture = Some(pg.get_pinch_gesture(themed_pointer.pointer(), qh, ()));
+ }
self.pointer = Some(themed_pointer);
}
if capability == Capability::Keyboard && self.keyboard.is_none() {
@@ -1996,6 +2008,7 @@ impl<A: Application> SeatHandler for EngineState<A> {
capability: Capability,
) {
if capability == Capability::Pointer {
+ self.pinch_gesture = None;
self.pointer = None;
}
if capability == Capability::Keyboard {
@@ -2036,6 +2049,8 @@ impl<A: Application> PointerHandler for EngineState<A> {
}
}
+ self.cursor_pos = (lx, ly);
+
match &event.kind {
PointerEventKind::Enter { .. } => {
let is_status_bar = self.inner.settings().app_id.starts_with("cce-status");
@@ -2512,6 +2527,68 @@ impl<A: Application> wayland_client::Dispatch<wl_callback::WlCallback, ()> for E
}
}
+impl<A: Application> wayland_client::Dispatch<ZwpPointerGesturesV1, ()> for EngineState<A> {
+ fn event(
+ _state: &mut Self,
+ _proxy: &ZwpPointerGesturesV1,
+ _event: zwp_pointer_gestures::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {}
+}
+
+impl<A: Application> wayland_client::Dispatch<ZwpPointerGesturePinchV1, ()> for EngineState<A> {
+ fn event(
+ state: &mut Self,
+ _proxy: &ZwpPointerGesturePinchV1,
+ event: zwp_pointer_gesture_pinch_v1::Event,
+ _data: &(),
+ _conn: &Connection,
+ _qh: &QueueHandle<Self>,
+ ) {
+ match event {
+ zwp_pointer_gesture_pinch_v1::Event::Begin { .. } => {
+ state.last_pinch_scale = 1.0;
+ }
+ zwp_pointer_gesture_pinch_v1::Event::Update { scale, .. } => {
+ let scale_f32 = scale as f32;
+ let factor = scale_f32 / state.last_pinch_scale;
+ state.last_pinch_scale = scale_f32;
+
+ let (px, py) = state.cursor_pos;
+ let mut rebuild = false;
+
+ // Calculate the y_delta for PixelDelta mapping.
+ // Since cce-graph interprets factor = 1.0 + y_delta * 0.015, we reverse it:
+ let y_delta = (factor - 1.0) / 0.015;
+ let delta = MouseScrollDelta::PixelDelta(Position {
+ x: 0.0,
+ y: y_delta as f64,
+ });
+
+ if let Some(ctx) = state.inner.ui_context_mut() {
+ ctx.ctrl_pressed = true; // Force ctrl_pressed = true for the pinch event
+ }
+
+ state.inner.handle_mouse_wheel(&delta, LogicalPosition::new(px, py), &mut rebuild);
+
+ if let Some(ctx) = state.inner.ui_context_mut() {
+ ctx.ctrl_pressed = state.ctrl_pressed; // Restore original state
+ }
+
+ if rebuild {
+ state.redraw = true;
+ }
+ }
+ zwp_pointer_gesture_pinch_v1::Event::End { .. } => {
+ state.last_pinch_scale = 1.0;
+ }
+ _ => {}
+ }
+ }
+}
+
pub fn run<A: Application>() {
let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
@@ -2529,6 +2606,8 @@ pub fn run<A: Application>() {
let settings = inner.settings();
crate::scale::set_app_id(settings.app_id.clone());
+ let pointer_gestures: Option<ZwpPointerGesturesV1> = globals.bind(&qh, 1..=3, ()).ok();
+
let mut engine_state = EngineState {
registry_state: RegistryState::new(&globals),
compositor_state,
@@ -2565,6 +2644,10 @@ pub fn run<A: Application>() {
current_cursor_icon: None,
qh: qh.clone(),
just_configured: false,
+ pointer_gestures,
+ pinch_gesture: None,
+ last_pinch_scale: 1.0,
+ cursor_pos: (0.0, 0.0),
};
event_queue.roundtrip(&mut engine_state).unwrap();