aboutsummaryrefslogtreecommitdiff
path: root/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'src/handlers')
-rw-r--r--src/handlers/compositor.rs91
-rw-r--r--src/handlers/mod.rs14
-rw-r--r--src/handlers/xdg_shell.rs234
3 files changed, 300 insertions, 39 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs
index cf2efaaf..a7761824 100644
--- a/src/handlers/compositor.rs
+++ b/src/handlers/compositor.rs
@@ -20,7 +20,7 @@ use smithay::{delegate_compositor, delegate_shm};
use super::xdg_shell::add_mapped_toplevel_pre_commit_hook;
use crate::handlers::XDG_ACTIVATION_TOKEN_TIMEOUT;
-use crate::layout::{ActivateWindow, AddWindowTarget};
+use crate::layout::{ActivateWindow, AddWindowTarget, LayoutElement as _};
use crate::niri::{CastTarget, ClientState, LockState, State};
use crate::utils::transaction::Transaction;
use crate::utils::{is_mapped, send_scale_transform};
@@ -91,35 +91,59 @@ impl CompositorHandler for State {
let toplevel = window.toplevel().expect("no X11 support");
- let (rules, width, height, is_full_width, output, workspace_id) =
- if let InitialConfigureState::Configured {
+ let (
+ rules,
+ width,
+ height,
+ is_full_width,
+ output,
+ workspace_id,
+ is_pending_maximized,
+ ) = if let InitialConfigureState::Configured {
+ rules,
+ width,
+ height,
+ floating_width: _,
+ floating_height: _,
+ is_full_width,
+ output,
+ workspace_name,
+ is_pending_maximized,
+ } = state
+ {
+ // Check that the output is still connected.
+ let output =
+ output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
+
+ // Check that the workspace still exists.
+ let workspace_id = workspace_name
+ .as_deref()
+ .and_then(|n| self.niri.layout.find_workspace_by_name(n))
+ .map(|(_, ws)| ws.id());
+
+ (
rules,
width,
height,
- floating_width: _,
- floating_height: _,
is_full_width,
output,
- workspace_name,
- } = state
- {
- // Check that the output is still connected.
- let output =
- output.filter(|o| self.niri.layout.monitor_for_output(o).is_some());
-
- // Check that the workspace still exists.
- let workspace_id = workspace_name
- .as_deref()
- .and_then(|n| self.niri.layout.find_workspace_by_name(n))
- .map(|(_, ws)| ws.id());
-
- (rules, width, height, is_full_width, output, workspace_id)
- } else {
- // Can happen when a surface unmaps by attaching a null buffer while
- // there are in-flight pending configures.
- debug!("window mapped without proper initial configure");
- (ResolvedWindowRules::empty(), None, None, false, None, None)
- };
+ workspace_id,
+ is_pending_maximized,
+ )
+ } else {
+ // Can happen when a surface unmaps by attaching a null buffer while
+ // there are in-flight pending configures.
+ debug!("window mapped without proper initial configure");
+ (
+ ResolvedWindowRules::empty(),
+ None,
+ None,
+ false,
+ None,
+ None,
+ false,
+ )
+ };
// The GTK about dialog sets min/max size after the initial configure but
// before mapping, so we need to compute open_floating at the last possible
@@ -169,7 +193,7 @@ impl CompositorHandler for State {
.map(|(mapped, _)| mapped.window.clone());
// The mapped pre-commit hook deals with dma-bufs on its own.
- self.remove_default_dmabuf_pre_commit_hook(toplevel.wl_surface());
+ self.remove_default_dmabuf_pre_commit_hook(surface);
let hook = add_mapped_toplevel_pre_commit_hook(toplevel);
let mapped = Mapped::new(window, rules, hook);
let window = mapped.window.clone();
@@ -193,8 +217,21 @@ impl CompositorHandler for State {
is_floating,
activate,
);
+ let output = output.cloned();
+
+ // The window state cannot contain Fullscreen and Maximized at once. Therefore,
+ // if the window ended up fullscreen, then we only know that it is also
+ // maximized from the is_pending_maximized variable. Tell the layout about it
+ // here so that unfullscreening the window makes it maximized.
+ if let Some((mapped, _)) = self.niri.layout.find_window_and_output(surface) {
+ if mapped.pending_sizing_mode().is_fullscreen() && is_pending_maximized {
+ self.niri.layout.set_maximized(&window, true);
+ }
+ } else {
+ error!("layout is missing the window that we just added");
+ }
- if let Some(output) = output.cloned() {
+ if let Some(output) = output {
self.niri.layout.start_open_animation_for_window(&window);
let new_focus = self.niri.layout.focus().map(|m| &m.window);
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs
index 3b673fa0..73575b45 100644
--- a/src/handlers/mod.rs
+++ b/src/handlers/mod.rs
@@ -550,6 +550,20 @@ impl ForeignToplevelHandler for State {
self.niri.layout.set_fullscreen(&window, false);
}
}
+
+ fn set_maximized(&mut self, wl_surface: WlSurface) {
+ if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
+ let window = mapped.window.clone();
+ self.niri.layout.set_maximized(&window, true);
+ }
+ }
+
+ fn unset_maximized(&mut self, wl_surface: WlSurface) {
+ if let Some((mapped, _)) = self.niri.layout.find_window_and_output(&wl_surface) {
+ let window = mapped.window.clone();
+ self.niri.layout.set_maximized(&window, false);
+ }
+ }
}
delegate_foreign_toplevel!(State);
diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs
index 5d8adc06..e5d91f16 100644
--- a/src/handlers/xdg_shell.rs
+++ b/src/handlers/xdg_shell.rs
@@ -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");