diff options
Diffstat (limited to 'src/handlers')
| -rw-r--r-- | src/handlers/compositor.rs | 91 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 14 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 234 |
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"); |
