Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix: a restored tiling mode is latched, so tiled survives a restart
get_mode_for_window returns a window's own mode only when mode_locked;
unlocked, it resolves from the config rules and falls through to
Floating. The arrange pass then writes that resolution straight back
into tiling_mode. try_restore restored the mode and never set the lock,
so a window restored Tiled was demoted by the very next arrange —
position, size and cell all restored exactly, the mode gone before the
first frame.
Both sibling promotions already pair the mode with the lock: the seat's
op_end detection, and the geometric promotion immediately below the line
that forgot it. Only Floating is left unlatched, so a window with no
explicit mode still resolves from the rules as before.
Why it looked erratic: a window saved Floating-but-cell-aligned was
fine, because that IS the geometric promotion's case and it locks. Only
a window saved Tiled broke, since the promotion is gated on the restored
mode being Floating and nothing else latched it. The bug bit exactly
when the previous session had been correct — tile a window and the next
restart is when you lose it.
Verified in a shadow driven with --bin, so nothing touched the installed
compositor. Against the unfixed binary a Tiled cce-files comes back
Floating after a compositor restart; against this one it comes back
Tiled, at the same cell-aligned geometry, for both a compositor restart
and an app relaunch. The saved state was confirmed correct on disk
(Tiled in windows and last_window_states) and the restore log line
confirmed try_restore ran, which is what moved the search from "not
saved" to "restored, then overwritten".
No unit test: try_restore takes a *mut Window with a live server
pointer, and the claim is an interaction across restore -> arrange ->
get_mode_for_window. Tests in this crate cluster where the logic is pure
and the FFI is not; a helper wrapping `mode != Floating` would assert
itself. The shadow run is the evidence.
Co-Authored-By: Claude Fable 5 <[email protected]>
src/server/window.rs | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/src/server/window.rs b/src/server/window.rs
index 3ed7f47..9f4ee08 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -1020,6 +1020,26 @@ impl Window {
_ => {}
}
+ // A restored non-Floating mode is EXPLICIT state, and has to be
+ // latched to survive. `get_mode_for_window` returns the window's own
+ // mode only when `mode_locked`; unlocked, it resolves from the config
+ // rules and falls through to Floating — and the arrange pass writes
+ // that resolution straight back into `tiling_mode`
+ // (`window_manager.rs`, the `wp.tiling_mode` apply). So a window
+ // restored Tiled but unlocked was demoted by the very next arrange,
+ // which is why a relaunched app came back floating however exactly
+ // its geometry had been restored: position, size and cell were all
+ // right, and the mode was gone before the first frame.
+ //
+ // Both sibling promotions already pair the mode with the lock — the
+ // seat's op_end detection, and the geometric one just below, which is
+ // why a window saved Floating-but-aligned survived while one saved
+ // Tiled did not. Only Floating is left unlatched here, so a window
+ // with no explicit mode still resolves from the rules as before.
+ if saved.tiling_mode != crate::tiling::TilingMode::Floating {
+ self.mode_locked = true;
+ }
+
// Geometric promotion at restore time: a window whose saved
// geometry sits cell-aligned IS tiled, even if an older session
// saved it as Floating (pre-rework state, or a session that