srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crates/wayland/src/window_memory.rs68
-rw-r--r--docs/TODO.md47
2 files changed, 114 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
diff --git a/docs/TODO.md b/docs/TODO.md
index df0bfae..e3b973c 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -1,5 +1,52 @@
# TODO / planned features - master checklist
+## The tint is AGS pushing shadows back on, and a store cleanup that could not stick (2026-08-28)
+
+Four reports. Two have a single cause each, and one of them is not srdwm's.
+
+**"Firefox has a dark-ish tint when it has focus" - that is the drop
+shadow, and AGS turns it on.** `init.lua` sets `general.shadows = false`;
+the running session reported `true` again after a restart on a binary that
+already had the config-precedence fix. The source is
+`ags_project/lib/srdwm.ts:801`, which pushes the shell's own stored value:
+
+ srdSet("shadows", shadows.peek() ? "true" : "false")
+
+alongside `border_color`, `gap_outer`, `gap_inner` and `corner_radius`. That
+is deliberate on the AGS side - propagating one accent pick to every
+surface is the point of its theme system - but it means srdwm's config
+loses for those keys every time the shell starts. It also explains the
+*focus* part exactly: a focused window gets the full `SHADOW_MAX_ALPHA`
+while an unfocused one is dimmed by `border_inactive_dim`, so focusing a
+window visibly darkens what is behind it.
+
+Worth flagging as a coordination problem rather than a bug in either half:
+srdwm's new rule is that the config wins for keys it states, applied on
+every reload, and AGS pushes its own values at startup. Both are reasonable
+in isolation and they disagree about the same keys.
+
+**Window memory: the earlier cleanup was undone the moment it was made.**
+Five placeholder-sized entries were deleted from
+`window-memory.json` by hand. They came back, because a running compositor
+holds the whole table in memory and `save_all` writes all of it back on the
+next window close - a hand-edited file cannot survive a running session.
+
+`load()` now drops entries whose size is exactly the placeholder
+(`800 x 600 + TITLEBAR_HEIGHT`), which is the only point where the fix
+sticks. `remove_window` already refuses to record such a size, so nothing
+new is captured; this clears what was written before that landed. A window
+genuinely that size loses its memory once and gets it back on the next real
+resize or close - much cheaper than an app permanently pinned to a shape it
+never chose.
+
+**Firefox and Nemo showing no srdwm decorations** is `rules.lua`'s own
+`decorated = false` for both, which is the double-titlebar fix working as
+intended: srdwm draws neither titlebar nor border, and each app draws its
+own chrome. The missing border on Nemo has the same single cause, and the
+owner prefers it.
+
+531 tests, clippy clean.
+
## "Why is there still a tint on windows": a live override the config could never take back (2026-08-28)
The tint is the drop shadow. `init.lua:65` sets `general.shadows` to