srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/wayland/src
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-07-06 11:32:00 +0200
committersrdusr <[email protected]>2026-07-06 11:32:00 +0200
commitad5a7349d28ad52032fff6885e2d8b80ea28dbce (patch)
tree9680b085e362a443f411ed36d1c22b40a8969163 /crates/wayland/src
parent744757fad22db7a5ebebca9a685cdb00589a1b02 (diff)
downloadsrdwm-ad5a7349d28ad52032fff6885e2d8b80ea28dbce.tar.gz
srdwm-ad5a7349d28ad52032fff6885e2d8b80ea28dbce.zip
Drop placeholder-sized window-memory entries at load
An earlier cleanup deleted five of them from window-memory.json by hand and they came back within minutes. The reason is that a running compositor holds the whole table in memory and save_all writes all of it back on the next window close, so a hand-edited file cannot survive a running session. Filtering on load is the only point where the fix sticks. remove_window already refuses to record a size the client never chose, so nothing new is captured this way; this clears what was written before that landed. Those entries are self-perpetuating - a remembered size makes the next launch non-provisional, which forces the client to that size rather than asking it to pick, which writes the same value back on close - so an affected app can never escape on its own. A window genuinely sized exactly 800x632 loses its remembered size once and gets it back at the next real resize or close. Two tests cover the exact match and that sharing only one dimension is not enough. 531 tests pass, clippy clean.
Diffstat (limited to 'crates/wayland/src')
-rw-r--r--crates/wayland/src/window_memory.rs68
1 files changed, 67 insertions, 1 deletions
diff --git a/crates/wayland/src/window_memory.rs b/crates/wayland/src/window_memory.rs
index a92c57c..b2d3711 100644
--- a/crates/wayland/src/window_memory.rs
+++ b/crates/wayland/src/window_memory.rs
@@ -51,11 +51,54 @@ fn memory_path() -> PathBuf {
/// the file doesn't exist yet or is present but unreadable/corrupt - a
/// bad state file degrades to "nothing remembered yet", not a startup
/// failure.
+/// The size `new_managed_window` gives a window before its client has
+/// chosen anything: `800 x 600 + TITLEBAR_HEIGHT`.
+///
+/// An entry recording exactly this is a size no client ever picked - see
+/// `discard_placeholder_entries`.
+fn placeholder_size() -> (u32, u32) {
+ (800, 600 + srdwm_core::TITLEBAR_HEIGHT)
+}
+
+/// Drops entries whose size is exactly the placeholder.
+///
+/// `remove_window` no longer records a size the client never chose, so
+/// nothing new is captured this way. Entries written before that fix are
+/// still on disk, and they are self-perpetuating: a remembered size makes
+/// the next launch non-provisional, which forces the client to that size
+/// instead of asking it to pick, which writes the same value back on close.
+/// An affected app can never escape on its own, so the stale entries have
+/// to be dropped rather than waited out.
+///
+/// Editing the file by hand does not work, which is worth recording: a
+/// running compositor holds the whole table in memory and `save_all` writes
+/// all of it back, so a hand-deleted entry reappears at the next save.
+/// Filtering on load is the only point where the fix actually sticks.
+///
+/// A window genuinely sized exactly 800x632 loses its remembered size once,
+/// and gets it back the moment it is next resized or closed at a real size.
+/// That is a far smaller cost than an app permanently pinned to a shape it
+/// never asked for.
+fn discard_placeholder_entries(apps: &mut HashMap<String, PersistedGeometry>) {
+ let (w, h) = placeholder_size();
+ apps.retain(|app_id, g| {
+ let stale = g.width == w && g.height == h;
+ if stale {
+ log::info!("window_memory: dropping {app_id}'s remembered {w}x{h} - that is the placeholder, not a size it chose");
+ }
+ !stale
+ });
+}
+
pub(crate) fn load() -> HashMap<String, PersistedGeometry> {
let path = memory_path();
let Ok(bytes) = std::fs::read(&path) else { return HashMap::new() };
match serde_json::from_slice::<PersistedWindowMemory>(&bytes) {
- Ok(memory) => memory.apps,
+ Ok(memory) => {
+ let mut apps = memory.apps;
+ discard_placeholder_entries(&mut apps);
+ apps
+ }
Err(e) => {
log::warn!("window_memory: couldn't parse {path:?} ({e}); starting with nothing remembered");
HashMap::new()
@@ -95,6 +138,29 @@ pub(crate) fn save_all<'a>(entries: impl Iterator<Item = (&'a str, (i32, i32, u3
mod tests {
use super::*;
+ #[test]
+ fn a_placeholder_sized_entry_is_dropped_on_load() {
+ // The self-perpetuating case: an app pinned to the size the
+ // compositor guessed before it had chosen one.
+ let (w, h) = placeholder_size();
+ let mut apps = HashMap::new();
+ apps.insert("pinned".to_string(), PersistedGeometry { x: 10, y: 20, width: w, height: h });
+ apps.insert("real".to_string(), PersistedGeometry { x: 30, y: 40, width: 1389, height: 933 });
+ discard_placeholder_entries(&mut apps);
+ assert!(!apps.contains_key("pinned"), "the placeholder entry must go");
+ assert!(apps.contains_key("real"), "a real remembered size must survive");
+ }
+
+ #[test]
+ fn an_entry_that_merely_shares_one_dimension_is_kept() {
+ let (w, h) = placeholder_size();
+ let mut apps = HashMap::new();
+ apps.insert("same_width".to_string(), PersistedGeometry { x: 0, y: 0, width: w, height: h + 1 });
+ apps.insert("same_height".to_string(), PersistedGeometry { x: 0, y: 0, width: w + 1, height: h });
+ discard_placeholder_entries(&mut apps);
+ assert_eq!(apps.len(), 2, "only an exact match is the placeholder");
+ }
+
// Only the pure JSON round-trip is exercised here - `state_dir()`/
// `load()`/`save_all()` all touch real environment variables and the
// filesystem, which parallel `cargo test` execution can't safely share