Implement maximize-to-edges (true Wayland maximize)

This commit is contained in:
Ivan Molodetskikh
2025-09-02 08:07:22 +03:00
parent e5d4e7c1b1
commit e1fad994da
251 changed files with 4858 additions and 280 deletions
+222 -12
View File
@@ -428,18 +428,205 @@ impl XdgShellHandler for State {
});
}
fn maximize_request(&mut self, surface: ToplevelSurface) {
// FIXME
fn maximize_request(&mut self, toplevel: ToplevelSurface) {
if let Some((mapped, _)) = self
.niri
.layout
.find_window_and_output_mut(toplevel.wl_surface())
{
// A configure is required in response to this event regardless if there are pending
// changes.
mapped.set_needs_configure();
// A configure is required in response to this event. However, if an initial configure
// wasn't sent, then we will send this as part of the initial configure later.
if surface.is_initial_configure_sent() {
surface.send_configure();
let window = mapped.window.clone();
self.niri.layout.set_maximized(&window, true);
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
match &mut unmapped.state {
InitialConfigureState::NotConfigured {
wants_maximized, ..
} => {
*wants_maximized = true;
// The required configure will be the initial configure.
}
InitialConfigureState::Configured {
rules,
output,
is_pending_maximized,
..
} => {
// Figure out the monitor following a similar logic to initial configure.
// FIXME: deduplicate.
let mon = output
.as_ref()
.and_then(|o| self.niri.layout.monitor_for_output(o))
.map(|mon| (mon, false))
// If not, check if we have a parent with a monitor.
.or_else(|| {
toplevel
.parent()
.and_then(|parent| self.niri.layout.find_window_and_output(&parent))
.and_then(|(_win, output)| output)
.and_then(|o| self.niri.layout.monitor_for_output(o))
.map(|mon| (mon, true))
})
// If not, fall back to the active monitor.
.or_else(|| {
self.niri
.layout
.active_monitor_ref()
.map(|mon| (mon, false))
});
*output = mon
.filter(|(_, parent)| !parent)
.map(|(mon, _)| mon.output().clone());
let mon = mon.map(|(mon, _)| mon);
let ws = mon
.map(|mon| mon.active_workspace_ref())
.or_else(|| self.niri.layout.active_workspace());
if let Some(ws) = ws {
// If the window is pending fullscreen, then this will do nothing. But
// that's expected: the window remains fullscreen, and we simply remember
// that it is now pending maximized.
*is_pending_maximized = true;
toplevel.with_pending_state(|state| {
if !state.states.contains(xdg_toplevel::State::Fullscreen) {
state.states.set(xdg_toplevel::State::Maximized);
}
});
ws.configure_new_window(&unmapped.window, None, None, false, rules);
}
// We already sent the initial configure, so we need to reconfigure.
toplevel.send_configure();
}
}
} else {
error!("couldn't find the toplevel in maximize_request()");
toplevel.send_configure();
}
}
fn unmaximize_request(&mut self, _surface: ToplevelSurface) {
// FIXME
fn unmaximize_request(&mut self, toplevel: ToplevelSurface) {
if let Some((mapped, _)) = self
.niri
.layout
.find_window_and_output_mut(toplevel.wl_surface())
{
// A configure is required in response to this event regardless if there are pending
// changes.
mapped.set_needs_configure();
let window = mapped.window.clone();
self.niri.layout.set_maximized(&window, false);
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
match &mut unmapped.state {
InitialConfigureState::NotConfigured {
wants_maximized, ..
} => {
*wants_maximized = false;
// The required configure will be the initial configure.
}
InitialConfigureState::Configured {
rules,
width,
height,
floating_width,
floating_height,
is_full_width,
output,
workspace_name,
is_pending_maximized,
} => {
// Figure out the monitor following a similar logic to initial configure.
// FIXME: deduplicate.
let mon = workspace_name
.as_deref()
.and_then(|name| self.niri.layout.monitor_for_workspace(name))
.map(|mon| (mon, false));
let mon = mon.or_else(|| {
output
.as_ref()
.and_then(|o| self.niri.layout.monitor_for_output(o))
.map(|mon| (mon, false))
// If not, check if we have a parent with a monitor.
.or_else(|| {
toplevel
.parent()
.and_then(|parent| {
self.niri.layout.find_window_and_output(&parent)
})
.and_then(|(_win, output)| output)
.and_then(|o| self.niri.layout.monitor_for_output(o))
.map(|mon| (mon, true))
})
// If not, fall back to the active monitor.
.or_else(|| {
self.niri
.layout
.active_monitor_ref()
.map(|mon| (mon, false))
})
});
*output = mon
.filter(|(_, parent)| !parent)
.map(|(mon, _)| mon.output().clone());
let mon = mon.map(|(mon, _)| mon);
let ws = workspace_name
.as_deref()
.and_then(|name| mon.map(|mon| mon.find_named_workspace(name)))
.unwrap_or_else(|| {
mon.map(|mon| mon.active_workspace_ref())
.or_else(|| self.niri.layout.active_workspace())
});
if let Some(ws) = ws {
// If the window is pending fullscreen, then this will do nothing since
// then the Maximized state is already unset. But that's expected: the
// window remains fullscreen, and we simply remember that it is no
// longer pending maximized.
*is_pending_maximized = false;
toplevel.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Maximized);
});
let is_floating = rules.compute_open_floating(&toplevel);
let configure_width = if is_floating {
*floating_width
} else if *is_full_width {
Some(PresetSize::Proportion(1.))
} else {
*width
};
let configure_height = if is_floating {
*floating_height
} else {
*height
};
ws.configure_new_window(
&unmapped.window,
configure_width,
configure_height,
is_floating,
rules,
);
}
// We already sent the initial configure, so we need to reconfigure.
toplevel.send_configure();
}
}
} else {
error!("couldn't find the toplevel in unmaximize_request()");
toplevel.send_configure();
}
}
fn fullscreen_request(
@@ -474,7 +661,9 @@ impl XdgShellHandler for State {
self.niri.layout.set_fullscreen(&window, true);
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
match &mut unmapped.state {
InitialConfigureState::NotConfigured { wants_fullscreen } => {
InitialConfigureState::NotConfigured {
wants_fullscreen, ..
} => {
*wants_fullscreen = Some(requested_output);
// The required configure will be the initial configure.
@@ -517,6 +706,7 @@ impl XdgShellHandler for State {
if let Some(ws) = ws {
toplevel.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
state.states.unset(xdg_toplevel::State::Maximized);
});
ws.configure_new_window(&unmapped.window, None, None, false, rules);
}
@@ -545,7 +735,9 @@ impl XdgShellHandler for State {
self.niri.layout.set_fullscreen(&window, false);
} else if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) {
match &mut unmapped.state {
InitialConfigureState::NotConfigured { wants_fullscreen } => {
InitialConfigureState::NotConfigured {
wants_fullscreen, ..
} => {
*wants_fullscreen = None;
// The required configure will be the initial configure.
@@ -559,6 +751,7 @@ impl XdgShellHandler for State {
is_full_width,
output,
workspace_name,
is_pending_maximized,
} => {
// Figure out the monitor following a similar logic to initial configure.
// FIXME: deduplicate.
@@ -608,6 +801,10 @@ impl XdgShellHandler for State {
if let Some(ws) = ws {
toplevel.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Fullscreen);
if *is_pending_maximized {
state.states.set(xdg_toplevel::State::Maximized);
}
});
let is_floating = rules.compute_open_floating(&toplevel);
@@ -858,7 +1055,11 @@ impl State {
let Unmapped { window, state, .. } = unmapped;
let InitialConfigureState::NotConfigured { wants_fullscreen } = state else {
let InitialConfigureState::NotConfigured {
wants_fullscreen,
wants_maximized,
} = state
else {
error!("window must not be already configured in send_initial_configure()");
return;
};
@@ -934,14 +1135,22 @@ impl State {
.or_else(|| self.niri.layout.active_workspace())
});
let mut is_pending_maximized = false;
if let Some(ws) = ws {
// Set a fullscreen state based on window request and window rule.
// Set a fullscreen and maximized state based on window request and window rule.
is_pending_maximized = (*wants_maximized && rules.open_maximized_to_edges.is_none())
|| rules.open_maximized_to_edges == Some(true);
if (wants_fullscreen.is_some() && rules.open_fullscreen.is_none())
|| rules.open_fullscreen == Some(true)
{
toplevel.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Fullscreen);
});
} else if is_pending_maximized {
toplevel.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Maximized);
});
}
width = ws.resolve_default_width(rules.default_width, false);
@@ -979,6 +1188,7 @@ impl State {
is_full_width,
output,
workspace_name: ws.and_then(|w| w.name().cloned()),
is_pending_maximized,
};
trace!(surface = %toplevel.wl_surface().id(), "sending initial configure");