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

commit7054266c7acea8b276a68d3e0dfa98f0559ee757
parent9ec4e6d9c5
authorLucas Galante <[email protected]>
date2026-07-20 15:10
feat: squircle window corners — scenefx corner cut follows corner_shape

scenefx's rounded-corner shaders cut circular arcs, so the superellipse
corners cce-ui clients draw (corner_shape > 2) got shaved by the
compositor's cut — visibly clipping e.g. the designer's focused-pane
border at window corners. get_dist now has a radius-normalized Lp branch
behind a renderer-global corner_shape uniform wired through every
rounded draw (tex, quad, quad_round, quad_grad_round, box_shadow clip),
set from the window_manager.corner_shape config key — the same key the
clients read — at load and live reload.

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

 scenefx/include/render/fx_renderer/shaders.h       |  8 +++++
 .../scenefx/render/fx_renderer/fx_renderer.h       |  6 ++++
 scenefx/render/fx_renderer/shaders.c               | 21 ++++++++++++
 .../render/fx_renderer/shaders/corner_alpha.frag   | 21 +++++++++++-
 src/server/config.rs                               | 38 +++++++++++++++++++++-
 5 files changed, 92 insertions(+), 2 deletions(-)

diff --git a/scenefx/include/render/fx_renderer/shaders.h b/scenefx/include/render/fx_renderer/shaders.h
index 98656fc..cc64327 100644
--- a/scenefx/include/render/fx_renderer/shaders.h
+++ b/scenefx/include/render/fx_renderer/shaders.h
@@ -27,11 +27,19 @@ struct shader_corner_radii {
 	GLint top_right;
 	GLint bottom_left;
 	GLint bottom_right;
+	// The program's shared "corner_shape" uniform (see corner_alpha.frag).
+	// One location per program; radius and clip_radius instances of the same
+	// program resolve to the same location, which is harmless.
+	GLint shape;
 };
 
 void uniform_corner_radii_set(const struct shader_corner_radii *uniform,
 		const struct fx_corner_fradii *corners);
 
+// Renderer-global corner-shape exponent fed to every rounded-corner shader:
+// 2 (default) = circular arcs, > 2 = superellipse squircle corners.
+float fx_corner_shape(void);
+
 struct quad_shader {
 	GLuint program;
 	GLint proj;
diff --git a/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h b/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h
index cbd312d..74d914e 100644
--- a/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h
+++ b/scenefx/include/scenefx/render/fx_renderer/fx_renderer.h
@@ -17,6 +17,12 @@ struct fx_renderer *fx_get_renderer(struct wlr_renderer *wlr_renderer);
 bool fx_renderer_check_ext(struct wlr_renderer *renderer, const char *ext);
 GLuint fx_renderer_get_buffer_fbo(struct wlr_renderer *renderer, struct wlr_buffer *buffer);
 
+// Renderer-global corner-shape exponent for every rounded-corner cut
+// (windows, blur, clipped regions): 2 (the default) is a circular arc,
+// > 2 a superellipse "squircle" with continuous curvature. Plain state —
+// callable before the renderer exists and without a current GL context.
+void fx_renderer_set_corner_shape(float shape);
+
 //
 // fx_texture
 //
diff --git a/scenefx/render/fx_renderer/shaders.c b/scenefx/render/fx_renderer/shaders.c
index 7b8a0d1..3b321fb 100644
--- a/scenefx/render/fx_renderer/shaders.c
+++ b/scenefx/render/fx_renderer/shaders.c
@@ -3,6 +3,7 @@
 #include <stdlib.h>
 #include <wlr/util/log.h>
 #include <scenefx/types/fx/clipped_region.h>
+#include <scenefx/render/fx_renderer/fx_renderer.h>
 
 #include "render/fx_renderer/shaders.h"
 
@@ -101,12 +102,25 @@ void load_gl_proc(void *proc_ptr, const char *name) {
 	*(void **)proc_ptr = proc;
 }
 
+// Renderer-global corner-shape exponent (circular arcs by default); set once
+// from the compositor's config via fx_renderer_set_corner_shape.
+static float global_corner_shape = 2.0f;
+
+void fx_renderer_set_corner_shape(float shape) {
+	global_corner_shape = shape;
+}
+
+float fx_corner_shape(void) {
+	return global_corner_shape;
+}
+
 void uniform_corner_radii_set(const struct shader_corner_radii *uniform,
 		const struct fx_corner_fradii *corners) {
 	glUniform1f(uniform->top_left, corners->top_left);
 	glUniform1f(uniform->top_right, corners->top_right);
 	glUniform1f(uniform->bottom_left, corners->bottom_left);
 	glUniform1f(uniform->bottom_right, corners->bottom_right);
+	glUniform1f(uniform->shape, global_corner_shape);
 }
 // Shaders
 
@@ -137,6 +151,7 @@ bool link_quad_program(struct quad_shader *shader, bool clip) {
 	shader->effects.clip_radius.top_right = glGetUniformLocation(prog, "clip_radius_top_right");
 	shader->effects.clip_radius.bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
 	shader->effects.clip_radius.bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+	shader->effects.clip_radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	return true;
 }
@@ -191,6 +206,7 @@ bool link_quad_round_program(struct quad_round_shader *shader) {
 	shader->radius.top_right = glGetUniformLocation(prog, "radius_top_right");
 	shader->radius.bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
 	shader->radius.bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+	shader->radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	shader->clip_size = glGetUniformLocation(prog, "clip_size");
 	shader->clip_position = glGetUniformLocation(prog, "clip_position");
@@ -198,6 +214,7 @@ bool link_quad_round_program(struct quad_round_shader *shader) {
 	shader->clip_radius.top_right = glGetUniformLocation(prog, "clip_radius_top_right");
 	shader->clip_radius.bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
 	shader->clip_radius.bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+	shader->clip_radius.shape = glGetUniformLocation(prog, "corner_shape");
 	shader->fade_inset = glGetUniformLocation(prog, "fade_inset");
 	shader->fade_mode = glGetUniformLocation(prog, "fade_mode");
 
@@ -227,6 +244,7 @@ bool link_quad_grad_round_program(struct quad_grad_round_shader *shader, int max
 	shader->radius.top_right = glGetUniformLocation(prog, "radius_top_right");
 	shader->radius.bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
 	shader->radius.bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+	shader->radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	shader->grad_size = glGetUniformLocation(prog, "grad_size");
 	shader->colors = glGetUniformLocation(prog, "colors");
@@ -274,6 +292,7 @@ bool link_tex_program(struct tex_shader *shader, enum fx_tex_shader_source sourc
 	shader->effects.radius.top_right = glGetUniformLocation(prog, "radius_top_right");
 	shader->effects.radius.bottom_left = glGetUniformLocation(prog, "radius_bottom_left");
 	shader->effects.radius.bottom_right = glGetUniformLocation(prog, "radius_bottom_right");
+	shader->effects.radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	shader->effects.clip_size = glGetUniformLocation(prog, "clip_size");
 	shader->effects.clip_position = glGetUniformLocation(prog, "clip_position");
@@ -281,6 +300,7 @@ bool link_tex_program(struct tex_shader *shader, enum fx_tex_shader_source sourc
 	shader->effects.clip_radius.top_right = glGetUniformLocation(prog, "clip_radius_top_right");
 	shader->effects.clip_radius.bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
 	shader->effects.clip_radius.bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+	shader->effects.clip_radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	return true;
 }
@@ -308,6 +328,7 @@ bool link_box_shadow_program(struct box_shadow_shader *shader) {
 	shader->clip_radius.top_right = glGetUniformLocation(prog, "clip_radius_top_right");
 	shader->clip_radius.bottom_left = glGetUniformLocation(prog, "clip_radius_bottom_left");
 	shader->clip_radius.bottom_right = glGetUniformLocation(prog, "clip_radius_bottom_right");
+	shader->clip_radius.shape = glGetUniformLocation(prog, "corner_shape");
 
 	return true;
 }
diff --git a/scenefx/render/fx_renderer/shaders/corner_alpha.frag b/scenefx/render/fx_renderer/shaders/corner_alpha.frag
index c3f78fa..6c355f8 100644
--- a/scenefx/render/fx_renderer/shaders/corner_alpha.frag
+++ b/scenefx/render/fx_renderer/shaders/corner_alpha.frag
@@ -1,5 +1,24 @@
+// Corner-shape exponent: 2 = circular arc, > 2 = superellipse "squircle"
+// corners — kept in lockstep with the cce-ui clients' corner_shape so the
+// compositor's corner cut lands exactly on the corners the clients draw.
+uniform float corner_shape;
+
 float get_dist(vec2 q, float radius) {
-	return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
+	vec2 p = max(q, 0.0);
+	if (corner_shape > 2.001 && radius > 0.0 && p.x > 0.0 && p.y > 0.0) {
+		// Superellipse (Lp-norm) corner, radius-normalized so the pow()
+		// arguments stay near 1 (mediump-safe). The Lp gradient is not unit
+		// length, so the distance carries a first-order |grad| correction —
+		// exact on the boundary, where the AA smoothstep samples it.
+		vec2 u = p / radius;
+		float lp = max(pow(pow(u.x, corner_shape) + pow(u.y, corner_shape),
+			1.0 / corner_shape), 1e-4);
+		vec2 g = vec2(pow(u.x / lp, corner_shape - 1.0),
+			pow(u.y / lp, corner_shape - 1.0));
+		return min(max(q.x, q.y), 0.0)
+			+ radius * (lp - 1.0) / max(length(g), 1e-4);
+	}
+	return min(max(q.x, q.y), 0.0) + length(p) - radius;
 }
 
 // Note: Returns 0.0 if outside, 1.0 if inside the bounds. The is_cutout parameter
diff --git a/src/server/config.rs b/src/server/config.rs
index db764b9..2575f30 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -248,6 +248,11 @@ pub struct WindowManagerConfig {
     /// Whether a newly spawned window pulls the viewport over to it. `None` = the default,
     /// which is to centre (what the compositor has always done).
     pub center_on_spawn: Option<bool>,
+    /// Corner-shape exponent for scenefx's rounded-corner cuts (window
+    /// surfaces, blur, shadows' clip): 2 = circular arc, > 2 = superellipse
+    /// squircle. The same `window_manager.corner_shape` key the cce-ui
+    /// clients read, so the compositor's cut lands on the corners they draw.
+    pub corner_shape: Option<f64>,
 }
 
 #[derive(Debug, Deserialize, Clone, Default, PartialEq, Eq)]
@@ -1708,7 +1713,8 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
         let toggle_overview = get_child_arg_string_opt(node, "toggle_overview");
         let window_switcher = get_child_arg_string_opt(node, "window_switcher");
         let center_on_spawn = get_child_arg_bool_opt(node, "center_on_spawn");
-        window_manager = Some(WindowManagerConfig { close_window, toggle_fullscreen, toggle_overview, window_switcher, center_on_spawn });
+        let corner_shape = get_child_arg_f64_opt(node, "corner_shape");
+        window_manager = Some(WindowManagerConfig { close_window, toggle_fullscreen, toggle_overview, window_switcher, center_on_spawn, corner_shape });
     }
 
     Ok(Config {
@@ -1771,6 +1777,19 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
         .and_then(|wm| wm.center_on_spawn)
         .unwrap_or(true);
 
+    // Feed scenefx's rounded-corner shaders the DE-wide corner-shape exponent
+    // (clamped like cce-ui's corner_shape()). Plain C state, safe pre-renderer
+    // and on live reload.
+    let corner_shape = config
+        .window_manager
+        .as_ref()
+        .and_then(|wm| wm.corner_shape)
+        .unwrap_or(2.0)
+        .clamp(2.0, 16.0) as f32;
+    unsafe {
+        crate::ffi::fx_renderer_set_corner_shape(corner_shape);
+    }
+
     state.layout.gap = config.layout.gap as i32;
     state.layout.gap_top = config.layout.gap_top as i32;
     state.layout.gap_left = config.layout.gap_left as i32;
@@ -2141,6 +2160,23 @@ mod tests {
         assert!(parse_kdl_config("layout {\n gap 4\n}").unwrap().window_manager.is_none());
     }
 
+    #[test]
+    fn test_kdl_window_manager_corner_shape() {
+        let set = parse_kdl_config(
+            r#"
+            window_manager {
+                corner_shape (f64)4.5
+            }
+        "#,
+        )
+        .unwrap();
+        assert_eq!(set.window_manager.unwrap().corner_shape, Some(4.5));
+
+        // Absent means "unset"; the apply step then feeds scenefx the circular default.
+        let unset = parse_kdl_config("window_manager {\n center_on_spawn (bool)true\n}").unwrap();
+        assert_eq!(unset.window_manager.unwrap().corner_shape, None);
+    }
+
     #[test]
     fn test_kdl_input_device_classes_parsing() {
         let content = r#"