aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tests/fullscreen.rs155
-rw-r--r--src/tests/mod.rs1
-rw-r--r--src/window/mapped.rs51
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
+ }
+ });
}
}