aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-03-17 14:56:29 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-03-17 22:31:19 -0700
commit39f52b75856936db01325aa1c7f15fe8f379485d (patch)
tree08fcbf8c938aa2b10a9be3014bf292f17225063d /src/layout
parentb447b1f4de65ee706bdbbdb9b650ab030459c0fb (diff)
downloadniri-39f52b75856936db01325aa1c7f15fe8f379485d.tar.gz
niri-39f52b75856936db01325aa1c7f15fe8f379485d.tar.bz2
niri-39f52b75856936db01325aa1c7f15fe8f379485d.zip
Implement toggle-windowed-fullscreen
Windowed, or fake, or detached, fullscreen, is when a window thinks that it's fullscreen, but the compositor treats it as a normal window.
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/mod.rs41
-rw-r--r--src/layout/tests.rs75
2 files changed, 116 insertions, 0 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 15d48d6a..64e6877c 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -244,6 +244,13 @@ pub trait LayoutElement {
Some(requested)
}
+ fn is_pending_windowed_fullscreen(&self) -> bool {
+ false
+ }
+ fn request_windowed_fullscreen(&mut self, value: bool) {
+ let _ = value;
+ }
+
fn is_child_of(&self, parent: &Self) -> bool;
fn rules(&self) -> &ResolvedWindowRules;
@@ -3458,6 +3465,20 @@ impl<W: LayoutElement> Layout<W> {
}
pub fn set_fullscreen(&mut self, id: &W::Id, is_fullscreen: bool) {
+ // Check if this is a request to unset the windowed fullscreen state.
+ if !is_fullscreen {
+ let mut handled = false;
+ self.with_windows_mut(|window, _| {
+ if window.id() == id && window.is_pending_windowed_fullscreen() {
+ window.request_windowed_fullscreen(false);
+ handled = true;
+ }
+ });
+ if handled {
+ return;
+ }
+ }
+
if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move {
if move_.tile.window().id() == id {
return;
@@ -3487,6 +3508,26 @@ impl<W: LayoutElement> Layout<W> {
}
}
+ pub fn toggle_windowed_fullscreen(&mut self, id: &W::Id) {
+ let (_, window) = self.windows().find(|(_, win)| win.id() == id).unwrap();
+ if window.is_pending_fullscreen() {
+ // Remove the real fullscreen.
+ for ws in self.workspaces_mut() {
+ if ws.has_window(id) {
+ ws.set_fullscreen(id, false);
+ break;
+ }
+ }
+ }
+
+ // This will switch is_pending_fullscreen() to false right away.
+ self.with_windows_mut(|window, _| {
+ if window.id() == id {
+ window.request_windowed_fullscreen(!window.is_pending_windowed_fullscreen());
+ }
+ });
+ }
+
pub fn workspace_switch_gesture_begin(&mut self, output: &Output, is_touchpad: bool) {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
diff --git a/src/layout/tests.rs b/src/layout/tests.rs
index 266b615e..8e1c3adf 100644
--- a/src/layout/tests.rs
+++ b/src/layout/tests.rs
@@ -29,6 +29,8 @@ struct TestWindowInner {
pending_fullscreen: Cell<bool>,
pending_activated: Cell<bool>,
is_fullscreen: Cell<bool>,
+ is_windowed_fullscreen: Cell<bool>,
+ is_pending_windowed_fullscreen: Cell<bool>,
}
#[derive(Debug, Clone)]
@@ -72,6 +74,8 @@ impl TestWindow {
pending_fullscreen: Cell::new(false),
pending_activated: Cell::new(false),
is_fullscreen: Cell::new(false),
+ is_windowed_fullscreen: Cell::new(false),
+ is_pending_windowed_fullscreen: Cell::new(false),
}))
}
@@ -101,6 +105,13 @@ impl TestWindow {
changed = true;
}
+ if self.0.is_windowed_fullscreen.get() != self.0.is_pending_windowed_fullscreen.get() {
+ self.0
+ .is_windowed_fullscreen
+ .set(self.0.is_pending_windowed_fullscreen.get());
+ changed = true;
+ }
+
changed
}
}
@@ -144,6 +155,10 @@ impl LayoutElement for TestWindow {
) {
self.0.requested_size.set(Some(size));
self.0.pending_fullscreen.set(is_fullscreen);
+
+ if is_fullscreen {
+ self.0.is_pending_windowed_fullscreen.set(false);
+ }
}
fn min_size(&self) -> Size<i32, Logical> {
@@ -191,10 +206,18 @@ impl LayoutElement for TestWindow {
fn set_floating(&mut self, _floating: bool) {}
fn is_fullscreen(&self) -> bool {
+ if self.0.is_windowed_fullscreen.get() {
+ return false;
+ }
+
self.0.is_fullscreen.get()
}
fn is_pending_fullscreen(&self) -> bool {
+ if self.0.is_pending_windowed_fullscreen.get() {
+ return false;
+ }
+
self.0.pending_fullscreen.get()
}
@@ -202,6 +225,14 @@ impl LayoutElement for TestWindow {
self.0.requested_size.get()
}
+ fn is_pending_windowed_fullscreen(&self) -> bool {
+ self.0.is_pending_windowed_fullscreen.get()
+ }
+
+ fn request_windowed_fullscreen(&mut self, value: bool) {
+ self.0.is_pending_windowed_fullscreen.set(value);
+ }
+
fn is_child_of(&self, parent: &Self) -> bool {
self.0.parent_id.get() == Some(parent.0.id)
}
@@ -374,6 +405,7 @@ enum Op {
window: usize,
is_fullscreen: bool,
},
+ ToggleWindowedFullscreen(#[proptest(strategy = "1..=5usize")] usize),
FocusColumnLeft,
FocusColumnRight,
FocusColumnFirst,
@@ -901,14 +933,26 @@ impl Op {
layout.remove_window(&id, Transaction::new());
}
Op::FullscreenWindow(id) => {
+ if !layout.has_window(&id) {
+ return;
+ }
layout.toggle_fullscreen(&id);
}
Op::SetFullscreenWindow {
window,
is_fullscreen,
} => {
+ if !layout.has_window(&window) {
+ return;
+ }
layout.set_fullscreen(&window, is_fullscreen);
}
+ Op::ToggleWindowedFullscreen(id) => {
+ if !layout.has_window(&id) {
+ return;
+ }
+ layout.toggle_windowed_fullscreen(&id);
+ }
Op::FocusColumnLeft => layout.focus_left(),
Op::FocusColumnRight => layout.focus_right(),
Op::FocusColumnFirst => layout.focus_column_first(),
@@ -3187,6 +3231,37 @@ fn unfullscreen_with_large_border() {
check_ops_with_options(options, &ops);
}
+#[test]
+fn fullscreen_to_windowed_fullscreen() {
+ let ops = [
+ Op::AddOutput(0),
+ Op::AddWindow {
+ params: TestWindowParams::new(0),
+ },
+ Op::FullscreenWindow(0),
+ Op::Communicate(0), // Make sure it goes into fullscreen.
+ Op::ToggleWindowedFullscreen(0),
+ ];
+
+ check_ops(&ops);
+}
+
+#[test]
+fn windowed_fullscreen_to_fullscreen() {
+ let ops = [
+ Op::AddOutput(0),
+ Op::AddWindow {
+ params: TestWindowParams::new(0),
+ },
+ Op::FullscreenWindow(0),
+ Op::Communicate(0), // Commit fullscreen state.
+ Op::ToggleWindowedFullscreen(0), // Switch is_fullscreen() to false.
+ Op::FullscreenWindow(0), // Switch is_fullscreen() back to true.
+ ];
+
+ check_ops(&ops);
+}
+
fn parent_id_causes_loop(layout: &Layout<TestWindow>, id: usize, mut parent_id: usize) -> bool {
if parent_id == id {
return true;