git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitab99cf0b171f6e968117c5ef565783cb04334b6f
parenta19edaa3b9
authorLucas Galante <[email protected]>
date2026-09-11 22:07
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)