atagen

monolith

public
Star0
Watch0

Commit 692a9dda8779

tarball

atagen <boss@atagen.co>2026-10-20 11:57 UTC

2 files changed+26-7
layout: keep windows an extended window covers out of focus

A window with no room on screen could still take focus from focus_dir, the focus history after a close, or a taskbar, leaving keybinds acting on something invisible. Layout::focus refuses it, focused_on skips it, and covered counts it hidden so a request for attention marks it urgent instead.
Modified file: crates/layout/src/lib.rs+25 additions−5 deletions
@@ -466,6 +466,9 @@ impl Layout {
466466 /// focused on its own brings its parent up beneath it, so closing the
467467 /// dialog hands focus back to its parent.
468468 pub fn focus(&mut self, mut w: WindowId) -> Option<WindowId> {
469+ if self.eclipsed(w) {
470+ return None;
471+ }
469472 loop {
470473 let win = self.windows.get(w)?;
471474 self.active = win.output.or(self.active);
@@ -547,7 +550,7 @@ impl Layout {
547550 .iter()
548551 .rev()
549552 .copied()
550- .find(|&w| self.windows[w].output == Some(o) && self.visible(w))
553+ .find(|&w| self.windows[w].output == Some(o) && self.visible(w) && !self.eclipsed(w))
551554 .map(|w| self.modal_leaf(w))
552555 }
553556
@@ -566,7 +569,7 @@ impl Layout {
566569 let candidates = arr
567570 .tiled
568571 .iter()
569- .filter(|p| p.visible)
572+ .filter(|p| p.visible && !self.eclipsed(p.window))
570573 .map(|p| (p.window, p.rect))
571574 .chain(arr.floating.iter().copied())
572575 .filter(|&(w, _)| Some(self.modal_leaf(w)) != current && reachable(w));
@@ -644,6 +647,20 @@ impl Layout {
644647 merged
645648 }
646649
650+ /// Whether another window is extended over the tiled window `w`, which
651+ /// leaves it no room and takes it out of reach of focus.
652+ pub fn eclipsed(&self, w: WindowId) -> bool {
653+ let Some(o) = self
654+ .windows
655+ .get(w)
656+ .filter(|w| !w.fullscreen)
657+ .and_then(|w| w.output)
658+ else {
659+ return false;
660+ };
661+ self.outputs[o].tree.eclipsed(w, self.vis_on(o))
662+ }
663+
647664 /// Whether a tiled window fills its output through expansion.
648665 pub fn maximized(&self, w: WindowId) -> bool {
649666 let Some(o) = self.windows.get(w).and_then(|w| w.output) else {
@@ -963,10 +980,13 @@ impl Layout {
963980 false
964981 }
965982
966- /// Whether a fullscreen window on `w`'s output hides it. The fullscreen
967- /// window's transients and shown scratchpads are not hidden, and a window
968- /// that is itself fullscreen takes over.
983+ /// Whether a fullscreen or extended window on `w`'s output hides it. The
984+ /// fullscreen window's transients and shown scratchpads are not hidden,
985+ /// and a window that is itself fullscreen takes over.
969986 pub fn covered(&self, w: WindowId) -> bool {
987+ if self.eclipsed(w) {
988+ return true;
989+ }
970990 let Some(win) = self.windows.get(w) else {
971991 return false;
972992 };
Modified file: crates/monolith/src/toplevels.rs+1 additions−2 deletions
@@ -43,8 +43,7 @@ impl ForeignToplevelHandler for State {
4343 }
4444 Action::Maximize(k, on) => {
4545 let Some(id) = id_of(k) else { return };
46- if self.wm.layout.maximized(id) != on {
47- self.wm.layout.focus(id);
46+ if self.wm.layout.maximized(id) != on && self.wm.layout.focus(id).is_some() {
4847 self.wm.layout.expand(monolith_layout::ExpandScope::All);
4948 }
5049 }