atagen

monolith

public
Star0
Watch0

Commit 9fdf9584d2bd

tarball

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

6 files changed+44-3
layout: track urgency per window

mark_urgent only flagged the tag of a hidden window, so a visible window demanding attention through xdg-activation looked like any other. Windows now carry an urgent flag set there and cleared on focus, drawn with the urgent border and exposed on the IPC window object.
Modified file: crates/ipc/src/lib.rs+1 additions
@@ -323,4 +323,5 @@ pub struct Window {
323323 pub scratchpad: Option<String>,
324324 pub visible: bool,
325325 pub focused: bool,
326+ pub urgent: bool,
326327 }
Modified file: crates/layout/src/lib.rs+21 additions−3 deletions
@@ -69,6 +69,7 @@ pub struct Window {
6969 pub parent: Option<WindowId>,
7070 pub sticky: bool,
7171 pub scratchpad: Option<usize>,
72+ pub urgent: bool,
7273 evicted_from: Option<String>,
7374 }
7475
@@ -339,6 +340,7 @@ impl Layout {
339340 parent: None,
340341 sticky: false,
341342 scratchpad: None,
343+ urgent: false,
342344 evicted_from: None,
343345 });
344346 if let Some(o) = output {
@@ -448,6 +450,7 @@ impl Layout {
448450 }
449451 self.history.retain(|&h| h != w);
450452 self.history.push(w);
453+ self.windows[w].urgent = false;
451454 }
452455
453456 /// The most recently focused visible window on the active output. Focus
@@ -618,14 +621,29 @@ impl Layout {
618621 self.mark_urgent(w);
619622 }
620623
624+ /// Flags a window that wants attention unless it already has the focus,
625+ /// and its tag too when the window is out of sight.
621626 pub fn mark_urgent(&mut self, w: WindowId) {
622- if !self.visible(w)
623- && let Some(tag) = self.windows.get(w).map(|win| win.tag)
624- {
627+ if !self.windows.contains_key(w) {
628+ return;
629+ }
630+ if self.focused() != Some(w) {
631+ self.windows[w].urgent = true;
632+ }
633+ if !self.visible(w) {
634+ let tag = self.windows[w].tag;
625635 self.tags[tag].urgent = true;
626636 }
627637 }
628638
639+ /// Drops the flag of the window holding the focus, which can reach it
640+ /// without `focus`, when the window before it closes or its tag is viewed.
641+ pub fn clear_focused_urgent(&mut self) {
642+ if let Some(w) = self.focused() {
643+ self.windows[w].urgent = false;
644+ }
645+ }
646+
629647 pub fn send_to_output(&mut self, w: WindowId, o: OutputId) {
630648 let Some(win) = self.windows.get(w) else {
631649 return;
Modified file: crates/layout/src/tests.rs+18 additions
@@ -611,3 +611,21 @@ fn autotile_dwindles_by_the_anchors_shape() {
611611 assert!(r(b).y < r(c).y && r(b).x == r(c).x);
612612 assert!(r(c).x < r(d).x && r(c).y == r(d).y);
613613 }
614+
615+#[test]
616+fn urgency_skips_the_focused_window_and_ends_with_focus() {
617+ let mut l = Layout::default();
618+ let o = l.add_output("a", AREA);
619+ let tag = l.tag("1");
620+ l.view(o, tag);
621+ let a = l.add_window(Some(o), Some(tag), None);
622+ let b = l.add_window(Some(o), Some(tag), None);
623+ l.focus(a);
624+ l.mark_urgent(a);
625+ l.mark_urgent(b);
626+ assert!(!l.window(a).unwrap().urgent);
627+ assert!(l.window(b).unwrap().urgent);
628+ assert!(!l.tags().any(|(_, t)| t.urgent));
629+ l.focus(b);
630+ assert!(!l.window(b).unwrap().urgent);
631+}
Modified file: crates/monolith/src/ipc.rs+1 additions
@@ -202,6 +202,7 @@ fn snapshot(state: &State) -> monolith_ipc::State {
202202 .map(|s| s.name.clone()),
203203 visible: l.visible(id),
204204 focused: focused == Some(id),
205+ urgent: w.urgent,
205206 }
206207 })
207208 .collect();
Modified file: crates/monolith/src/scene.rs+2 additions
@@ -138,6 +138,8 @@ pub fn build(
138138 if framed && deco.border > 0 {
139139 let color = if focused == Some(id) {
140140 deco.focused.0
141+ } else if layout.window(id).is_some_and(|w| w.urgent) {
142+ deco.urgent.0
141143 } else {
142144 deco.unfocused.0
143145 };
Modified file: crates/monolith/src/state.rs+1 additions
@@ -404,6 +404,7 @@ impl State {
404404 }
405405
406406 pub fn sync_focus(&mut self) {
407+ self.wm.layout.clear_focused_urgent();
407408 let focused = self.wm.layout.focused();
408409 for (id, s) in self.wm.iter() {
409410 let window = &s.window;