Fix mouse input on outputs not located at 0,0

Possibly addresses #21.
This commit is contained in:
Shawn Wallace 2024-06-30 01:20:02 -04:00
parent 0a5dddacfd
commit d3a46b7c8a
8 changed files with 263 additions and 48 deletions

View file

@ -1,6 +1,5 @@
use crate::server::{ObjectEvent, ObjectKey}; use crate::server::{ObjectEvent, ObjectKey};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::sync::mpsc;
use wayland_client::protocol::{ use wayland_client::protocol::{
wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor, wl_buffer::WlBuffer, wl_callback::WlCallback, wl_compositor::WlCompositor,
wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_region::WlRegion, wl_keyboard::WlKeyboard, wl_output::WlOutput, wl_pointer::WlPointer, wl_region::WlRegion,
@ -156,19 +155,6 @@ impl Dispatch<WlCallback, server::wl_callback::WlCallback> for Globals {
} }
} }
impl Dispatch<WlOutput, mpsc::Sender<Event<WlOutput>>> for Globals {
fn event(
_: &mut Self,
_: &WlOutput,
event: <WlOutput as Proxy>::Event,
data: &mpsc::Sender<Event<WlOutput>>,
_: &Connection,
_: &QueueHandle<Self>,
) {
let _ = data.send(event);
}
}
macro_rules! push_events { macro_rules! push_events {
($type:ident) => { ($type:ident) => {
impl Dispatch<$type, ObjectKey> for Globals { impl Dispatch<$type, ObjectKey> for Globals {

View file

@ -1103,7 +1103,31 @@ where
}); });
} }
} }
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput); impl<C: XConnection> GlobalDispatch<WlOutput, Global> for ServerState<C> {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &wayland_server::Client,
resource: wayland_server::New<WlOutput>,
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::<client::wl_output::WlOutput, _, _>(
data.name,
server.version(),
&state.qh,
key,
);
Output::new(client, server).into()
});
}
}
global_dispatch_with_events!(WlDrmServer, WlDrmClient); global_dispatch_with_events!(WlDrmServer, WlDrmClient);
impl<C: XConnection> GlobalDispatch<XwaylandShellV1, ()> for ServerState<C> { impl<C: XConnection> GlobalDispatch<XwaylandShellV1, ()> for ServerState<C> {

View file

@ -1,5 +1,6 @@
use super::*; use super::*;
use log::{debug, trace, warn}; use log::{debug, trace, warn};
use std::collections::HashSet;
use std::os::fd::AsFd; use std::os::fd::AsFd;
use wayland_client::{protocol as client, Proxy}; use wayland_client::{protocol as client, Proxy};
use wayland_protocols::{ 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! { simple_event_shunt! {
surface, event: client::wl_surface::Event => [ surface, event: client::wl_surface::Event => [
Enter { |output| { Enter { |output| {
let Some(object) = &state.get_object_from_client_object::<Output, _>(&output) else { let key: ObjectKey = output.data().copied().unwrap();
let Some(object) = state.objects.get_mut(key) else {
return; 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| { Leave { |output| {
let Some(object) = &state.get_object_from_client_object::<Output, _>(&output) else { let key: ObjectKey = output.data().copied().unwrap();
let Some(object) = state.objects.get_mut(key) else {
return; return;
}; };
&object.server let output: &mut Output = object.as_mut();
output.surfaces.remove(&self.client);
&output.server
}}, }},
PreferredBufferScale { factor } PreferredBufferScale { factor }
] ]
@ -179,32 +190,36 @@ impl SurfaceData {
if let Some(pending) = xdg.pending.take() { if let Some(pending) = xdg.pending.take() {
let window = state.associated_windows[self.key]; let window = state.associated_windows[self.key];
let window = state.windows.get_mut(&window).unwrap(); 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 { let width = if pending.width > 0 {
pending.width as _ pending.width as u16
} else { } else {
window.attrs.dims.width window.attrs.dims.width
}; };
let height = if pending.height > 0 { let height = if pending.height > 0 {
pending.height as _ pending.height as u16
} else { } else {
window.attrs.dims.height window.attrs.dims.height
}; };
debug!( debug!("configuring {:?}: {x}x{y}, {width}x{height}", window.window);
"configuring {:?}: {}x{}, {width}x{height}",
window.window, pending.x, pending.y
);
connection.set_window_dims( connection.set_window_dims(
window.window, window.window,
PendingSurfaceState { PendingSurfaceState {
x: pending.x,
y: pending.y,
width: width as _, width: width as _,
height: height as _, height: height as _,
..pending
}, },
); );
window.attrs.dims = WindowDims { window.attrs.dims = WindowDims {
x: pending.x as _, x,
y: pending.y as _, y,
width, width,
height, height,
}; };
@ -277,8 +292,8 @@ impl SurfaceData {
} => { } => {
trace!("popup configure: {x}x{y}, {width}x{height}"); trace!("popup configure: {x}x{y}, {width}x{height}");
self.xdg_mut().unwrap().pending = Some(PendingSurfaceState { self.xdg_mut().unwrap().pending = Some(PendingSurfaceState {
x, x: Some(x),
y, y: Some(y),
width, width,
height, height,
}); });
@ -618,11 +633,78 @@ impl HandleEvent for Touch {
} }
} }
pub type Output = GenericObject<WlOutput, client::wl_output::WlOutput>; pub struct Output {
pub client: client::wl_output::WlOutput,
pub server: WlOutput,
surfaces: HashSet<client::wl_surface::WlSurface>,
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<C: XConnection>(
&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 { impl HandleEvent for Output {
type Event = client::wl_output::Event; type Event = client::wl_output::Event;
fn handle_event<C: XConnection>(&mut self, event: Self::Event, _: &mut ServerState<C>) { fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
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::<SurfaceData, _>(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! { simple_event_shunt! {
self.server, event: client::wl_output::Event => [ self.server, event: client::wl_output::Event => [
Name { name }, Name { name },

View file

@ -937,8 +937,8 @@ impl<C: XConnection> ServerState<C> {
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct PendingSurfaceState { pub struct PendingSurfaceState {
pub x: i32, pub x: Option<i32>,
pub y: i32, pub y: Option<i32>,
pub width: i32, pub width: i32,
pub height: i32, pub height: i32,
} }

View file

@ -191,8 +191,8 @@ impl super::XConnection for FakeXConnection {
#[track_caller] #[track_caller]
fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) { fn set_window_dims(&mut self, window: Window, state: super::PendingSurfaceState) {
self.window(window).dims = WindowDims { self.window(window).dims = WindowDims {
x: state.x as _, x: state.x.unwrap_or(0) as _,
y: state.y as _, y: state.y.unwrap_or(0) as _,
width: state.width as _, width: state.width as _,
height: state.height as _, height: state.height as _,
}; };
@ -699,6 +699,8 @@ fn pass_through_globals() {
use wayland_client::protocol::wl_output::WlOutput; use wayland_client::protocol::wl_output::WlOutput;
let mut f = TestFixture::new(); let mut f = TestFixture::new();
f.testwl.new_output(0, 0);
f.run();
const fn check<T: Proxy>() {} const fn check<T: Proxy>() {}

View file

@ -782,14 +782,21 @@ impl super::XConnection for Arc<xcb::Connection> {
fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) { fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
trace!("reconfiguring window {window:?}"); trace!("reconfiguring window {window:?}");
unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow { let mut vals = vec![
window,
value_list: &[
x::ConfigWindow::X(dims.x),
x::ConfigWindow::Y(dims.y),
x::ConfigWindow::Width(dims.width as _), x::ConfigWindow::Width(dims.width as _),
x::ConfigWindow::Height(dims.height 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: &vals
})); }));
} }

View file

@ -870,3 +870,42 @@ fn copy_from_wayland() {
assert_eq!(val, data); 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);
}

View file

@ -38,7 +38,7 @@ use wayland_server::{
wl_data_offer::{self, WlDataOffer}, wl_data_offer::{self, WlDataOffer},
wl_data_source::{self, WlDataSource}, wl_data_source::{self, WlDataSource},
wl_keyboard::{self, WlKeyboard}, wl_keyboard::{self, WlKeyboard},
wl_output::WlOutput, wl_output::{self, WlOutput},
wl_pointer::{self, WlPointer}, wl_pointer::{self, WlPointer},
wl_seat::{self, WlSeat}, wl_seat::{self, WlSeat},
wl_shm::WlShm, wl_shm::WlShm,
@ -155,6 +155,7 @@ struct DataSourceData {
struct State { struct State {
surfaces: HashMap<SurfaceId, SurfaceData>, surfaces: HashMap<SurfaceId, SurfaceData>,
outputs: Vec<WlOutput>,
positioners: HashMap<PositionerId, PositionerState>, positioners: HashMap<PositionerId, PositionerState>,
buffers: HashSet<WlBuffer>, buffers: HashSet<WlBuffer>,
begin: Instant, begin: Instant,
@ -172,6 +173,7 @@ impl Default for State {
fn default() -> Self { fn default() -> Self {
Self { Self {
surfaces: Default::default(), surfaces: Default::default(),
outputs: Default::default(),
buffers: Default::default(), buffers: Default::default(),
positioners: Default::default(), positioners: Default::default(),
begin: Instant::now(), begin: Instant::now(),
@ -286,7 +288,6 @@ impl Server {
dh.create_global::<State, XdgWmBase, _>(6, ()); dh.create_global::<State, XdgWmBase, _>(6, ());
dh.create_global::<State, WlSeat, _>(5, ()); dh.create_global::<State, WlSeat, _>(5, ());
dh.create_global::<State, WlDataDeviceManager, _>(3, ()); dh.create_global::<State, WlDataDeviceManager, _>(3, ());
global_noop!(WlOutput);
global_noop!(ZwpLinuxDmabufV1); global_noop!(ZwpLinuxDmabufV1);
global_noop!(ZwpRelativePointerManagerV1); global_noop!(ZwpRelativePointerManagerV1);
global_noop!(ZxdgOutputManagerV1); global_noop!(ZxdgOutputManagerV1);
@ -382,6 +383,15 @@ impl Server {
self.state.last_surface_id 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<T: Resource + 'static>( pub fn get_object<T: Resource + 'static>(
&self, &self,
id: SurfaceId, id: SurfaceId,
@ -476,6 +486,27 @@ impl Server {
dev.selection(Some(&offer)); dev.selection(Some(&offer));
self.display.flush_clients().unwrap(); 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::<State, WlOutput, _>(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 { pub struct PasteDataResolver {
@ -527,6 +558,46 @@ simple_global_dispatch!(WlShm);
simple_global_dispatch!(WlCompositor); simple_global_dispatch!(WlCompositor);
simple_global_dispatch!(XdgWmBase); simple_global_dispatch!(XdgWmBase);
impl GlobalDispatch<WlOutput, (i32, i32)> for State {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: wayland_server::New<WlOutput>,
&(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<WlOutput, ()> for State {
fn request(
_: &mut Self,
_: &Client,
_: &WlOutput,
_: <WlOutput as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut wayland_server::DataInit<'_, Self>,
) {
unreachable!();
}
}
impl GlobalDispatch<WlDataDeviceManager, ()> for State { impl GlobalDispatch<WlDataDeviceManager, ()> for State {
fn bind( fn bind(
state: &mut Self, state: &mut Self,
@ -688,8 +759,11 @@ impl Dispatch<WlPointer, ()> for State {
.unwrap(); .unwrap();
assert!( assert!(
data.role.replace(SurfaceRole::Cursor).is_none(), matches!(
"Surface already had a role!" data.role.replace(SurfaceRole::Cursor),
None | Some(SurfaceRole::Cursor)
),
"Surface already had a non cursor role!"
); );
} }
} }
@ -1144,6 +1218,7 @@ impl Dispatch<WlSurface, ()> for State {
.remove(&SurfaceId(resource.id().protocol_id())); .remove(&SurfaceId(resource.id().protocol_id()));
} }
SetInputRegion { .. } => {} SetInputRegion { .. } => {}
SetBufferScale { .. } => {}
other => todo!("unhandled request {other:?}"), other => todo!("unhandled request {other:?}"),
} }
} }