diff options
| author | srdusr <[email protected]> | 2026-06-02 20:08:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-06-02 20:08:00 +0200 |
| commit | 30d45a7b9e0ddcd7ed2836c16e77b423cf3cff28 (patch) | |
| tree | e7a85ad53642184b73afe7b8d47e9ef206d1b342 /crates/core/src/manager | |
| parent | 3ef784e09f6b81b767837e048d691c4e63528e55 (diff) | |
| download | srdwm-30d45a7b9e0ddcd7ed2836c16e77b423cf3cff28.tar.gz srdwm-30d45a7b9e0ddcd7ed2836c16e77b423cf3cff28.zip | |
Stop window memory poisoning itself, and publish the decoration side to GTK
Two reports, both traced to a cause other than the one being blamed.
"Windows still spawn as squares" was not placement. On the live session
firefox was 800x632 and so were four other apps, and 800x632 is exactly the
placeholder new_managed_window assigns before a client has chosen anything.
Firefox's remembered size earlier the same day was 1389x933.
The loop: a window closes while still carrying the placeholder, the
placeholder is remembered, the next launch therefore has a remembered size
and is no longer provisional, a non-provisional window is forced to its size
instead of being asked to pick, and on close the placeholder is written back.
Every app that ever closed early gets pinned to one identical box, and no
amount of placement work can touch it because the size never came from
placement.
remove_window now refuses to remember a size the client never chose. That
alone would have been wrong: adopt_provisional_size cleared its own tracking
set but never cleared Window::size_is_provisional, so nothing would ever have
been remembered again. Both halves are covered by tests. Five poisoned
entries were dropped from the live store and the six real ones kept, with a
backup alongside it.
"When user sets decorations should override all applications": the earlier
answer was true about the protocol and wrong about the outcome. GTK never
negotiates decoration, but it does read the desktop's button-layout
preference - GTK4 through xdg-desktop-portal, GTK3 through
gtk-decoration-layout. srdwm now publishes its own button_side there at
startup and after every reload, which is precisely the job kde-gtk-config
does for KWin. Verified in both directions from a neutral starting value;
testing the second direction is what exposed an ordering bug where the
publish ran before apply_general_settings and broadcast the default instead
of the configured side.
527 tests pass, clippy clean.
Diffstat (limited to 'crates/core/src/manager')
| -rw-r--r-- | crates/core/src/manager/dragresize.rs | 8 | ||||
| -rw-r--r-- | crates/core/src/manager/tests.rs | 33 | ||||
| -rw-r--r-- | crates/core/src/manager/windows.rs | 19 |
3 files changed, 59 insertions, 1 deletions
diff --git a/crates/core/src/manager/dragresize.rs b/crates/core/src/manager/dragresize.rs index 906e5db..16aa539 100644 --- a/crates/core/src/manager/dragresize.rs +++ b/crates/core/src/manager/dragresize.rs @@ -387,6 +387,14 @@ impl WindowManager { /// Every remembered `app_id` and its geometry - what `window_memory.rs` /// iterates to persist the full table (e.g. on a clean shutdown), not /// just whatever changed most recently. + /// This app's remembered geometry, if any. `None` means nothing has + /// been recorded for it - which is what a window that closed while its + /// size was still a placeholder deliberately leaves behind, so its next + /// launch gets to pick its own size again. + pub fn remembered_geometry_for(&self, app_id: &str) -> Option<(i32, i32, u32, u32)> { + self.remembered_geometry.get(app_id).copied() + } + pub fn all_remembered_geometry(&self) -> impl Iterator<Item = (&str, (i32, i32, u32, u32))> { self.remembered_geometry.iter().map(|(k, &v)| (k.as_str(), v)) } diff --git a/crates/core/src/manager/tests.rs b/crates/core/src/manager/tests.rs index 152c71d..fdf0517 100644 --- a/crates/core/src/manager/tests.rs +++ b/crates/core/src/manager/tests.rs @@ -915,6 +915,39 @@ } #[test] + fn a_size_the_client_never_chose_is_not_remembered() { + // The poisoning loop: remember a placeholder once and every future + // launch is forced to it, which looks like "every window spawns the + // same shape". + let mut wm = wm_with_monitor(); + let id = wm.alloc_window_id(); + let mut w = Window::new(id, "w"); + w.app_id = "someapp".into(); + wm.add_window(w); + assert!(wm.window(id).unwrap().size_is_provisional, "no remembered size, so the guess is provisional"); + wm.remove_window(id); + assert!(wm.remembered_geometry_for("someapp").is_none(), "a guess must never be remembered"); + } + + #[test] + fn a_size_the_client_did_choose_is_remembered() { + let mut wm = wm_with_monitor(); + let id = wm.alloc_window_id(); + let mut w = Window::new(id, "w"); + w.app_id = "someapp".into(); + wm.add_window(w); + // What the backend does once the client commits a real buffer. + if let Some(w) = wm.window_mut(id) { + w.size_is_provisional = false; + w.geometry.width = 1389; + w.geometry.height = 933; + } + wm.remove_window(id); + let remembered = wm.remembered_geometry_for("someapp").expect("a real choice must be remembered"); + assert_eq!((remembered.2, remembered.3), (1389, 933)); + } + + #[test] fn a_dialog_opens_centered_not_cascaded_into_the_corner() { let mut wm = wm_with_monitor(); let a = wm.alloc_window_id(); diff --git a/crates/core/src/manager/windows.rs b/crates/core/src/manager/windows.rs index 0186679..1e8a6cb 100644 --- a/crates/core/src/manager/windows.rs +++ b/crates/core/src/manager/windows.rs @@ -369,8 +369,25 @@ impl WindowManager { // simply never consulted again on the read side once `layout_name // == "tiling"`, so remembering it anyway is harmless, not wasted // work worth a special case. + // + // `size_is_provisional` still set means the client never actually + // chose a size: the window closed while still carrying the + // backend's placeholder guess. Remembering that guess poisons this + // table permanently, and does so in a way that hides itself: + // + // 1. a window closes early, the placeholder is remembered + // 2. next launch finds a remembered size, so it is NOT provisional + // 3. the client is therefore forced to the placeholder instead of + // being asked to pick, and looks identical to every other + // poisoned app + // 4. on close the same placeholder is written back + // + // Reported as windows "spawning in squares" - every app coming out + // the same shape whatever it is. Five of eleven entries in the live + // store had been captured this way, including Firefox, whose real + // remembered size earlier the same day had been 1389x933. if let Some(w) = &window { - if !w.app_id.is_empty() { + if !w.app_id.is_empty() && !w.size_is_provisional { self.remembered_geometry.insert(w.app_id.clone(), (w.geometry.x, w.geometry.y, w.geometry.width, w.geometry.height)); } } |