atagen

monolith

public
Star0
Watch0

Commit 5c459f657de6

tarball

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

5 files changed+58-48
popups: show and hit-test transients above a fullscreen window

A fullscreen window hid the popups of every other window, including the floating transients that Arrangement::over keeps above it, so a dialog's menus were neither drawn nor clickable. The fullscreen check is also now Layout::fullscreen_at, which reads the history instead of arranging the whole output, since each pointer motion made it three or four times. window_popup_under no longer collects a Vec of popups for every window it tests.
Modified file: crates/layout/src/lib.rs+43 additions−20 deletions
@@ -852,6 +852,23 @@ impl Layout {
852852 visible: vis(window),
853853 })
854854 .collect();
855+ let floating = self.floating_on(o, out, vis);
856+ let fullscreen = self.fullscreen_in(vis);
857+ let over = fullscreen.map_or_else(Vec::new, |f| self.above_fullscreen(f, &floating));
858+ Arrangement {
859+ tiled,
860+ floating,
861+ fullscreen,
862+ over,
863+ }
864+ }
865+
866+ fn floating_on(
867+ &self,
868+ o: OutputId,
869+ out: &Output,
870+ vis: &dyn Fn(WindowId) -> bool,
871+ ) -> Vec<(WindowId, Rect)> {
855872 let mut floating: Vec<(WindowId, Rect)> = out
856873 .floating
857874 .iter()
@@ -868,31 +885,37 @@ impl Layout {
868885 floating.push((sp.windows[i], centre_in(out.usable(), r.w, r.h)));
869886 }
870887 }
888+ floating
889+ }
890+
891+ fn fullscreen_in(&self, vis: &dyn Fn(WindowId) -> bool) -> Option<WindowId> {
871892 let is_fullscreen = |w: WindowId| self.windows[w].fullscreen && vis(w);
872- let fullscreen = self
873- .history
893+ self.history
874894 .iter()
875895 .rev()
876896 .copied()
877897 .find(|&w| is_fullscreen(w))
878- .or_else(|| self.windows.keys().find(|&w| is_fullscreen(w)));
879- let over = fullscreen
880- .map(|f| {
881- floating
882- .iter()
883- .map(|&(w, _)| w)
884- .filter(|&w| {
885- w != f && (self.descends(w, f) || self.windows[w].scratchpad.is_some())
886- })
887- .collect()
888- })
889- .unwrap_or_default();
890- Arrangement {
891- tiled,
892- floating,
893- fullscreen,
894- over,
895- }
898+ .or_else(|| self.windows.keys().find(|&w| is_fullscreen(w)))
899+ }
900+
901+ fn above_fullscreen(&self, f: WindowId, floating: &[(WindowId, Rect)]) -> Vec<WindowId> {
902+ floating
903+ .iter()
904+ .map(|&(w, _)| w)
905+ .filter(|&w| w != f && (self.descends(w, f) || self.windows[w].scratchpad.is_some()))
906+ .collect()
907+ }
908+
909+ /// The fullscreen window of the output under (x, y), with the floating
910+ /// transients that stay above it. Cheaper than arranging the output.
911+ pub fn fullscreen_at(&self, x: f64, y: f64) -> Option<(WindowId, Vec<WindowId>)> {
912+ let (o, out) = self
913+ .outputs
914+ .iter()
915+ .find(|(_, out)| out.area.contains(x, y))?;
916+ let vis = self.vis_on(o);
917+ let f = self.fullscreen_in(&vis)?;
918+ Some((f, self.above_fullscreen(f, &self.floating_on(o, out, &vis))))
896919 }
897920
898921 /// Records that `w` is a transient of `parent`.
Modified file: crates/monolith/src/input.rs+2 additions−1 deletions
@@ -275,7 +275,8 @@ fn focus_under_pointer(state: &mut State) {
275275 }
276276
277277 fn window_at(state: &State, pos: Point<f64, Logical>) -> Option<WindowId> {
278- if let Some((root, ..)) = state.window_popup_under(pos) {
278+ let fullscreen = state.wm.layout.fullscreen_at(pos.x, pos.y);
279+ if let Some((root, ..)) = state.window_popup_under(pos, fullscreen.as_ref()) {
279280 return state.window_id(&root);
280281 }
281282 let (w, _) = state.wm.space.element_under(pos)?;
Modified file: crates/monolith/src/popups.rs+8 additions−22 deletions
@@ -165,25 +165,21 @@ impl State {
165165 pub fn placed(
166166 root: &WlSurface,
167167 geometry_at: Point<i32, Logical>,
168-) -> Vec<(WlSurface, Point<i32, Logical>)> {
169- offsets(root)
170- .into_iter()
171- .map(|(popup, at)| (popup, geometry_at + at))
172- .collect()
168+) -> impl Iterator<Item = (WlSurface, Point<i32, Logical>)> + use<> {
169+ offsets(root).map(move |(popup, at)| (popup, geometry_at + at))
173170 }
174171
175-fn offsets(root: &WlSurface) -> Vec<(WlSurface, Point<i32, Logical>)> {
172+fn offsets(root: &WlSurface) -> impl Iterator<Item = (WlSurface, Point<i32, Logical>)> + use<> {
176173 PopupManager::popups_for_surface(root)
177174 .map(|(popup, at)| (popup.wl_surface().clone(), at - popup.geometry().loc))
178- .collect()
179175 }
180176
181177 /// The surface of `popups` under `pos`, with its origin.
182178 pub fn hit(
183- popups: Vec<(WlSurface, Point<i32, Logical>)>,
179+ mut popups: impl Iterator<Item = (WlSurface, Point<i32, Logical>)>,
184180 pos: Point<f64, Logical>,
185181 ) -> Option<(WlSurface, Point<f64, Logical>)> {
186- popups.into_iter().find_map(|(popup, at)| {
182+ popups.find_map(|(popup, at)| {
187183 let (s, p) =
188184 under_from_surface_tree(&popup, pos - at.to_f64(), (0, 0), WindowSurfaceType::ALL)?;
189185 Some((s, (p + at).to_f64()))
@@ -217,28 +213,18 @@ pub fn elements(
217213 }
218214
219215 impl State {
220- /// The window covering the output under `pos` while it is fullscreen.
221- pub fn fullscreen_at(&self, pos: Point<f64, Logical>) -> Option<WindowId> {
222- self.wm.layout.outputs().find_map(|(o, out)| {
223- let a = out.area;
224- a.contains(pos.x, pos.y)
225- .then(|| self.wm.layout.arrange(o).fullscreen)
226- .flatten()
227- })
228- }
229-
230216 /// The popup of a window under `pos`, frontmost window first, with the
231217 /// window's root surface. A fullscreen window hides the popups of the
232- /// windows below it.
218+ /// windows below it, except those of the transients kept above it.
233219 pub fn window_popup_under(
234220 &self,
235221 pos: Point<f64, Logical>,
222+ fullscreen: Option<&(WindowId, Vec<WindowId>)>,
236223 ) -> Option<(WlSurface, WlSurface, Point<f64, Logical>)> {
237- let fullscreen = self.fullscreen_at(pos);
238224 self.wm.space.elements().rev().find_map(|window| {
239225 let root = crate::wm::root(window).filter(|_| window.toplevel().is_some())?;
240226 let id = self.window_id(&root)?;
241- if fullscreen.is_some_and(|f| f != id) {
227+ if fullscreen.is_some_and(|(f, over)| *f != id && !over.contains(&id)) {
242228 return None;
243229 }
244230 let (_, rect, _) = self.wm.get(id)?.placed?;
Modified file: crates/monolith/src/scene.rs+1 additions−1 deletions
@@ -137,7 +137,7 @@ pub fn build(
137137 }
138138 let covering = arr.fullscreen.filter(|_| fullscreen_at_rest);
139139 for &(id, r, _, _) in tiled.iter().chain(&above).rev() {
140- if covering.is_some_and(|f| f != id) {
140+ if covering.is_some_and(|f| f != id) && !arr.over.contains(&id) {
141141 continue;
142142 }
143143 let Some(root) = windows.get(id).and_then(|s| s.window.toplevel()) else {
Modified file: crates/monolith/src/state.rs+4 additions−4 deletions
@@ -560,8 +560,8 @@ impl State {
560560 return Some(other((s, (p + geo.loc).to_f64())));
561561 }
562562 }
563- let fullscreen = self.fullscreen_at(pos).is_some();
564- let above: &[Layer] = if fullscreen {
563+ let fullscreen = self.wm.layout.fullscreen_at(pos.x, pos.y);
564+ let above: &[Layer] = if fullscreen.is_some() {
565565 &[Layer::Overlay]
566566 } else {
567567 &[Layer::Overlay, Layer::Top]
@@ -569,11 +569,11 @@ impl State {
569569 let layer = |layers| self.layer_under(pos, layers).map(|(_, s, p)| (s, p));
570570 layer(above)
571571 .or_else(|| {
572- self.window_popup_under(pos)
572+ self.window_popup_under(pos, fullscreen.as_ref())
573573 .map(|(_, surface, origin)| (surface, origin))
574574 })
575575 .or_else(|| {
576- if fullscreen {
576+ if fullscreen.is_some() {
577577 return None;
578578 }
579579 self.lower_popup_under(pos)