From 653391c7c98346ce33880e5e1bf140dc6db6a804 Mon Sep 17 00:00:00 2001 From: Shawn Wallace Date: Mon, 10 Feb 2025 22:57:38 -0500 Subject: [PATCH] server: auto-fullscreen windows that match an output's dimensions Fixes #93 --- src/server/dispatch.rs | 3 +- src/server/event.rs | 70 ++++++++++++++++++++++++++++++------------ src/server/mod.rs | 44 ++++++++++++++++++++------ src/server/tests.rs | 54 ++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 30 deletions(-) diff --git a/src/server/dispatch.rs b/src/server/dispatch.rs index 4a21ec6..423008e 100644 --- a/src/server/dispatch.rs +++ b/src/server/dispatch.rs @@ -1296,7 +1296,7 @@ impl GlobalDispatch for ServerState { data: &Global, data_init: &mut wayland_server::DataInit<'_, Self>, ) { - state.objects.insert_with_key(|key| { + let key = state.objects.insert_with_key(|key| { let server = data_init.init(resource, key); let client = state .clientside @@ -1310,6 +1310,7 @@ impl GlobalDispatch for ServerState { ); Output::new(client, server).into() }); + state.output_keys.insert(key, ()); } } global_dispatch_with_events!(WlDrmServer, WlDrmClient); diff --git a/src/server/event.rs b/src/server/event.rs index 8190881..470f228 100644 --- a/src/server/event.rs +++ b/src/server/event.rs @@ -128,13 +128,12 @@ impl SurfaceData { debug!("{} entered {}", self.server.id(), output.server.id()); let windows = &mut state.windows; if let Some(win_data) = self.window.as_ref().and_then(|win| windows.get_mut(win)) { - 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, y }, + WindowOutputOffset { + x: output.dimensions.x, + y: output.dimensions.y, + }, state.connection.as_mut().unwrap(), ); let window = win_data.window; @@ -656,9 +655,18 @@ pub struct XdgOutput { } #[derive(Copy, Clone)] -pub enum OutputPosition { - Wl { x: i32, y: i32 }, - Xdg { x: i32, y: i32 }, +enum OutputDimensionsSource { + Wl, + Xdg, +} + +#[derive(Copy, Clone)] +pub(super) struct OutputDimensions { + source: OutputDimensionsSource, + x: i32, + y: i32, + pub width: i32, + pub height: i32, } pub struct Output { @@ -666,7 +674,7 @@ pub struct Output { pub server: WlOutput, pub xdg: Option, windows: HashSet, - position: OutputPosition, + pub(super) dimensions: OutputDimensions, name: String, } @@ -677,7 +685,13 @@ impl Output { server, xdg: None, windows: HashSet::new(), - position: OutputPosition::Wl { x: 0, y: 0 }, + dimensions: OutputDimensions { + source: OutputDimensionsSource::Wl, + x: 0, + y: 0, + width: 0, + height: 0, + }, name: "".to_string(), } } @@ -715,19 +729,23 @@ impl HandleEvent for Output { impl Output { fn update_offset( &mut self, - offset: OutputPosition, + source: OutputDimensionsSource, + x: i32, + y: i32, state: &mut ServerState, ) { - if matches!(offset, OutputPosition::Wl { .. }) - && matches!(self.position, OutputPosition::Xdg { .. }) + if matches!(source, OutputDimensionsSource::Wl) + && matches!(self.dimensions.source, OutputDimensionsSource::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()), + self.dimensions.source = source; + self.dimensions.x = x; + self.dimensions.y = y; + let id = match source { + OutputDimensionsSource::Xdg => self.xdg.as_ref().unwrap().server.id(), + OutputDimensionsSource::Wl => self.server.id(), }; debug!("moving {id} to {x}x{y}"); self.windows.retain(|window| { @@ -750,7 +768,15 @@ impl Output { state: &mut ServerState, ) { if let client::wl_output::Event::Geometry { x, y, .. } = event { - self.update_offset(OutputPosition::Wl { x, y }, state); + self.update_offset(OutputDimensionsSource::Wl, x, y, state); + } + + if let client::wl_output::Event::Mode { width, height, .. } = event { + if matches!(self.dimensions.source, OutputDimensionsSource::Wl) { + self.dimensions.width = width; + self.dimensions.height = height; + debug!("{} dimensions: {width}x{height} (wl)", self.server.id()); + } } simple_event_shunt! { @@ -790,9 +816,15 @@ impl Output { state: &mut ServerState, ) { if let zxdg_output_v1::Event::LogicalPosition { x, y } = event { - self.update_offset(OutputPosition::Xdg { x, y }, state); + self.update_offset(OutputDimensionsSource::Xdg, x, y, state); } let xdg = &self.xdg.as_ref().unwrap().server; + if let zxdg_output_v1::Event::LogicalSize { width, height } = event { + self.dimensions.source = OutputDimensionsSource::Xdg; + self.dimensions.width = width; + self.dimensions.height = height; + debug!("{} dimensions: {width}x{height} (xdg)", self.server.id()); + } simple_event_shunt! { xdg, event: zxdg_output_v1::Event => [ LogicalPosition { x, y }, diff --git a/src/server/mod.rs b/src/server/mod.rs index 0b5f3bf..c79edce 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -480,6 +480,7 @@ pub struct ServerState { clientside: ClientState, objects: ObjectMap, associated_windows: SparseSecondaryMap, + output_keys: SparseSecondaryMap, windows: HashMap, qh: ClientQueueHandle, @@ -539,6 +540,7 @@ impl ServerState { last_hovered: None, connection: None, objects: Default::default(), + output_keys: Default::default(), associated_windows: Default::default(), xdg_wm_base, clipboard_data, @@ -922,15 +924,20 @@ impl ServerState { } fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) { - let surface: &mut SurfaceData = self.objects[surface_key].as_mut(); + // Temporarily remove surface to placate borrow checker + let mut surface: SurfaceData = self.objects[surface_key] + .0 + .take() + .unwrap() + .try_into() + .unwrap(); surface.window = Some(window); - let client = &surface.client; - client.attach(None, 0, 0); - client.commit(); + surface.client.attach(None, 0, 0); + surface.client.commit(); let xdg_surface = self .xdg_wm_base - .get_xdg_surface(client, &self.qh, surface_key); + .get_xdg_surface(&surface.client, &self.qh, surface_key); let window_data = self.windows.get_mut(&window).unwrap(); if window_data.attrs.override_redirect { @@ -941,15 +948,27 @@ impl ServerState { window_data.attrs.popup_for = Some(win); } } + + let mut fullscreen = false; + let (width, height) = (window_data.attrs.dims.width, window_data.attrs.dims.height); + for (key, _) in &self.output_keys { + let output: &Output = self.objects[key].as_ref(); + if output.dimensions.width == width as i32 && output.dimensions.height == height as i32 + { + fullscreen = true; + window_data.attrs.popup_for = None; + } + } + + let surface_id = surface.client.id(); + // Reinsert surface + self.objects[surface_key].0 = Some(surface.into()); let window = self.windows.get(&window).unwrap(); let role = if let Some(parent) = window.attrs.popup_for { debug!( "creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}", - window.window, - parent, - window.attrs.dims, - client.id() + window.window, parent, window.attrs.dims, surface_id ); let parent_window = self.windows.get(&parent).unwrap(); @@ -988,7 +1007,7 @@ impl ServerState { }; SurfaceRole::Popup(Some(popup)) } else { - let data = self.create_toplevel(window, surface_key, xdg_surface); + let data = self.create_toplevel(window, surface_key, xdg_surface, fullscreen); SurfaceRole::Toplevel(Some(data)) }; @@ -1013,6 +1032,7 @@ impl ServerState { window: &WindowData, surface_key: ObjectKey, xdg: XdgSurface, + fullscreen: bool, ) -> ToplevelData { debug!("creating toplevel for {:?}", window.window); @@ -1044,6 +1064,10 @@ impl ServerState { toplevel.set_title(title.name().to_string()); } + if fullscreen { + toplevel.set_fullscreen(None); + } + ToplevelData { xdg: XdgSurfaceData { surface: xdg, diff --git a/src/server/tests.rs b/src/server/tests.rs index 8e0ed2f..59cc419 100644 --- a/src/server/tests.rs +++ b/src/server/tests.rs @@ -1651,6 +1651,60 @@ fn tablet_smoke_test() { ], ) } + +#[test] +fn fullscreen_heuristic() { + let (mut f, comp) = TestFixture::new_with_compositor(); + let (_, output) = f.new_output(0, 0); + + let window1 = unsafe { Window::new(1) }; + let (_, id) = f.create_toplevel(&comp, window1); + f.testwl.move_surface_to_output(id, &output); + f.run(); + + let mut check_fullscreen = |id, override_redirect| { + let window = unsafe { Window::new(id) }; + let (buffer, surface) = comp.create_surface(); + let data = WindowData { + mapped: true, + dims: WindowDims { + x: 0, + y: 0, + // Outputs default to 1000x1000 in testwl + width: 1000, + height: 1000, + }, + fullscreen: false, + }; + f.new_window(window, override_redirect, data, None); + f.map_window(&comp, window, &surface.obj, &buffer); + f.run(); + let id = f.check_new_surface(); + let surface_data = f.testwl.get_surface_data(id).unwrap(); + assert!( + surface_data.surface + == f.testwl + .get_object::(id) + .unwrap() + ); + + let Some(testwl::SurfaceRole::Toplevel(toplevel_data)) = &surface_data.role else { + panic!("Expected toplevel, got {:?}", surface_data.role); + }; + + assert!( + toplevel_data + .states + .contains(&xdg_toplevel::State::Fullscreen), + "states: {:?}", + toplevel_data.states + ); + }; + + check_fullscreen(2, false); + check_fullscreen(3, true); +} + /// See Pointer::handle_event for an explanation. #[test] fn popup_pointer_motion_workaround() {}