atagen

monolith

public
Star0
Watch0

Commit a748589d3821

tarball

atagen <boss@atagen.co>2026-10-19 07:56 UTC

3 files changed+53-2
popups: keep xdg popups on their output

new_popup and reposition_request used PositionerState::get_geometry, which ignores the constraint adjustment the client asked for, so menus opened near an edge ran off the screen. Layer-shell popups take the same path from WlrLayerShellHandler::new_popup.
Modified file: crates/monolith/src/layers.rs+1 additions
@@ -57,6 +57,7 @@ impl WlrLayerShellHandler for State {
5757 }
5858
5959 fn new_popup(&mut self, _parent: WlrLayerSurface, popup: PopupSurface) {
60+ self.unconstrain_popup(&popup);
6061 let _ = self.wm.popups.track_popup(PopupKind::Xdg(popup));
6162 }
6263
Modified file: crates/monolith/src/popups.rs+50 additions−2 deletions
@@ -2,13 +2,16 @@
22
33 use crate::focus::Focus;
44 use crate::state::State;
5+use monolith_layout::Rect;
56 use smithay::desktop::{
6- PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, find_popup_root_surface,
7+ PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, WindowSurfaceType,
8+ find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
79 };
810 use smithay::input::Seat;
911 use smithay::input::pointer::Focus as GrabFocus;
1012 use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
11-use smithay::utils::Serial;
13+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
14+use smithay::utils::{Logical, Point, Rectangle, Serial};
1215 use smithay::wayland::shell::xdg::PopupSurface;
1316
1417 impl State {
@@ -67,3 +70,48 @@ impl State {
6770 }
6871 }
6972 }
73+
74+fn whole(r: Rect) -> Rectangle<i32, Logical> {
75+ let (x, y) = (r.x.round() as i32, r.y.round() as i32);
76+ Rectangle::new(
77+ (x, y).into(),
78+ (r.right().round() as i32 - x, r.bottom().round() as i32 - y).into(),
79+ )
80+}
81+
82+impl State {
83+ /// The global position of the popup root's geometry origin, and the output
84+ /// area the popup has to stay inside.
85+ fn popup_frame(
86+ &self,
87+ root: &WlSurface,
88+ ) -> Option<(Point<i32, Logical>, Rectangle<i32, Logical>)> {
89+ if let Some(id) = self.window_id(root) {
90+ let (_, rect, o) = self.wm.get(id)?.placed?;
91+ let area = self.wm.layout.output(o)?.area;
92+ return Some((whole(rect).loc, whole(area)));
93+ }
94+ self.wm.space.outputs().find_map(|output| {
95+ let map = layer_map_for_output(output);
96+ let layer = map.layer_for_surface(root, WindowSurfaceType::TOPLEVEL)?;
97+ let at = map.layer_geometry(layer)?.loc;
98+ let out = self.wm.space.output_geometry(output)?;
99+ Some((out.loc + at, out))
100+ })
101+ }
102+
103+ /// Slides or flips the popup's pending geometry to keep it on the output
104+ /// its root is on, as the positioner allows.
105+ pub fn unconstrain_popup(&self, popup: &PopupSurface) {
106+ let kind = PopupKind::Xdg(popup.clone());
107+ let Ok(root) = find_popup_root_surface(&kind) else {
108+ return;
109+ };
110+ let Some((origin, area)) = self.popup_frame(&root) else {
111+ return;
112+ };
113+ let mut target = area;
114+ target.loc -= origin + get_popup_toplevel_coords(&kind);
115+ popup.with_pending_state(|s| s.geometry = s.positioner.get_unconstrained_geometry(target));
116+ }
117+}
Modified file: crates/monolith/src/xdg.rs+2 additions
@@ -56,6 +56,7 @@ impl XdgShellHandler for State {
5656
5757 fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
5858 surface.with_pending_state(|s| s.geometry = positioner.get_geometry());
59+ self.unconstrain_popup(&surface);
5960 let _ = self.wm.popups.track_popup(PopupKind::Xdg(surface));
6061 }
6162
@@ -69,6 +70,7 @@ impl XdgShellHandler for State {
6970 s.geometry = positioner.get_geometry();
7071 s.positioner = positioner;
7172 });
73+ self.unconstrain_popup(&surface);
7274 surface.send_repositioned(token);
7375 }