From cf439000a5a271a32ab97d389dd200afe6c83434 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Thu, 8 Aug 2024 01:32:18 -0400 Subject: [PATCH] Prioritize xdg_output for updating window positions Fixes #46 --- src/server/dispatch.rs | 18 ++--- src/server/event.rs | 152 ++++++++++++++++++++++++++++------------- src/server/mod.rs | 3 +- src/server/tests.rs | 98 ++++++++++++++++++++++---- tests/integration.rs | 9 +-- testwl/src/lib.rs | 87 +++++++++++++++++++++-- 6 files changed, 284 insertions(+), 83 deletions(-) diff --git a/src/server/dispatch.rs b/src/server/dispatch.rs index 9b450ee..cc91cd5 100644 --- a/src/server/dispatch.rs +++ b/src/server/dispatch.rs @@ -815,9 +815,9 @@ impl Dispatch for ServerState { unreachable!(); }; - let output: &XdgOutput = state.objects[*key].as_ref(); - output.client.destroy(); - state.objects.remove(*key); + let output: &mut Output = state.objects[*key].as_mut(); + let xdg_output = output.xdg.take().unwrap(); + xdg_output.client.destroy(); } } @@ -836,14 +836,10 @@ impl Dispatch { let output_key: ObjectKey = output.data().copied().unwrap(); - state - .objects - .insert_from_other_objects([output_key], |[output_obj], key| { - let output: &Output = output_obj.try_into().unwrap(); - let client = client.get_xdg_output(&output.client, &state.qh, key); - let server = data_init.init(id, key); - XdgOutput { server, client }.into() - }); + let output: &mut Output = state.objects[output_key].as_mut(); + let client = client.get_xdg_output(&output.client, &state.qh, output_key); + let server = data_init.init(id, output_key); + output.xdg = Some(XdgOutput { client, server }); } s_output_man::Request::Destroy => {} _ => unreachable!(), diff --git a/src/server/event.rs b/src/server/event.rs index e3f086b..54b1b6e 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -162,12 +162,13 @@ impl SurfaceData { .as_ref() .map(|win| state.windows.get_mut(&win).unwrap()) { + let (x, y) = match output.position { + OutputPosition::Xdg { x, y } => (x, y), + OutputPosition::Wl { x, y } => (x, y), + }; win_data.update_output_offset( key, - WindowOutputOffset { - x: output.x, - y: output.y, - }, + WindowOutputOffset { x, y }, state.connection.as_mut().unwrap(), ); output.windows.insert(win_data.window); @@ -326,22 +327,6 @@ impl HandleEvent for Buffer { } } -pub type XdgOutput = GenericObject; -impl HandleEvent for XdgOutput { - type Event = zxdg_output_v1::Event; - fn handle_event(&mut self, event: Self::Event, _: &mut ServerState) { - simple_event_shunt! { - self.server, event: zxdg_output_v1::Event => [ - LogicalPosition { x, y }, - LogicalSize { width, height }, - Done, - Name { name }, - Description { description } - ] - } - } -} - pub type Seat = GenericObject; impl HandleEvent for Seat { type Event = client::wl_seat::Event; @@ -647,12 +632,23 @@ impl HandleEvent for Touch { } } +pub struct XdgOutput { + pub client: ClientXdgOutput, + pub server: ServerXdgOutput, +} + +#[derive(Copy, Clone)] +pub enum OutputPosition { + Wl { x: i32, y: i32 }, + Xdg { x: i32, y: i32 }, +} + pub struct Output { pub client: client::wl_output::WlOutput, pub server: WlOutput, - pub windows: HashSet, - pub x: i32, - pub y: i32, + pub xdg: Option, + windows: HashSet, + position: OutputPosition, } impl Output { @@ -660,37 +656,81 @@ impl Output { Self { client, server, + xdg: None, windows: HashSet::new(), - x: 0, - y: 0, + position: OutputPosition::Wl { x: 0, y: 0 }, } } } + +#[derive(Debug)] +pub enum OutputEvent { + Wl(client::wl_output::Event), + Xdg(zxdg_output_v1::Event), +} + +impl From for ObjectEvent { + fn from(value: client::wl_output::Event) -> Self { + Self::Output(OutputEvent::Wl(value)) + } +} + +impl From for ObjectEvent { + fn from(value: zxdg_output_v1::Event) -> Self { + Self::Output(OutputEvent::Xdg(value)) + } +} + impl HandleEvent for Output { - type Event = client::wl_output::Event; + type Event = OutputEvent; fn handle_event(&mut self, event: Self::Event, state: &mut ServerState) { + match event { + OutputEvent::Xdg(event) => self.xdg_event(event, state), + OutputEvent::Wl(event) => self.wl_event(event, state), + } + } +} + +impl Output { + fn update_offset( + &mut self, + offset: OutputPosition, + state: &mut ServerState, + ) { + if matches!(offset, OutputPosition::Wl { .. }) + && matches!(self.position, OutputPosition::Xdg { .. }) + { + return; + } + + self.position = offset; + let (x, y, id) = match offset { + OutputPosition::Xdg { x, y } => (x, y, self.xdg.as_ref().unwrap().server.id()), + OutputPosition::Wl { x, y } => (x, y, self.server.id()), + }; + debug!("moving {id} to {x}x{y}"); + self.windows.retain(|window| { + let Some(data): Option<&mut WindowData> = state.windows.get_mut(window) else { + return false; + }; + + data.update_output_offset( + self.server.data().copied().unwrap(), + WindowOutputOffset { x, y }, + state.connection.as_mut().unwrap(), + ); + + return true; + }); + } + fn wl_event( + &mut self, + event: client::wl_output::Event, + state: &mut ServerState, + ) { if let client::wl_output::Event::Geometry { x, y, .. } = event { - debug!("moving {} to {x}x{y}", self.server.id()); - self.x = x; - self.y = y; - - self.windows.retain(|window| { - let Some(data): Option<&mut WindowData> = state.windows.get_mut(window) else { - return false; - }; - - data.update_output_offset( - self.server.data().copied().unwrap(), - WindowOutputOffset { - x: self.x, - y: self.y, - }, - state.connection.as_mut().unwrap(), - ); - - return true; - }); + self.update_offset(OutputPosition::Wl { x, y }, state); } simple_event_shunt! { @@ -718,6 +758,26 @@ impl HandleEvent for Output { ] } } + + fn xdg_event( + &mut self, + event: zxdg_output_v1::Event, + state: &mut ServerState, + ) { + if let zxdg_output_v1::Event::LogicalPosition { x, y } = event { + self.update_offset(OutputPosition::Xdg { x, y }, state); + } + let xdg = &self.xdg.as_ref().unwrap().server; + simple_event_shunt! { + xdg, event: zxdg_output_v1::Event => [ + LogicalPosition { x, y }, + LogicalSize { width, height }, + Done, + Name { name }, + Description { description } + ] + } + } } pub type Drm = GenericObject; diff --git a/src/server/mod.rs b/src/server/mod.rs index 0898dd0..f0922b2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -134,7 +134,7 @@ impl WindowData { offset: WindowOutputOffset, connection: &mut C, ) { - debug!("offset: {offset:?}"); + log::trace!("offset: {offset:?}"); if self.output_key != Some(output_key) { self.output_key = Some(output_key); } @@ -360,7 +360,6 @@ pub(crate) enum Object { RelativePointer(RelativePointer), DmabufFeedback(DmabufFeedback), Drm(Drm), - XdgOutput(XdgOutput), Touch(Touch), ConfinedPointer(ConfinedPointer), LockedPointer(LockedPointer) diff --git a/src/server/tests.rs b/src/server/tests.rs index f161751..e9d572d 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -32,7 +32,10 @@ use wayland_protocols::{ }, xdg::{ shell::server::{xdg_positioner, xdg_toplevel}, - xdg_output::zv1::client::zxdg_output_manager_v1::ZxdgOutputManagerV1, + xdg_output::zv1::client::{ + zxdg_output_manager_v1::{self, ZxdgOutputManagerV1}, + zxdg_output_v1::ZxdgOutputV1, + }, }, xwayland::shell::v1::client::{ xwayland_shell_v1::XwaylandShellV1, xwayland_surface_v1::XwaylandSurfaceV1, @@ -371,7 +374,14 @@ impl TestFixture { } } - fn new_output(&mut self, x: i32, y: i32) -> wayland_server::protocol::wl_output::WlOutput { + fn new_output( + &mut self, + x: i32, + y: i32, + ) -> ( + TestObject, + wayland_server::protocol::wl_output::WlOutput, + ) { self.testwl.new_output(x, y); self.run(); self.run(); @@ -388,7 +398,7 @@ impl TestFixture { }; assert_eq!(interface, WlOutput::interface().name); - TestObject::::from_request( + let output = TestObject::::from_request( &self.registry.obj, Req::::Bind { name, @@ -396,7 +406,49 @@ impl TestFixture { }, ); self.run(); - self.testwl.last_created_output() + (output, self.testwl.last_created_output()) + } + + fn enable_xdg_output(&mut self) -> TestObject { + self.testwl.enable_xdg_output_manager(); + self.run(); + self.run(); + + let mut events = std::mem::take(&mut *self.registry.data.events.lock().unwrap()); + assert_eq!( + events.len(), + 1, + "Unexpected number of global events after enabling xdg output" + ); + let event = events.pop().unwrap(); + let Ev::::Global { + name, + interface, + version, + } = event + else { + panic!("Unexpected event: {event:?}"); + }; + + assert_eq!(interface, ZxdgOutputManagerV1::interface().name); + let man = TestObject::::from_request( + &self.registry.obj, + Req::::Bind { + name, + id: (ZxdgOutputManagerV1::interface(), version), + }, + ); + self.run(); + man + } + + fn create_xdg_output(&mut self, man: &TestObject, output: WlOutput) { + TestObject::::from_request( + &man.obj, + zxdg_output_manager_v1::Request::GetXdgOutput { output }, + ); + self.run(); + self.run(); } fn register_window(&mut self, window: Window, data: WindowData) { @@ -744,6 +796,8 @@ fn pass_through_globals() { let mut f = TestFixture::new(); f.testwl.new_output(0, 0); + f.testwl.enable_xdg_output_manager(); + f.run(); f.run(); const fn check() {} @@ -1214,7 +1268,11 @@ fn override_redirect_choose_hover_window() { #[test] fn output_offset() { let (mut f, comp) = TestFixture::new_with_compositor(); - let output = f.new_output(500, 100); + let (output_obj, output) = f.new_output(0, 0); + let man = f.enable_xdg_output(); + f.create_xdg_output(&man, output_obj.obj); + f.testwl.move_xdg_output(&output, 500, 100); + f.run(); let window = unsafe { Window::new(1) }; { @@ -1285,22 +1343,38 @@ fn output_offset() { #[test] fn output_offset_change() { let (mut f, comp) = TestFixture::new_with_compositor(); - let output = f.new_output(500, 100); + + let (output_obj, output) = f.new_output(500, 100); let window = unsafe { Window::new(1) }; let (_, id) = f.create_toplevel(&comp, window); f.testwl.move_surface_to_output(id, &output); f.run(); - let data = &f.connection().windows[&window]; - assert_eq!(data.dims.x, 500); - assert_eq!(data.dims.y, 100); + let test_position = |f: &TestFixture, x, y| { + let data = &f.connection().windows[&window]; + assert_eq!(data.dims.x, x); + assert_eq!(data.dims.y, y); + }; + test_position(&f, 500, 100); f.testwl.move_output(&output, 600, 200); f.run(); f.run(); - let data = &f.connection().windows[&window]; - assert_eq!(data.dims.x, 600); - assert_eq!(data.dims.y, 200); + test_position(&f, 600, 200); + + let man = f.enable_xdg_output(); + f.create_xdg_output(&man, output_obj.obj); + // testwl inits xdg output position to 0, and it should take priority over wl_output position + test_position(&f, 0, 0); + + f.testwl.move_xdg_output(&output, 1000, 22); + f.run(); + test_position(&f, 1000, 22); + + f.testwl.move_output(&output, 600, 200); + f.run(); + f.run(); + test_position(&f, 1000, 22); } #[test] diff --git a/tests/integration.rs b/tests/integration.rs index 991888f..d0c92e4 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -829,6 +829,7 @@ fn copy_from_wayland() { #[test] fn different_output_position() { let mut f = Fixture::new(); + //f.testwl.enable_xdg_output_manager(); let mut connection = Connection::new(&f.display); let window = connection.new_window(connection.root, 0, 0, 200, 200, false); @@ -854,6 +855,7 @@ fn different_output_position() { f.testwl.new_output(100, 0); f.wait_and_dispatch(); let output = f.testwl.last_created_output(); + //f.testwl.move_xdg_output(&output, 100, 0); f.testwl.move_surface_to_output(surface, &output); f.testwl.move_pointer_to(surface, 150.0, 12.0); f.wait_and_dispatch(); @@ -986,12 +988,7 @@ fn funny_window_title() { let mut f = Fixture::new(); let mut connection = Connection::new(&f.display); let window = connection.new_window(connection.root, 0, 0, 20, 20, false); - connection.set_property( - window, - x::ATOM_STRING, - x::ATOM_WM_NAME, - b"title\0\0\0\0", - ); + connection.set_property(window, x::ATOM_STRING, x::ATOM_WM_NAME, b"title\0\0\0\0"); connection.map_window(window); f.wait_and_dispatch(); diff --git a/testwl/src/lib.rs b/testwl/src/lib.rs index 6b48196..b3292a1 100644 --- a/testwl/src/lib.rs +++ b/testwl/src/lib.rs @@ -20,7 +20,10 @@ use wayland_protocols::{ xdg_toplevel::{self, XdgToplevel}, xdg_wm_base::{self, XdgWmBase}, }, - xdg_output::zv1::server::zxdg_output_manager_v1::ZxdgOutputManagerV1, + xdg_output::zv1::server::{ + zxdg_output_manager_v1::{self, ZxdgOutputManagerV1}, + zxdg_output_v1::{self, ZxdgOutputV1}, + }, }, }; use wayland_server::{ @@ -153,13 +156,19 @@ struct DataSourceData { mimes: Vec, } +struct Output { + wl: WlOutput, + xdg: Option, +} + struct State { surfaces: HashMap, - outputs: Vec, + outputs: HashMap, positioners: HashMap, buffers: HashSet, begin: Instant, last_surface_id: Option, + last_output: Option, callbacks: Vec, pointer: Option, keyboard: Option, @@ -178,6 +187,7 @@ impl Default for State { positioners: Default::default(), begin: Instant::now(), last_surface_id: None, + last_output: None, callbacks: Vec::new(), pointer: None, keyboard: None, @@ -302,7 +312,6 @@ impl Server { dh.create_global::(3, ()); global_noop!(ZwpLinuxDmabufV1); global_noop!(ZwpRelativePointerManagerV1); - global_noop!(ZxdgOutputManagerV1); global_noop!(WpViewporter); global_noop!(ZwpPointerConstraintsV1); @@ -397,8 +406,8 @@ impl Server { #[track_caller] pub fn last_created_output(&self) -> WlOutput { self.state - .outputs - .last() + .last_output + .as_ref() .expect("No outputs created!") .clone() } @@ -525,6 +534,22 @@ impl Server { data.surface.enter(output); self.display.flush_clients().unwrap(); } + + pub fn enable_xdg_output_manager(&mut self) { + self.dh + .create_global::(3, ()); + self.display.flush_clients().unwrap(); + } + + pub fn move_xdg_output(&mut self, output: &WlOutput, x: i32, y: i32) { + let xdg = self.state.outputs[output] + .xdg + .as_ref() + .expect("Output doesn't have an xdg output"); + xdg.logical_position(x, y); + xdg.done(); + self.display.flush_clients().unwrap(); + } } pub struct PasteDataResolver { @@ -575,6 +600,49 @@ pub struct PasteData { simple_global_dispatch!(WlShm); simple_global_dispatch!(WlCompositor); simple_global_dispatch!(XdgWmBase); +simple_global_dispatch!(ZxdgOutputManagerV1); + +impl Dispatch for State { + fn request( + state: &mut Self, + _: &Client, + _: &ZxdgOutputManagerV1, + request: ::Request, + _: &(), + _: &DisplayHandle, + data_init: &mut wayland_server::DataInit<'_, Self>, + ) { + match request { + zxdg_output_manager_v1::Request::GetXdgOutput { id, output } => { + let xdg = data_init.init(id, output.clone()); + xdg.logical_position(0, 0); + xdg.logical_size(1000, 1000); + xdg.done(); + state.outputs.get_mut(&output).unwrap().xdg = Some(xdg); + } + other => todo!("unhandled request: {other:?}"), + } + } +} + +impl Dispatch for State { + fn request( + state: &mut Self, + _: &Client, + _: &ZxdgOutputV1, + request: ::Request, + output: &WlOutput, + _: &DisplayHandle, + _: &mut wayland_server::DataInit<'_, Self>, + ) { + match request { + zxdg_output_v1::Request::Destroy => { + state.outputs.get_mut(output).unwrap().xdg = None; + } + other => todo!("unhandled request: {other:?}"), + } + } +} impl GlobalDispatch for State { fn bind( @@ -598,7 +666,14 @@ impl GlobalDispatch for State { ); output.mode(wl_output::Mode::Current, 1000, 1000, 0); output.done(); - state.outputs.push(output); + state.outputs.insert( + output.clone(), + Output { + wl: output.clone(), + xdg: None, + }, + ); + state.last_output = Some(output); } }