From 03cbb2ee3a9da931bb9a39eb917674297a0b9318 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Thu, 19 Jun 2025 23:03:51 -0400 Subject: [PATCH] server: scale tablet_tool motion events Also add missing Distance event handler. Closes #179, closes #123 --- src/server/clientside.rs | 4 +++ src/server/event.rs | 64 +++++++++++++++++++++++++++---------- src/server/tests.rs | 69 ++++++++++++++++++++++++++++++++++++++++ testwl/src/lib.rs | 21 +++++++++++- 4 files changed, 141 insertions(+), 17 deletions(-) diff --git a/src/server/clientside.rs b/src/server/clientside.rs index 5a66203..79ba099 100644 --- a/src/server/clientside.rs +++ b/src/server/clientside.rs @@ -264,6 +264,10 @@ where } } + pub fn get(&self) -> Entity { + self.key.get().copied().expect("Object key is not set") + } + fn new() -> Self { Self { key: OnceLock::new(), diff --git a/src/server/event.rs b/src/server/event.rs index de88c4b..eaa1950 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -19,7 +19,8 @@ use wayland_protocols::{ tablet::zv2::{ client::{ zwp_tablet_pad_group_v2, zwp_tablet_pad_ring_v2, zwp_tablet_pad_strip_v2, - zwp_tablet_pad_v2, zwp_tablet_seat_v2, zwp_tablet_tool_v2, zwp_tablet_v2, + zwp_tablet_pad_v2, zwp_tablet_seat_v2, zwp_tablet_tool_v2, + zwp_tablet_v2::{self, ZwpTabletV2 as TabletClient}, }, server::{ zwp_tablet_pad_group_v2::ZwpTabletPadGroupV2 as TabletPadGroupServer, @@ -739,7 +740,10 @@ impl Event for client::wl_touch::Event { cmd.run_on(&mut state.world); } Self::Motion { time, id, x, y } => { - let (touch, scale) = state.world.query_one_mut::<(&WlTouch, &SurfaceScaleFactor)>(target).unwrap(); + let (touch, scale) = state + .world + .query_one_mut::<(&WlTouch, &SurfaceScaleFactor)>(target) + .unwrap(); touch.motion(time, id, x * scale.0, y * scale.0); } _ => { @@ -1327,27 +1331,55 @@ impl Event for zwp_tablet_pad_v2::Event { impl Event for zwp_tablet_tool_v2::Event { fn handle(self, target: Entity, state: &mut ServerState) { - let tool = state.world.get::<&TabletToolServer>(target).unwrap(); match self { Self::ProximityIn { serial, tablet, surface, } => { - let (e_tab, s_tablet) = from_client::(&tablet, state); - let Some(surface) = surface - .data() - .copied() - .and_then(|key| state.world.get::<&WlSurface>(key).ok()) - else { - return; - }; - tool.proximity_in(serial, &s_tablet, &surface); - drop(tool); - drop(surface); - state.world.spawn_at(e_tab, (tablet, s_tablet)); + let mut cmd = CommandBuffer::new(); + { + let Some(mut query) = surface.data().copied().and_then(|key| { + state + .world + .query_one::<(&WlSurface, &SurfaceScaleFactor)>(key) + .ok() + }) else { + warn!("tablet tool proximity_in failed: stale surface"); + return; + }; + let (surface, scale) = query.get().unwrap(); + cmd.insert(target, (*scale,)); + + let Some(s_tablet) = + tablet + .data() + .and_then(|key: &LateInitObjectKey| { + state.world.get::<&TabletServer>(key.get()).ok() + }) + else { + warn!("tablet tool proximity_in failed: stale tablet"); + return; + }; + + state + .world + .get::<&TabletToolServer>(target) + .unwrap() + .proximity_in(serial, &s_tablet, surface); + } + cmd.run_on(&mut state.world); + } + Self::Motion { x, y } => { + let (tool, scale) = state + .world + .query_one_mut::<(&TabletToolServer, Option<&SurfaceScaleFactor>)>(target) + .unwrap(); + let scale = scale.map(|s| s.0).unwrap_or(1.0); + tool.motion(x * scale, y * scale); } _ => { + let tool = state.world.get::<&TabletToolServer>(target).unwrap(); simple_event_shunt! { tool, self => [ Type { |tool_type| convert_wenum(tool_type) }, @@ -1359,7 +1391,7 @@ impl Event for zwp_tablet_tool_v2::Event { ProximityOut, Down { serial }, Up, - Motion { x, y }, + Distance { distance }, Pressure { pressure }, Tilt { tilt_x, tilt_y }, Rotation { degrees }, diff --git a/src/server/tests.rs b/src/server/tests.rs index d5c5927..45e7b78 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -2290,6 +2290,75 @@ fn touch_fractional_scale() { assert_eq!(y, 40.0 * 1.5); } +#[test] +fn tablet_tool_fractional_scale() { + let mut f = TestFixture::new_pre_connect(|testwl| { + testwl.enable_fractional_scale(); + }); + let comp = f.compositor(); + let (_, output) = f.new_output(0, 0); + let toplevel = unsafe { Window::new(1) }; + let (_, id) = f.create_toplevel(&comp, toplevel); + let surface_data = f.testwl.get_surface_data(id).unwrap(); + let fractional = surface_data.fractional.as_ref().cloned().unwrap(); + let server_surface = surface_data.surface.clone(); + f.testwl.move_surface_to_output(id, &output); + + let seat = TestObject::::from_request( + &comp.tablet_man.obj, + zwp_tablet_manager_v2::Request::GetTabletSeat { + seat: comp.seat.obj, + }, + ); + // Not sure why exactly this requires 4 runs but it works so idk + for _ in 0..4 { + f.run(); + } + + let mut tool = None; + for event in seat.data.events.lock().unwrap().drain(..) { + if let zwp_tablet_seat_v2::Event::ToolAdded { id } = event { + tool = Some(id.clone()); + break; + } + } + + let client_tool = tool.expect("Didn't get tool"); + let client_data = f.object_data(&client_tool); + let server_tool = f.testwl.tablet_tool().clone(); + let tablet = f.testwl.tablet().clone(); + + let get_motion = || { + let events = &mut *client_data.events.lock().unwrap(); + let event = events.pop(); + let Some(zwp_tablet_tool_v2::Event::Motion { x, y }) = event else { + panic!("Didn't get motion event: {event:?}"); + }; + (x, y) + }; + + server_tool.proximity_in(0, &tablet, &server_surface); + server_tool.motion(20.0, 40.0); + f.testwl.dispatch(); + f.run(); + f.run(); + + let (x, y) = get_motion(); + assert_eq!(x, 20.0); + assert_eq!(y, 40.0); + + fractional.preferred_scale(180); // 1.5 scale + server_tool.proximity_in(0, &tablet, &server_surface); + server_tool.motion(20.0, 40.0); + f.testwl.dispatch(); + f.run(); + f.run(); + + let (x, y) = get_motion(); + assert_eq!(x, 20.0 * 1.5); + assert_eq!(y, 40.0 * 1.5); +} + /// See Pointer::handle_event for an explanation. #[test] fn popup_pointer_motion_workaround() {} diff --git a/testwl/src/lib.rs b/testwl/src/lib.rs index d92cb11..d00f279 100644 --- a/testwl/src/lib.rs +++ b/testwl/src/lib.rs @@ -227,6 +227,8 @@ struct State { pointer: Option, keyboard: Option, touch: Option, + tablet: Option, + tablet_tool: Option, configure_serial: u32, selection: Option, data_device_man: Option, @@ -251,6 +253,8 @@ impl Default for State { pointer: None, keyboard: None, touch: None, + tablet: None, + tablet_tool: None, configure_serial: 0, selection: None, data_device_man: None, @@ -760,6 +764,19 @@ impl Server { pub fn touch(&mut self) -> &WlTouch { self.state.touch.as_ref().expect("No touch object created") } + + #[track_caller] + pub fn tablet_tool(&mut self) -> &ZwpTabletToolV2 { + self.state + .tablet_tool + .as_ref() + .expect("No tablet tool created") + } + + #[track_caller] + pub fn tablet(&mut self) -> &ZwpTabletV2 { + self.state.tablet.as_ref().expect("No tablet created") + } } #[derive(Clone, Eq, PartialEq, Debug)] @@ -779,7 +796,7 @@ simple_global_dispatch!(WpFractionalScaleManagerV1); impl Dispatch for State { fn request( - _: &mut Self, + state: &mut Self, client: &Client, _: &ZwpTabletManagerV2, request: ::Request, @@ -794,11 +811,13 @@ impl Dispatch for State { seat.tablet_added(&tablet); tablet.name("tabby".to_owned()); tablet.done(); + state.tablet = Some(tablet); let tool = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); seat.tool_added(&tool); tool._type(zwp_tablet_tool_v2::Type::Finger); tool.done(); + state.tablet_tool = Some(tool); let pad = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap(); let group = client.create_resource::<_, _, State>(dhandle, 1, ()).unwrap();