diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/tests/fullscreen.rs | 155 | ||||
| -rw-r--r-- | src/tests/mod.rs | 1 | ||||
| -rw-r--r-- | src/window/mapped.rs | 51 |
3 files changed, 195 insertions, 12 deletions
diff --git a/src/tests/fullscreen.rs b/src/tests/fullscreen.rs new file mode 100644 index 00000000..6a39d89a --- /dev/null +++ b/src/tests/fullscreen.rs @@ -0,0 +1,155 @@ +use client::ClientId; +use insta::assert_snapshot; +use wayland_client::protocol::wl_surface::WlSurface; + +use super::*; +use crate::layout::LayoutElement as _; + +// Sets up a fixture with two outputs and 100×100 window. +fn set_up() -> (Fixture, ClientId, WlSurface) { + let mut f = Fixture::new(); + f.add_output(1, (1920, 1080)); + f.add_output(2, (1280, 720)); + + let id = f.add_client(); + let window = f.client(id).create_window(); + let surface = window.surface.clone(); + window.commit(); + f.roundtrip(id); + + let window = f.client(id).window(&surface); + window.attach_new_buffer(); + window.set_size(100, 100); + window.ack_last_and_commit(); + f.double_roundtrip(id); + + (f, id, surface) +} + +#[test] +fn windowed_fullscreen() { + let (mut f, id, surface) = set_up(); + + let _ = f.client(id).window(&surface).recent_configures(); + + let niri = f.niri(); + let mapped = niri.layout.windows().next().unwrap().1; + let window_id = mapped.window.clone(); + + // Enable windowed fullscreen. + niri.layout.toggle_windowed_fullscreen(&window_id); + f.double_roundtrip(id); + + // Should request fullscreen state with the tiled size. + let window = f.client(id).window(&surface); + assert_snapshot!( + window.format_recent_configures(), + @"size: 936 × 1048, bounds: 1888 × 1048, states: [Activated, Fullscreen]" + ); + + let mapped = f.niri().layout.windows().next().unwrap().1; + // Not committed yet. + assert!(!mapped.is_windowed_fullscreen()); + + // Commit in response. + let window = f.client(id).window(&surface); + window.ack_last_and_commit(); + f.roundtrip(id); + + let mapped = f.niri().layout.windows().next().unwrap().1; + // Now it is committed. + assert!(mapped.is_windowed_fullscreen()); + + // Disable windowed fullscreen. + f.niri().layout.toggle_windowed_fullscreen(&window_id); + f.double_roundtrip(id); + + // Should request without fullscreen state with the tiled size. + let window = f.client(id).window(&surface); + assert_snapshot!( + window.format_recent_configures(), + @"size: 936 × 1048, bounds: 1888 × 1048, states: [Activated]" + ); + + let mapped = f.niri().layout.windows().next().unwrap().1; + // Not commited yet. + assert!(mapped.is_windowed_fullscreen()); + + // Commit in response. + let window = f.client(id).window(&surface); + window.ack_last_and_commit(); + f.roundtrip(id); + + let mapped = f.niri().layout.windows().next().unwrap().1; + // Now it is committed. + assert!(!mapped.is_windowed_fullscreen()); +} + +#[test] +fn windowed_fullscreen_chain() { + let (mut f, id, surface) = set_up(); + + let _ = f.client(id).window(&surface).recent_configures(); + + let mapped = f.niri().layout.windows().next().unwrap().1; + let window_id = mapped.window.clone(); + + f.niri().layout.toggle_windowed_fullscreen(&window_id); + f.roundtrip(id); + f.niri().layout.toggle_windowed_fullscreen(&window_id); + f.roundtrip(id); + f.niri().layout.toggle_windowed_fullscreen(&window_id); + f.roundtrip(id); + f.niri().layout.toggle_windowed_fullscreen(&window_id); + f.double_roundtrip(id); + + // Should be four configures matching the four requests. + let window = f.client(id).window(&surface); + assert_snapshot!( + window.format_recent_configures(), + @r" + size: 936 × 1048, bounds: 1888 × 1048, states: [Activated, Fullscreen] + size: 936 × 1048, bounds: 1888 × 1048, states: [Activated] + size: 936 × 1048, bounds: 1888 × 1048, states: [Activated, Fullscreen] + size: 936 × 1048, bounds: 1888 × 1048, states: [Activated] + " + ); + + let window = f.client(id).window(&surface); + let serials = Vec::from_iter( + window.configures_received[window.configures_received.len() - 4..] + .iter() + .map(|(s, _c)| *s), + ); + + let get_state = |f: &mut Fixture| { + let mapped = f.niri().layout.windows().next().unwrap().1; + format!( + "fs {}, wfs {}", + mapped.is_fullscreen(), + mapped.is_windowed_fullscreen() + ) + }; + + let mut states = vec![get_state(&mut f)]; + for serial in serials { + let window = f.client(id).window(&surface); + window.xdg_surface.ack_configure(serial); + window.commit(); + f.roundtrip(id); + states.push(get_state(&mut f)); + } + + // We expect fs to always be false (because each Fullscreen state request corresponded to a + // windowed fullscreen), and wfs to toggle on and off. + assert_snapshot!( + states.join("\n"), + @r" + fs false, wfs false + fs false, wfs true + fs false, wfs false + fs false, wfs true + fs false, wfs false + " + ); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index cdb1c072..87841cbc 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -5,4 +5,5 @@ mod fixture; mod server; mod floating; +mod fullscreen; mod window_opening; diff --git a/src/window/mapped.rs b/src/window/mapped.rs index 29dc185a..82734220 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -143,13 +143,19 @@ pub struct Mapped { /// its fullscreen size. Then turning on is_windowed_fullscreen will both keep the /// fullscreen state, and keep the size (since it matches), resulting in no configure. /// - /// So we work around this by "committing" is_pending_windowed_fullscreen to - /// is_windowed_fullscreen on receiving any actual window commit, and whenever - /// is_pending_windowed_fullscreen changes, we mark the window as needs_configure. This does - /// mean some unnecessary delays in some cases, but it also means being able to better - /// synchronize our windowed fullscreen state to the real window updates, so that's good I - /// guess. + /// So we work around this by emulating a configure-ack/commit cycle through + /// is_pending_windowed_fullscreen and uncommited_windowed_fullscreen. We ensure we send actual + /// configures in all cases through needs_configure. This can result in unnecessary configures + /// (like in the example above), but in most cases there will be a configure anyway to change + /// the Fullscreen state and/or the size. What this gives us is being able to synchronize our + /// windowed fullscreen state to the real window updates to avoid any flickering. is_pending_windowed_fullscreen: bool, + + /// Pending windowed fullscreen updates. + /// + /// These have been "sent" to the window in form of configures, but the window hadn't committed + /// in response yet. + uncommited_windowed_fullscreen: Vec<(Serial, bool)>, } niri_render_elements! { @@ -241,6 +247,7 @@ impl Mapped { last_interactive_resize_start: Cell::new(None), is_windowed_fullscreen: false, is_pending_windowed_fullscreen: false, + uncommited_windowed_fullscreen: Vec::new(), } } @@ -499,6 +506,10 @@ impl Mapped { pub fn update_tiled_state(&self, prefer_no_csd: bool) { update_tiled_state(self.toplevel(), prefer_no_csd, self.rules.tiled_state); } + + pub fn is_windowed_fullscreen(&self) -> bool { + self.is_windowed_fullscreen + } } impl Drop for Mapped { @@ -966,6 +977,18 @@ impl LayoutElement for Mapped { if let Some(RequestSizeOnce::WaitingForConfigure) = self.request_size_once { self.request_size_once = Some(RequestSizeOnce::WaitingForCommit(serial)); } + + // If is_pending_windowed_fullscreen changed compared to the last value that we "sent" + // to the window, store the configure serial. + let last_sent_windowed_fullscreen = self + .uncommited_windowed_fullscreen + .last() + .map(|(_, value)| *value) + .unwrap_or(self.is_windowed_fullscreen); + if last_sent_windowed_fullscreen != self.is_pending_windowed_fullscreen { + self.uncommited_windowed_fullscreen + .push((serial, self.is_pending_windowed_fullscreen)); + } } else { self.interactive_resize = match self.interactive_resize.take() { // We probably started and stopped resizing in the same loop cycle without anything @@ -1180,11 +1203,15 @@ impl LayoutElement for Mapped { } } - // HACK: this is not really accurate because the commit might be for an earlier serial than - // when we requested windowed fullscren. But we don't actually care much, since this is - // entirely compositor state. We're only tying it to configure/commit as a workaround to - // the rest of the code expecting that fullscreen doesn't suddenly just change in the - // middle of something. - self.is_windowed_fullscreen = self.is_pending_windowed_fullscreen; + // "Commit" our "acked" pending windowed fullscreen state. + self.uncommited_windowed_fullscreen + .retain_mut(|(serial, value)| { + if commit_serial.is_no_older_than(serial) { + self.is_windowed_fullscreen = *value; + false + } else { + true + } + }); } } |
