Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
fix(overview): the resize ring paints in the focused color, not a stale plan
Hovering a window in overview sometimes drew its resize ring in the plain
border gray (#3e3e3e here) instead of the focus color, on a window that was
demonstrably focused.
The ring's two halves disagreed about where "focused" comes from. Whether it
is drawn is read live off the seat — is_seat_focused() in handles_on, and
again in step_border_fade's reveal. What color it is came from
rendering_requested.border.color, a PLAN value the arrange pass writes from
the focus it saw when it last ran. And Seat::focus only schedules an arrange
when the newly focused window happens to be Floating; every other focus
change arms the border fade and nothing else. So the ring eased in wearing
whatever color the last arrange left, and a tiled window hovered in overview
stayed gray until some unrelated transaction refreshed the plan. Hence
"sometimes": the floating case dirties windowing and looked right.
Both are the same fact, so both now read it live: the ring takes the focused
color from the layout. update_bevel already does this for the rim highlight,
and for the same stated reason. window_background keeps the plan color — it
is the window's own plate, not a compositor-drawn handle.
Measured headless at the ring pixels, same window and session: focused with
a fresh arrange #7aa2f7, focus away to a floating window and back #3e3e3e,
and after this change #7aa2f7 for that same sequence. The hover highlight is
untouched — the hovered band still reads #a8c7fa against a #7aa2f7 ring.
Not fixed, because nothing shows it today: the same staleness leaves the
plate color and the unfocused opacity behind on a tiled focus change. The
plate only draws for clients calling use_ssd (none do) and both opacity
levels have been 1.0 since 2026-09-03. Refreshing those means arranging on
every focus change, which is a bigger hammer than the symptom needs.
Co-Authored-By: Claude Opus 5 <[email protected]>
src/server/window.rs | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/server/window.rs b/src/server/window.rs
index 7cfd96e..45984c2 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -4103,7 +4103,29 @@ impl Window {
None => [0.0; 4],
};
ffi::wlr_scene_frame_set_exclusion(self.border.frame, ex.as_ptr());
- ffi::wlr_scene_frame_set_color(self.border.frame, premul(&border_color).as_ptr());
+ // The ring exists only for the SEAT-focused window — `handles_on`
+ // above says so, and so does step_border_fade's reveal — so it
+ // paints in the focused color, taken from the layout rather than
+ // from `requested.border.color`.
+ //
+ // That field is a PLAN value, written by the arrange pass from the
+ // focus it saw when it ran, and a focus change only schedules an
+ // arrange when the newly focused window happens to be Floating
+ // (Seat::focus). Every other focus change armed the border fade
+ // and nothing else, so the ring eased in wearing the UNFOCUSED
+ // color: hovering a tiled window in overview drew its resize ring
+ // in the plain border gray instead of the focus color, and it
+ // stayed gray until some unrelated transaction refreshed the plan.
+ // Whether the ring is drawn and what color it is are the same
+ // fact — focused — so both now read it live, the way update_bevel
+ // already reads focus off the seats for the rim highlight.
+ //
+ // `window_background` above keeps the plan color: that one is the
+ // window's own plate, not this compositor-drawn handle.
+ ffi::wlr_scene_frame_set_color(
+ self.border.frame,
+ premul(&layout.border_color_focused).as_ptr(),
+ );
let hovered = self
.hovered_border_element
.map(|e| e.index() as f32)