atagen

monolith

public
Star0
Watch0

Commit 1cf303f60c35

tarball

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

2 files changed+55-20
launch: place windows by owning ancestors and focus

Launch context only matched programs monolith spawned in the last 120 s, so Inkscape started from a long-lived terminal landed on the homeless tag. The PID walk now also matches the nearest ancestor owning a mapped window, itself included for a second window, and anything unmatched falls back to the focus at map time as decisions.md ranks it.
Modified file: crates/monolith/src/launch.rs+52 additions−8 deletions
@@ -1,8 +1,11 @@
11 //! Launch context. A window goes to the output and tag the user was on when
22 //! its launch began, found by matching its process or an ancestor against
3-//! the programs monolith spawned.
3+//! the programs monolith spawned, then against processes that own a mapped
4+//! window, and last the focus at map time.
45
6+use crate::state::State;
57 use monolith_layout::{OutputId, TagId};
8+use smithay::reexports::wayland_server::Resource;
69 use std::time::{Duration, Instant};
710
811 const KEEP: Duration = Duration::from_secs(120);
@@ -29,14 +32,55 @@ impl Launches {
2932 }
3033
3134 pub fn find(&self, pid: u32) -> Option<(Option<OutputId>, TagId)> {
32- let mut pid = pid;
33- for _ in 0..32 {
34- if let Some(l) = self.0.iter().find(|l| l.pid == pid) {
35- return Some((l.output, l.tag));
36- }
37- pid = parent(pid).filter(|&p| p > 1)?;
35+ lineage(pid).find_map(|pid| {
36+ let l = self.0.iter().find(|l| l.pid == pid)?;
37+ Some((l.output, l.tag))
38+ })
39+ }
40+}
41+
42+/// `pid` and its ancestors, nearest first.
43+fn lineage(pid: u32) -> impl Iterator<Item = u32> {
44+ std::iter::successors(Some(pid), |&pid| parent(pid).filter(|&p| p > 1)).take(32)
45+}
46+
47+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.
51+ pub fn launch_of(&self, pid: Option<u32>) -> (Option<OutputId>, TagId) {
52+ let Some(pid) = pid else {
53+ return self.launch_context();
54+ };
55+ if let Some(found) = self.services.launches.find(pid) {
56+ return found;
57+ }
58+ let owners: Vec<(u32, OutputId, TagId)> = self
59+ .wm
60+ .iter()
61+ .filter_map(|(id, s)| {
62+ let w = self.wm.layout.window(id)?;
63+ if w.scratchpad.is_some() || w.sticky {
64+ return None;
65+ }
66+ Some((self.window_pid(&s.window)?, w.output?, w.tag))
67+ })
68+ .collect();
69+ lineage(pid)
70+ .find_map(|p| owners.iter().find(|o| o.0 == p))
71+ .map(|&(_, output, tag)| (Some(output), tag))
72+ .unwrap_or_else(|| self.launch_context())
73+ }
74+
75+ pub fn window_pid(&self, window: &smithay::desktop::Window) -> Option<u32> {
76+ match window.x11_surface() {
77+ Some(x) => x.pid(),
78+ None => window
79+ .toplevel()
80+ .and_then(|t| t.wl_surface().client())
81+ .and_then(|c| c.get_credentials(&self.core.display).ok())
82+ .map(|c| c.pid as u32),
3883 }
39- None
4084 }
4185 }
4286
Modified file: crates/monolith/src/windows.rs+3 additions−12 deletions
@@ -8,7 +8,6 @@ use smithay::backend::renderer::utils::{import_surface_tree, with_renderer_surfa
88 use smithay::desktop::Window;
99 use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
1010 use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState;
11-use smithay::reexports::wayland_server::Resource;
1211 use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
1312 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
1413 use smithay::utils::{Logical, Point, Rectangle, Serial};
@@ -22,15 +21,7 @@ impl State {
2221 pub fn map_window(&mut self, window: Window, float: Option<Rectangle<i32, Logical>>) {
2322 let (app_id, title) = identity(&window);
2423 let rule = self.settings.config.placement(&app_id, &title);
25- let pid = match window.x11_surface() {
26- Some(x) => x.pid(),
27- None => window
28- .toplevel()
29- .and_then(|t| t.wl_surface().client())
30- .and_then(|c| c.get_credentials(&self.core.display).ok())
31- .map(|c| c.pid as u32),
32- };
33- let launch = pid.and_then(|pid| self.services.launches.find(pid));
24+ let launch = self.launch_of(self.window_pid(&window));
3425 let (fullscreen, fullscreen_output) = requested_fullscreen(&window);
3526 let output = rule
3627 .output
@@ -43,10 +34,10 @@ impl State {
4334 .map(|(id, _)| id)
4435 })
4536 .or(fullscreen_output.and_then(|o| self.layout_output(&o)))
46- .or(launch.and_then(|l| l.0));
37+ .or(launch.0);
4738 let tag = match rule.tag.as_deref() {
4839 Some(t) => Some(self.wm.layout.tag(t)),
49- None => launch.map(|l| l.1),
40+ None => (output == launch.0).then_some(launch.1),
5041 };
5142 let float = float.map(|r| {
5243 Rect::new(