From d3a46b7c8a8abd339eca406d4f29cc6093e9a9f2 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Sun, 30 Jun 2024 01:20:02 -0400 Subject: [PATCH] Fix mouse input on outputs not located at 0,0 Possibly addresses #21. --- src/clientside.rs | 14 ----- src/server/dispatch.rs | 26 ++++++++- src/server/event.rs | 120 ++++++++++++++++++++++++++++++++++------- src/server/mod.rs | 4 +- src/server/tests.rs | 6 ++- src/xstate/mod.rs | 19 ++++--- tests/integration.rs | 39 ++++++++++++++ testwl/src/lib.rs | 83 ++++++++++++++++++++++++++-- 8 files changed, 263 insertions(+), 48 deletions(-) diff --git a/src/clientside.rs b/src/clientside.rs index ee3a1e9..956a97a 100644 --- a/src/clientside.rs +++ b/src/clientside.rs @@ -1,6 +1,5 @@ use crate::server::{ObjectEvent, ObjectKey}; use std::os::unix::net::UnixStream; -use std::sync::mpsc; use wayland_client::protocol::{ wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_region::WlRegion, @@ -156,19 +155,6 @@ impl Dispatch for Globals { } } -impl Dispatch>> for Globals { - fn event( - _: &mut Self, - _: &WlOutput, - event: ::Event, - data: &mpsc::Sender>, - _: &Connection, - _: &QueueHandle, - ) { - let _ = data.send(event); - } -} - macro_rules! push_events { ($type:ident) => { impl Dispatch<$type, ObjectKey> for Globals { diff --git a/src/server/dispatch.rs b/src/server/dispatch.rs index e68988f..9b450ee 100644 --- a/src/server/dispatch.rs +++ b/src/server/dispatch.rs @@ -1103,7 +1103,31 @@ where }); } } -global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput); +impl GlobalDispatch for ServerState { + fn bind( + state: &mut Self, + _: &DisplayHandle, + _: &wayland_server::Client, + resource: wayland_server::New, + data: &Global, + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + state.objects.insert_with_key(|key| { + let server = data_init.init(resource, key); + let client = state + .clientside + .global_list + .registry() + .bind::( + data.name, + server.version(), + &state.qh, + key, + ); + Output::new(client, server).into() + }); + } +} global_dispatch_with_events!(WlDrmServer, WlDrmClient); impl GlobalDispatch for ServerState { diff --git a/src/server/event.rs b/src/server/event.rs index 032df34..d028494 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -1,5 +1,6 @@ use super::*; use log::{debug, trace, warn}; +use std::collections::HashSet; use std::os::fd::AsFd; use wayland_client::{protocol as client, Proxy}; use wayland_protocols::{ @@ -97,7 +98,7 @@ macro_rules! simple_event_shunt { } } )+ - _ => log::warn!(concat!("unhandled", stringify!($event_type), ": {:?}"), $event) + _ => log::warn!(concat!("unhandled ", stringify!($event_type), ": {:?}"), $event) } } } @@ -150,16 +151,26 @@ impl SurfaceData { simple_event_shunt! { surface, event: client::wl_surface::Event => [ Enter { |output| { - let Some(object) = &state.get_object_from_client_object::(&output) else { + let key: ObjectKey = output.data().copied().unwrap(); + let Some(object) = state.objects.get_mut(key) else { return; }; - &object.server + let output: &mut Output = object.as_mut(); + if let Some(win) = self.window { + if let Some(data) = state.windows.get(&win) { + output.add_surface(self, data.attrs.dims, state.connection.as_mut().unwrap()); + } + } + &output.server }}, Leave { |output| { - let Some(object) = &state.get_object_from_client_object::(&output) else { + let key: ObjectKey = output.data().copied().unwrap(); + let Some(object) = state.objects.get_mut(key) else { return; }; - &object.server + let output: &mut Output = object.as_mut(); + output.surfaces.remove(&self.client); + &output.server }}, PreferredBufferScale { factor } ] @@ -179,32 +190,36 @@ impl SurfaceData { if let Some(pending) = xdg.pending.take() { let window = state.associated_windows[self.key]; let window = state.windows.get_mut(&window).unwrap(); + let x = match pending.x { + Some(x) => x as i16, + None => 0, + }; + let y = match pending.y { + Some(y) => y as i16, + None => 0, + }; let width = if pending.width > 0 { - pending.width as _ + pending.width as u16 } else { window.attrs.dims.width }; let height = if pending.height > 0 { - pending.height as _ + pending.height as u16 } else { window.attrs.dims.height }; - debug!( - "configuring {:?}: {}x{}, {width}x{height}", - window.window, pending.x, pending.y - ); + debug!("configuring {:?}: {x}x{y}, {width}x{height}", window.window); connection.set_window_dims( window.window, PendingSurfaceState { - x: pending.x, - y: pending.y, width: width as _, height: height as _, + ..pending }, ); window.attrs.dims = WindowDims { - x: pending.x as _, - y: pending.y as _, + x, + y, width, height, }; @@ -277,8 +292,8 @@ impl SurfaceData { } => { trace!("popup configure: {x}x{y}, {width}x{height}"); self.xdg_mut().unwrap().pending = Some(PendingSurfaceState { - x, - y, + x: Some(x), + y: Some(y), width, height, }); @@ -618,11 +633,78 @@ impl HandleEvent for Touch { } } -pub type Output = GenericObject; +pub struct Output { + pub client: client::wl_output::WlOutput, + pub server: WlOutput, + surfaces: HashSet, + x: i32, + y: i32, +} + +impl Output { + pub fn new(client: client::wl_output::WlOutput, server: WlOutput) -> Self { + Self { + client, + server, + surfaces: HashSet::new(), + x: 0, + y: 0, + } + } + + fn add_surface( + &mut self, + surface: &SurfaceData, + dims: WindowDims, + connection: &mut C, + ) { + self.surfaces.insert(surface.client.clone()); + let window = surface.window.unwrap(); + + debug!("moving surface to {}x{}", self.x, self.y); + connection.set_window_dims( + window, + PendingSurfaceState { + x: Some(self.x), + y: Some(self.y), + width: dims.width as _, + height: dims.height as _, + }, + ); + } +} impl HandleEvent for Output { type Event = client::wl_output::Event; - fn handle_event(&mut self, event: Self::Event, _: &mut ServerState) { + fn handle_event(&mut self, event: Self::Event, state: &mut ServerState) { + if let client::wl_output::Event::Geometry { x, y, .. } = event { + debug!("moving output to {x}x{y}"); + self.x = x; + self.y = y; + self.surfaces.retain(|surface| { + let Some(data) = state.get_object_from_client_object::(surface) + else { + return false; + }; + + let window = data.window.as_ref().copied().unwrap(); + let Some(win_data) = state.windows.get(&window) else { + return false; + }; + + state.connection.as_mut().unwrap().set_window_dims( + window, + PendingSurfaceState { + x: Some(x), + y: Some(y), + width: win_data.attrs.dims.width as _, + height: win_data.attrs.dims.height as _, + }, + ); + true + }); + } + simple_event_shunt! { self.server, event: client::wl_output::Event => [ Name { name }, diff --git a/src/server/mod.rs b/src/server/mod.rs index f03f1c4..9250e99 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -937,8 +937,8 @@ impl ServerState { #[derive(Default, Debug)] pub struct PendingSurfaceState { - pub x: i32, - pub y: i32, + pub x: Option, + pub y: Option, pub width: i32, pub height: i32, } diff --git a/src/server/tests.rs b/src/server/tests.rs index c94db97..ca38476 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -191,8 +191,8 @@ impl super::XConnection for FakeXConnection { #[track_caller] fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) { self.window(window).dims = WindowDims { - x: state.x as _, - y: state.y as _, + x: state.x.unwrap_or(0) as _, + y: state.y.unwrap_or(0) as _, width: state.width as _, height: state.height as _, }; @@ -699,6 +699,8 @@ fn pass_through_globals() { use wayland_client::protocol::wl_output::WlOutput; let mut f = TestFixture::new(); + f.testwl.new_output(0, 0); + f.run(); const fn check() {} diff --git a/src/xstate/mod.rs b/src/xstate/mod.rs index f0cab60..f5b76ed 100644 --- a/src/xstate/mod.rs +++ b/src/xstate/mod.rs @@ -782,14 +782,21 @@ impl super::XConnection for Arc { fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) { trace!("reconfiguring window {window:?}"); + let mut vals = vec![ + x::ConfigWindow::Width(dims.width as _), + x::ConfigWindow::Height(dims.height as _), + ]; + if let Some(x) = dims.x { + vals.push(x::ConfigWindow::X(x)); + } + if let Some(y) = dims.y { + vals.push(x::ConfigWindow::Y(y)); + } + vals.sort(); + unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow { window, - value_list: &[ - x::ConfigWindow::X(dims.x), - x::ConfigWindow::Y(dims.y), - x::ConfigWindow::Width(dims.width as _), - x::ConfigWindow::Height(dims.height as _), - ], + value_list: &vals })); } diff --git a/tests/integration.rs b/tests/integration.rs index ceec3c5..5acc17a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -870,3 +870,42 @@ fn copy_from_wayland() { assert_eq!(val, data); } } + +// TODO: this test doesn't actually match real behavior for some reason... +#[test] +fn different_output_position() { + let mut f = Fixture::new(); + let mut connection = Connection::new(&f.display); + + let window = connection.new_window(connection.root, 0, 0, 200, 200, false); + connection.map_window(window); + f.wait_and_dispatch(); + let surface = f + .testwl + .last_created_surface_id() + .expect("No surface created!"); + f.configure_and_verify_new_toplevel(&mut connection, window, surface); + + f.testwl.new_output(0, 0); + f.wait_and_dispatch(); + let output = f.testwl.last_created_output(); + f.testwl.move_surface_to_output(surface, output); + f.testwl.move_pointer_to(surface, 10.0, 10.0); + f.wait_and_dispatch(); + let reply = connection.get_reply(&x::QueryPointer { window }); + assert_eq!(reply.same_screen(), true); + assert_eq!(reply.win_x(), 10); + assert_eq!(reply.win_y(), 10); + + f.testwl.new_output(100, 0); + f.wait_and_dispatch(); + let output = f.testwl.last_created_output(); + f.testwl.move_surface_to_output(surface, output); + f.testwl.move_pointer_to(surface, 150.0, 12.0); + f.wait_and_dispatch(); + let reply = connection.get_reply(&x::QueryPointer { window }); + println!("reply: {reply:?}"); + assert_eq!(reply.same_screen(), true); + assert_eq!(reply.win_x(), 150); + assert_eq!(reply.win_y(), 12); +} diff --git a/testwl/src/lib.rs b/testwl/src/lib.rs index d0814e5..18f7a91 100644 --- a/testwl/src/lib.rs +++ b/testwl/src/lib.rs @@ -38,7 +38,7 @@ use wayland_server::{ wl_data_offer::{self, WlDataOffer}, wl_data_source::{self, WlDataSource}, wl_keyboard::{self, WlKeyboard}, - wl_output::WlOutput, + wl_output::{self, WlOutput}, wl_pointer::{self, WlPointer}, wl_seat::{self, WlSeat}, wl_shm::WlShm, @@ -155,6 +155,7 @@ struct DataSourceData { struct State { surfaces: HashMap, + outputs: Vec, positioners: HashMap, buffers: HashSet, begin: Instant, @@ -172,6 +173,7 @@ impl Default for State { fn default() -> Self { Self { surfaces: Default::default(), + outputs: Default::default(), buffers: Default::default(), positioners: Default::default(), begin: Instant::now(), @@ -286,7 +288,6 @@ impl Server { dh.create_global::(6, ()); dh.create_global::(5, ()); dh.create_global::(3, ()); - global_noop!(WlOutput); global_noop!(ZwpLinuxDmabufV1); global_noop!(ZwpRelativePointerManagerV1); global_noop!(ZxdgOutputManagerV1); @@ -382,6 +383,15 @@ impl Server { self.state.last_surface_id } + #[track_caller] + pub fn last_created_output(&self) -> WlOutput { + self.state + .outputs + .last() + .expect("No outputs created!") + .clone() + } + pub fn get_object( &self, id: SurfaceId, @@ -476,6 +486,27 @@ impl Server { dev.selection(Some(&offer)); self.display.flush_clients().unwrap(); } + + #[track_caller] + pub fn move_pointer_to(&mut self, surface: SurfaceId, x: f64, y: f64) { + let pointer = self.state.pointer.as_ref().expect("No pointer created"); + let data = self.state.surfaces.get(&surface).expect("No such surface"); + + pointer.enter(24, &data.surface, x, y); + pointer.frame(); + self.display.flush_clients().unwrap(); + } + + pub fn new_output(&mut self, x: i32, y: i32) { + self.dh.create_global::(4, (x, y)); + self.display.flush_clients().unwrap(); + } + + pub fn move_surface_to_output(&mut self, surface: SurfaceId, output: WlOutput) { + let data = self.state.surfaces.get(&surface).expect("No such surface"); + data.surface.enter(&output); + self.display.flush_clients().unwrap(); + } } pub struct PasteDataResolver { @@ -527,6 +558,46 @@ simple_global_dispatch!(WlShm); simple_global_dispatch!(WlCompositor); simple_global_dispatch!(XdgWmBase); +impl GlobalDispatch for State { + fn bind( + state: &mut Self, + _: &DisplayHandle, + _: &Client, + resource: wayland_server::New, + &(x, y): &(i32, i32), + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + let output = data_init.init(resource, ()); + output.geometry( + x, + y, + 0, + 0, + wl_output::Subpixel::None, + "xwls".to_string(), + "fake monitor".to_string(), + wl_output::Transform::Normal, + ); + output.mode(wl_output::Mode::Current, 1000, 1000, 0); + output.done(); + state.outputs.push(output); + } +} + +impl Dispatch for State { + fn request( + _: &mut Self, + _: &Client, + _: &WlOutput, + _: ::Request, + _: &(), + _: &DisplayHandle, + _: &mut wayland_server::DataInit<'_, Self>, + ) { + unreachable!(); + } +} + impl GlobalDispatch for State { fn bind( state: &mut Self, @@ -688,8 +759,11 @@ impl Dispatch for State { .unwrap(); assert!( - data.role.replace(SurfaceRole::Cursor).is_none(), - "Surface already had a role!" + matches!( + data.role.replace(SurfaceRole::Cursor), + None | Some(SurfaceRole::Cursor) + ), + "Surface already had a non cursor role!" ); } } @@ -1144,6 +1218,7 @@ impl Dispatch for State { .remove(&SurfaceId(resource.id().protocol_id())); } SetInputRegion { .. } => {} + SetBufferScale { .. } => {} other => todo!("unhandled request {other:?}"), } }