diff options
Diffstat (limited to 'src/handlers')
| -rw-r--r-- | src/handlers/compositor.rs | 3 | ||||
| -rw-r--r-- | src/handlers/layer_shell.rs | 5 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 96 |
3 files changed, 98 insertions, 6 deletions
diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index 30963f47..7cceaea0 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -135,6 +135,9 @@ impl CompositorHandler for State { // The toplevel remains mapped. self.niri.layout.update_window(&window); + // Popup placement depends on window size which might have changed. + self.update_reactive_popups(&window, &output); + self.niri.queue_redraw(output); return; } diff --git a/src/handlers/layer_shell.rs b/src/handlers/layer_shell.rs index a5b1d120..117f14e1 100644 --- a/src/handlers/layer_shell.rs +++ b/src/handlers/layer_shell.rs @@ -8,6 +8,7 @@ use smithay::wayland::shell::wlr_layer::{ Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler, WlrLayerShellState, }; +use smithay::wayland::shell::xdg::PopupSurface; use crate::niri::State; @@ -52,6 +53,10 @@ impl WlrLayerShellHandler for State { self.niri.output_resized(output); } } + + fn new_popup(&mut self, _parent: WlrLayerSurface, popup: PopupSurface) { + self.unconstrain_popup(&popup); + } } delegate_layer_shell!(State); diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 3eafcdbf..29bedb70 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -1,11 +1,14 @@ -use smithay::desktop::{find_popup_root_surface, PopupKind, Window}; +use smithay::desktop::{ + find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output, LayerSurface, + PopupKind, PopupManager, Window, WindowSurfaceType, +}; use smithay::output::Output; use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}; use smithay::reexports::wayland_server::protocol::wl_output; use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat; use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; -use smithay::utils::Serial; +use smithay::utils::{Rectangle, Serial}; use smithay::wayland::compositor::{send_surface_state, with_states}; use smithay::wayland::shell::kde::decoration::{KdeDecorationHandler, KdeDecorationState}; use smithay::wayland::shell::xdg::decoration::XdgDecorationHandler; @@ -49,8 +52,8 @@ impl XdgShellHandler for State { } fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) { - // FIXME: adjust the geometry so the popup doesn't overflow at least off the top and bottom - // screen edges, and ideally off the view size. + self.unconstrain_popup(&surface); + if let Err(err) = self.niri.popups.track_popup(PopupKind::Xdg(surface)) { warn!("error tracking popup: {err:?}"); } @@ -76,13 +79,12 @@ impl XdgShellHandler for State { positioner: PositionerState, token: u32, ) { - // FIXME: adjust the geometry so the popup doesn't overflow at least off the top and bottom - // screen edges, and ideally off the view size. surface.with_pending_state(|state| { let geometry = positioner.get_geometry(); state.geometry = geometry; state.positioner = positioner; }); + self.unconstrain_popup(&surface); surface.send_repositioned(token); } @@ -289,4 +291,86 @@ impl State { let root = find_popup_root_surface(popup).ok()?; self.niri.output_for_root(&root) } + + pub fn unconstrain_popup(&self, popup: &PopupSurface) { + let _span = tracy_client::span!("Niri::unconstrain_popup"); + + // Popups with a NULL parent will get repositioned in their respective protocol handlers + // (i.e. layer-shell). + let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else { + return; + }; + + // Figure out if the root is a window or a layer surface. + if let Some((window, output)) = self.niri.layout.find_window_and_output(&root) { + self.unconstrain_window_popup(popup, &window, &output); + } else if let Some((layer_surface, output)) = self.niri.layout.outputs().find_map(|o| { + let map = layer_map_for_output(o); + let layer_surface = map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL)?; + Some((layer_surface.clone(), o)) + }) { + self.unconstrain_layer_shell_popup(popup, &layer_surface, output); + } + } + + fn unconstrain_window_popup(&self, popup: &PopupSurface, window: &Window, output: &Output) { + let window_geo = window.geometry(); + let output_geo = self.niri.global_space.output_geometry(output).unwrap(); + + // The target geometry for the positioner should be relative to its parent's geometry, so + // we will compute that here. + // + // We try to keep regular window popups within the window itself horizontally (since the + // window can be scrolled to both edges of the screen), but within the whole monitor's + // height. + let mut target = + Rectangle::from_loc_and_size((0, 0), (window_geo.size.w, output_geo.size.h)); + target.loc.y -= self.niri.layout.window_y(window).unwrap(); + target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone())); + + popup.with_pending_state(|state| { + state.geometry = state.positioner.get_unconstrained_geometry(target); + }); + } + + pub fn unconstrain_layer_shell_popup( + &self, + popup: &PopupSurface, + layer_surface: &LayerSurface, + output: &Output, + ) { + let output_geo = self.niri.global_space.output_geometry(output).unwrap(); + let map = layer_map_for_output(output); + let Some(layer_geo) = map.layer_geometry(layer_surface) else { + return; + }; + + // The target geometry for the positioner should be relative to its parent's geometry, so + // we will compute that here. + let mut target = Rectangle::from_loc_and_size((0, 0), output_geo.size); + target.loc -= layer_geo.loc; + target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone())); + + popup.with_pending_state(|state| { + state.geometry = state.positioner.get_unconstrained_geometry(target); + }); + } + + pub fn update_reactive_popups(&self, window: &Window, output: &Output) { + let _span = tracy_client::span!("Niri::update_reactive_popups"); + + for (popup, _) in PopupManager::popups_for_surface(window.toplevel().wl_surface()) { + match popup { + PopupKind::Xdg(ref popup) => { + if popup.with_pending_state(|state| state.positioner.reactive) { + self.unconstrain_window_popup(popup, window, output); + if let Err(err) = popup.send_pending_configure() { + warn!("error re-configuring reactive popup: {err:?}"); + } + } + } + PopupKind::InputMethod(_) => (), + } + } + } } |
