mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
Implement maximize-to-edges (true Wayland maximize)
This commit is contained in:
+222
-12
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user