atagen

monolith

public
Star0
Watch0

Commit 33d439770e90

tarball

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

1 file changed+38-16
launch: expire records in find and prefer the owning window

Launches::find only checked age when a record was added, so a terminal spawned in the morning placed every window launched from it at the morning's tag and output. The nearest ancestor owning a mapped window now beats a record, so later windows follow their app wherever the user moved it.
Modified file: crates/monolith/src/launch.rs+38 additions−16 deletions
@@ -1,7 +1,7 @@
11 //! Launch context. A window goes to the output and tag the user was on when
2-//! its launch began, found by matching its process or an ancestor against
3-//! the programs monolith spawned, then against processes that own a mapped
4-//! window, and last the focus at map time.
2+//! its launch began. Its process and then each ancestor is matched against
3+//! processes that own a mapped window and the programs monolith spawned, the
4+//! nearest match deciding, and the focus at map time is the last resort.
55
66 use crate::state::State;
77 use monolith_layout::{OutputId, TagId};
@@ -31,11 +31,11 @@ impl Launches {
3131 });
3232 }
3333
34- pub fn find(&self, pid: u32) -> Option<(Option<OutputId>, TagId)> {
35- lineage(pid).find_map(|pid| {
36- let l = self.0.iter().find(|l| l.pid == pid)?;
37- Some((l.output, l.tag))
38- })
34+ fn get(&self, pid: u32) -> Option<(Option<OutputId>, TagId)> {
35+ self.0
36+ .iter()
37+ .find(|l| l.pid == pid && l.at.elapsed() < KEEP)
38+ .map(|l| (l.output, l.tag))
3939 }
4040 }
4141
@@ -45,16 +45,14 @@ fn lineage(pid: u32) -> impl Iterator<Item = u32> {
4545 }
4646
4747 impl State {
48- /// Where the window of process `pid` belongs. A program monolith spawned
49- /// comes first, then the nearest ancestor owning a mapped window, which
50- /// includes `pid` itself for its second window, then the focus now.
48+ /// Where the window of process `pid` belongs. The nearest of `pid` and
49+ /// its ancestors that owns a mapped window decides, so a program's later
50+ /// windows follow its first one wherever the user moved it. Failing that
51+ /// the nearest program monolith spawned recently, then the focus now.
5152 pub fn launch_of(&self, pid: Option<u32>) -> (Option<OutputId>, TagId) {
5253 let Some(pid) = pid else {
5354 return self.launch_context();
5455 };
55- if let Some(found) = self.services.launches.find(pid) {
56- return found;
57- }
5856 let owners: Vec<(u32, OutputId, TagId)> = self
5957 .wm
6058 .iter()
@@ -67,8 +65,12 @@ impl State {
6765 })
6866 .collect();
6967 lineage(pid)
70- .find_map(|p| owners.iter().find(|o| o.0 == p))
71- .map(|&(_, output, tag)| (Some(output), tag))
68+ .find_map(|p| {
69+ let owned = owners.iter().find(|o| o.0 == p);
70+ owned
71+ .map(|&(_, output, tag)| (Some(output), tag))
72+ .or_else(|| self.services.launches.get(p))
73+ })
7274 .unwrap_or_else(|| self.launch_context())
7375 }
7476
@@ -89,3 +91,23 @@ fn parent(pid: u32) -> Option<u32> {
8991 let rest = stat.get(stat.rfind(')')? + 2..)?;
9092 rest.split(' ').nth(1)?.parse().ok()
9193 }
94+
95+#[cfg(test)]
96+mod tests {
97+ use super::*;
98+
99+ #[test]
100+ fn a_record_past_its_keep_time_matches_nothing() {
101+ let mut launches = Launches::default();
102+ launches.record(8, None, TagId::default());
103+ let old = Instant::now().checked_sub(KEEP + Duration::from_secs(1));
104+ launches.0.push(Launch {
105+ pid: 7,
106+ output: None,
107+ tag: TagId::default(),
108+ at: old.expect("the clock has run for two minutes"),
109+ });
110+ assert_eq!(launches.get(7), None);
111+ assert!(launches.get(8).is_some());
112+ }
113+}