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

commit1c73f7cdfae10a5d014f26f9e1fb42d9cc7f1a56
parent96159346be
authorLucas Galante <[email protected]>
date2026-08-30 10:05
feat(handles): longer corner runs, clamped per side

corner_length 120 -> 160 in the user's config makes the corner zones span
38% of a side, at the edge bars' expense — but doing that surfaced a latent
hazard and a coupling, both fixed here.

The hazard: nothing capped the corner run against the window, so on a
window short enough for one side's two corner runs to meet, every point on
that side became a corner zone and its MIDPOINT resized diagonally.

The coupling: the obvious cap (against the window's shorter side) binds
both axes to the short one — measured on an 840x520 window, it silently
held the top edge's corner run at 28% when the config asked for 38%.

So the clamp is per SIDE, 0.45 of that side's own length, applied
identically in the shader's zone cut and the hit test's corner squares. A
long side carries the full configured run; a short one shortens; a 10%
edge bar always survives in the middle. Verified by gap-notch positions:
top edge at 38%/62% (the configured length), left edge at 45%/54% (its
cap), where the single cap had pinned both to 28%.

Co-Authored-By: Claude Fable 5 <[email protected]>

 CLAUDE.md                                     |  6 +++++-
 scenefx/render/fx_renderer/shaders/frame.frag | 11 ++++++++---
 src/server/cursor.rs                          | 13 +++++++++----
 src/server/window.rs                          |  6 +++++-
 4 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/CLAUDE.md b/CLAUDE.md
index 178f47c..b2e001a 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -331,7 +331,11 @@ where the old band sat outside them.
   The gap notches sever only the band; a groove across a pad would read as
   damage. Both `get_border_zone` and the drawing treat a pad as its corner's
   zone — its tip reaches past the band, so the hit test carries a matching
-  corner-disc check. The profile is continuous along
+  corner-disc check. The corner run (`corner_length`) clamps PER SIDE at 0.45
+  of that side's length, in the shader and the hit test alike: two corner
+  zones on one side must never meet, or its midpoint would resize diagonally
+  — and per-side (rather than against the window's short side) lets a long
+  side carry the full configured run while a short one shortens. The profile is continuous along
   a side, which a rect cannot express: its only shaping tool is a clipped
   region whose corner radius is a single scalar, capped by the thickness
   change (tens of px) while a side is hundreds long, so it reads as a bump
diff --git a/scenefx/render/fx_renderer/shaders/frame.frag b/scenefx/render/fx_renderer/shaders/frame.frag
index 26a0f1f..40edfdb 100644
--- a/scenefx/render/fx_renderer/shaders/frame.frag
+++ b/scenefx/render/fx_renderer/shaders/frame.frag
@@ -123,13 +123,18 @@ void main() {
     // Zones: anything on the pad is its corner's, then the band cut as
     // before. The gap only severs the BAND — a groove across the pad would
     // read as damage, so it skips fragments the disc owns.
+    // The corner run clamps against ITS OWN side: two corner zones on one
+    // side must never meet, or the side's midpoint would resize diagonally —
+    // and clamping per side rather than to the window's short side lets a
+    // long side carry the full configured run while a short one shortens.
+    float cl = min(corner_len, 0.45 * len);
     float zone;
-    if (u <= corner_len || f_disc > 0.0) {
+    if (u <= cl || f_disc > 0.0) {
         zone = top ? (left ? ZONE_TL : ZONE_TR) : (left ? ZONE_BL : ZONE_BR);
-        if (u > corner_len && u <= corner_len + gap && f_disc <= 0.0) {
+        if (u > cl && u <= cl + gap && f_disc <= 0.0) {
             discard;
         }
-    } else if (u <= corner_len + gap) {
+    } else if (u <= cl + gap) {
         discard;
     } else if (vertical) {
         zone = (dl <= dr) ? ZONE_LEFT : ZONE_RIGHT;
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 70d8ccf..ad951b7 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -2890,16 +2890,21 @@ pub unsafe fn get_border_zone(window: *mut crate::window::Window, lx: f64, ly: f
 
     // Corner squares of `corner_len`, measured from the content corners —
     // the same length draw_borders gives the corner handles.
+    // Floored at the band, then clamped per AXIS to 0.45 of that side, the
+    // same rule the shader draws with: two corner runs on one side must
+    // never meet, or its midpoint would resize diagonally.
     let corner_len = (crate::window::border_corner_len(
         bw_unscaled,
         (*(*window).server).wm.layout.border_corner_length,
         0.0,
     ) * scale)
         .max(bw);
-    let corner_l = rx < corner_len;
-    let corner_r = rx >= content_w - corner_len;
-    let corner_t = ry < corner_len;
-    let corner_b = ry >= content_h - corner_len;
+    let cl_x = corner_len.min(content_w * 0.45);
+    let cl_y = corner_len.min(content_h * 0.45);
+    let corner_l = rx < cl_x;
+    let corner_r = rx >= content_w - cl_x;
+    let corner_t = ry < cl_y;
+    let corner_b = ry >= content_h - cl_y;
 
     if (corner_l || corner_r) && (corner_t || corner_b) {
         return BorderZone::Resize(crate::window::Edges {
diff --git a/src/server/window.rs b/src/server/window.rs
index be916a0..4a029b6 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -3815,7 +3815,11 @@ impl Window {
                 self.border.frame,
                 band_screen as f32,
                 (band_screen as f32 * layout.border_taper.clamp(0.0, 1.0)).max(2.0),
-                (px(cl) as f32).max(band_screen as f32),
+                // Floored at the band; the shader clamps the run per SIDE
+                // (0.45 of that side), so a long side carries the full
+                // configured length while a short one shortens. The hit test
+                // applies the same per-axis clamp.
+                (px(cl) as f64).max(band_screen) as f32,
                 px(g) as f32,
                 layout.border_swell_curve,
                 (layout.border_corner_bulge as f64).min(short_side * 0.3) as f32,