Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
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#"