Properly offset popups on offset outputs

This commit is contained in:
Shawn Wallace 2024-07-02 00:16:01 -04:00
parent d8a9c28fa7
commit 3794c4b945
3 changed files with 96 additions and 31 deletions

View file

@ -157,8 +157,8 @@ impl SurfaceData {
}; };
let output: &mut Output = object.as_mut(); let output: &mut Output = object.as_mut();
if let Some(win) = self.window { if let Some(win) = self.window {
if let Some(data) = state.windows.get(&win) { if let Some(data) = state.windows.get_mut(&win) {
output.add_surface(self, data.attrs.dims, state.connection.as_mut().unwrap()); output.add_surface(self, &mut data.attrs.dims, state.connection.as_mut().unwrap());
} }
} }
&output.server &output.server
@ -661,11 +661,13 @@ impl Output {
fn add_surface<C: XConnection>( fn add_surface<C: XConnection>(
&mut self, &mut self,
surface: &SurfaceData, surface: &SurfaceData,
dims: WindowDims, dims: &mut WindowDims,
connection: &mut C, connection: &mut C,
) { ) {
self.surfaces.insert(surface.client.clone()); self.surfaces.insert(surface.client.clone());
let window = surface.window.unwrap(); let window = surface.window.unwrap();
dims.x = self.x as _;
dims.y = self.y as _;
debug!("moving surface to {}x{}", self.x, self.y); debug!("moving surface to {}x{}", self.x, self.y);
connection.set_window_dims( connection.set_window_dims(

View file

@ -817,10 +817,14 @@ impl<C: XConnection> ServerState<C> {
let parent_window = self.windows.get(&parent).unwrap(); let parent_window = self.windows.get(&parent).unwrap();
let parent_surface: &SurfaceData = let parent_surface: &SurfaceData =
self.objects[parent_window.surface_key.unwrap()].as_ref(); self.objects[parent_window.surface_key.unwrap()].as_ref();
let parent_dims = parent_window.attrs.dims;
let x = window.attrs.dims.x - parent_dims.x;
let y = window.attrs.dims.y - parent_dims.y;
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ()); let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _); positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _);
positioner.set_offset(window.attrs.dims.x as i32, window.attrs.dims.y as i32); positioner.set_offset(x as i32, y as i32);
positioner.set_anchor(Anchor::TopLeft); positioner.set_anchor(Anchor::TopLeft);
positioner.set_gravity(Gravity::BottomRight); positioner.set_gravity(Gravity::BottomRight);
positioner.set_anchor_rect( positioner.set_anchor_rect(

View file

@ -13,6 +13,7 @@ use wayland_client::{
wl_compositor::WlCompositor, wl_compositor::WlCompositor,
wl_display::WlDisplay, wl_display::WlDisplay,
wl_keyboard::WlKeyboard, wl_keyboard::WlKeyboard,
wl_output::WlOutput,
wl_pointer::WlPointer, wl_pointer::WlPointer,
wl_registry::WlRegistry, wl_registry::WlRegistry,
wl_seat::{self, WlSeat}, wl_seat::{self, WlSeat},
@ -226,6 +227,7 @@ struct TestFixture {
/// Exwayland's display - must dispatch this for our server state to advance /// Exwayland's display - must dispatch this for our server state to advance
exwl_display: Display<FakeServerState>, exwl_display: Display<FakeServerState>,
surface_serial: u64, surface_serial: u64,
registry: TestObject<WlRegistry>,
} }
static INIT: std::sync::Once = std::sync::Once::new(); static INIT: std::sync::Once = std::sync::Once::new();
@ -259,12 +261,20 @@ impl TestFixture {
exwayland.connect(ex_server); exwayland.connect(ex_server);
exwayland.set_x_connection(FakeXConnection::default()); exwayland.set_x_connection(FakeXConnection::default());
let exwl_connection = Connection::from_socket(fake_client).unwrap();
let registry = TestObject::<WlRegistry>::from_request(
&exwl_connection.display(),
Req::<WlDisplay>::GetRegistry {},
);
let mut f = TestFixture { let mut f = TestFixture {
testwl, testwl,
exwayland, exwayland,
exwl_connection: Connection::from_socket(fake_client).unwrap().into(), exwl_connection: exwl_connection.into(),
exwl_display: display, exwl_display: display,
surface_serial: 1, surface_serial: 1,
registry,
}; };
f.run(); f.run();
f f
@ -282,13 +292,7 @@ impl TestFixture {
fn compositor(&mut self) -> Compositor { fn compositor(&mut self) -> Compositor {
let mut ret = CompositorOptional::default(); let mut ret = CompositorOptional::default();
let wl_display = self.exwl_connection.display(); let events = std::mem::take(&mut *self.registry.data.events.lock().unwrap());
let registry =
TestObject::<WlRegistry>::from_request(&wl_display, Req::<WlDisplay>::GetRegistry {});
self.run();
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
assert!(!events.is_empty()); assert!(!events.is_empty());
for event in events { for event in events {
@ -306,25 +310,25 @@ impl TestFixture {
match interface { match interface {
x if x == WlCompositor::interface().name => { x if x == WlCompositor::interface().name => {
ret.compositor = Some(TestObject::from_request( ret.compositor = Some(TestObject::from_request(
&registry.obj, &self.registry.obj,
bind_req(WlCompositor::interface()), bind_req(WlCompositor::interface()),
)); ));
} }
x if x == WlShm::interface().name => { x if x == WlShm::interface().name => {
ret.shm = Some(TestObject::from_request( ret.shm = Some(TestObject::from_request(
&registry.obj, &self.registry.obj,
bind_req(WlShm::interface()), bind_req(WlShm::interface()),
)); ));
} }
x if x == XwaylandShellV1::interface().name => { x if x == XwaylandShellV1::interface().name => {
ret.shell = Some(TestObject::from_request( ret.shell = Some(TestObject::from_request(
&registry.obj, &self.registry.obj,
bind_req(XwaylandShellV1::interface()), bind_req(XwaylandShellV1::interface()),
)); ));
} }
x if x == WlSeat::interface().name => { x if x == WlSeat::interface().name => {
ret.seat = Some(TestObject::from_request( ret.seat = Some(TestObject::from_request(
&registry.obj, &self.registry.obj,
bind_req(WlSeat::interface()), bind_req(WlSeat::interface()),
)); ));
} }
@ -361,12 +365,40 @@ impl TestFixture {
// Get our events // Get our events
let res = self.exwl_connection.prepare_read().unwrap().read(); let res = self.exwl_connection.prepare_read().unwrap().read();
if res.is_err() if res.is_err()
&& !matches!(res, Err(WaylandError::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock) && matches!(res, Err(WaylandError::Io(ref e)) if e.kind() != std::io::ErrorKind::WouldBlock)
{ {
panic!("Read failed: {res:?}") panic!("Read failed: {res:?}")
} }
} }
fn new_output(&mut self, x: i32, y: i32) -> wayland_server::protocol::wl_output::WlOutput {
self.testwl.new_output(x, y);
self.run();
self.run();
let mut events = std::mem::take(&mut *self.registry.data.events.lock().unwrap());
assert_eq!(events.len(), 1);
let event = events.pop().unwrap();
let Ev::<WlRegistry>::Global {
name,
interface,
version,
} = event
else {
panic!("Unexpected event: {event:?}");
};
assert_eq!(interface, WlOutput::interface().name);
TestObject::<WlOutput>::from_request(
&self.registry.obj,
Req::<WlRegistry>::Bind {
name,
id: (WlOutput::interface(), version),
},
);
self.run();
self.testwl.last_created_output()
}
fn register_window(&mut self, window: Window, data: WindowData) { fn register_window(&mut self, window: Window, data: WindowData) {
self.exwayland self.exwayland
.connection .connection
@ -510,14 +542,17 @@ impl TestFixture {
&mut self, &mut self,
comp: &Compositor, comp: &Compositor,
window: Window, window: Window,
parent_id: testwl::SurfaceId, parent_window: Window,
parent_surface: testwl::SurfaceId,
x: i16,
y: i16,
) -> (TestObject<WlSurface>, testwl::SurfaceId) { ) -> (TestObject<WlSurface>, testwl::SurfaceId) {
let (buffer, surface) = comp.create_surface(); let (buffer, surface) = comp.create_surface();
let data = WindowData { let data = WindowData {
mapped: true, mapped: true,
dims: WindowDims { dims: WindowDims {
x: 10, x,
y: 10, y,
width: 50, width: 50,
height: 50, height: 50,
}, },
@ -529,7 +564,7 @@ impl TestFixture {
self.run(); self.run();
let popup_id = self.check_new_surface(); let popup_id = self.check_new_surface();
assert_ne!(popup_id, parent_id); assert_ne!(popup_id, parent_surface);
{ {
let surface_data = self.testwl.get_surface_data(popup_id).unwrap(); let surface_data = self.testwl.get_surface_data(popup_id).unwrap();
@ -549,7 +584,7 @@ impl TestFixture {
let toplevel_xdg = &self let toplevel_xdg = &self
.testwl .testwl
.get_surface_data(parent_id) .get_surface_data(parent_surface)
.unwrap() .unwrap()
.xdg() .xdg()
.surface; .surface;
@ -557,18 +592,23 @@ impl TestFixture {
let pos = &surface_data.popup().positioner_state; let pos = &surface_data.popup().positioner_state;
assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 }); assert_eq!(pos.size.as_ref().unwrap(), &testwl::Vec2 { x: 50, y: 50 });
let parent_win = &self.connection().windows[&parent_window];
assert_eq!( assert_eq!(
pos.anchor_rect.as_ref().unwrap(), pos.anchor_rect.as_ref().unwrap(),
&testwl::Rect { &testwl::Rect {
size: testwl::Vec2 { x: 100, y: 100 }, size: testwl::Vec2 {
x: parent_win.dims.width as _,
y: parent_win.dims.height as _
},
offset: testwl::Vec2::default() offset: testwl::Vec2::default()
} }
); );
assert_eq!( assert_eq!(
pos.offset, pos.offset,
testwl::Vec2 { testwl::Vec2 {
x: dims.x as _, x: (dims.x - parent_win.dims.x) as _,
y: dims.y as _ y: (dims.y - parent_win.dims.y) as _
} }
); );
assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft); assert_eq!(pos.anchor, xdg_positioner::Anchor::TopLeft);
@ -692,7 +732,7 @@ fn popup_flow_simple() {
let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel); let (_, toplevel_id) = f.create_toplevel(&compositor, win_toplevel);
let win_popup = unsafe { Window::new(2) }; let win_popup = unsafe { Window::new(2) };
let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, toplevel_id); let (popup_surface, popup_id) = f.create_popup(&compositor, win_popup, win_toplevel, toplevel_id, 10, 10);
f.exwayland.unmap_window(win_popup); f.exwayland.unmap_window(win_popup);
f.exwayland.destroy_window(win_popup); f.exwayland.destroy_window(win_popup);
@ -756,11 +796,8 @@ fn pass_through_globals() {
} }
let mut globals = SupportedGlobals::default(); let mut globals = SupportedGlobals::default();
let display = f.exwl_connection.display();
let registry =
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
f.run(); f.run();
let events = std::mem::take(&mut *registry.data.events.lock().unwrap()); let events = std::mem::take(&mut *f.registry.data.events.lock().unwrap());
assert!(!events.is_empty()); assert!(!events.is_empty());
for event in events { for event in events {
let Ev::<WlRegistry>::Global { interface, .. } = event else { let Ev::<WlRegistry>::Global { interface, .. } = event else {
@ -809,7 +846,7 @@ fn popup_window_changes_surface() {
let (_, toplevel_id) = f.create_toplevel(&comp, t_win); let (_, toplevel_id) = f.create_toplevel(&comp, t_win);
let win = unsafe { Window::new(2) }; let win = unsafe { Window::new(2) };
let (surface, old_id) = f.create_popup(&comp, win, toplevel_id); let (surface, old_id) = f.create_popup(&comp, win, t_win, toplevel_id, 0, 0);
f.exwayland.unmap_window(win); f.exwayland.unmap_window(win);
surface.obj.destroy(); surface.obj.destroy();
@ -1178,6 +1215,28 @@ fn override_redirect_choose_hover_window() {
assert_eq!(&popup_data.popup().parent, win1_xdg); assert_eq!(&popup_data.popup().parent, win1_xdg);
} }
#[test]
fn offset_output() {
let (mut f, comp) = TestFixture::new_with_compositor();
let output = f.new_output(500, 100);
let window = unsafe { Window::new(1) };
let (_, surface_id) = f.create_toplevel(&comp, window);
f.testwl.move_surface_to_output(surface_id, output);
f.run();
let data = &f.connection().windows[&window];
assert_eq!(data.dims.x, 500);
assert_eq!(data.dims.y, 100);
let popup = unsafe { Window::new(2) };
let (_, p_id) = f.create_popup(&comp, popup, window, surface_id, 510, 110);
let data = f.testwl.get_surface_data(p_id).unwrap();
assert_eq!(
data.popup().positioner_state.offset,
testwl::Vec2 { x: 10, y: 10 }
);
}
/// See Pointer::handle_event for an explanation. /// See Pointer::handle_event for an explanation.
#[test] #[test]
fn popup_pointer_motion_workaround() {} fn popup_pointer_motion_workaround() {}