feat(effects): implement window rounded corners via scenefx

- Parse effects.windows.rounded/rounding from Lua config
- Add CfgEffectsWindows and CfgEffects structs to Config
- Switch to fx_renderer_create for scenefx support
- Replace 5 wlr/types/wlr_scene.h includes with scenefx variant
- Add config_get_corner_radius() helper (light=6px, heavy=14px)
- Apply corner_radii_top/bottom/left/right to 4 border rects
- Apply wlr_scene_buffer_set_corner_radius to surface buffers
- Disable on fullscreen, restore on exit
- Re-apply on resize and config hot-reload
- Add -Wno-extra-semi for scenefx header compatibility
- All targets compile cleanly (make test passes)
This commit is contained in:
2026-07-02 05:00:26 -04:00
parent 20e38fe682
commit 4ac03d4059
9 changed files with 146 additions and 15 deletions
+4 -1
View File
@@ -35,7 +35,7 @@ XLIBS = xcb xcb-icccm
DISTRO =
LUA_PKG = lua5.4
PKGS = wayland-server xkbcommon libinput $(LUA_PKG) $(XLIBS)
PKGS = wayland-server xkbcommon libinput $(LUA_PKG) $(XLIBS) scenefx-0.5
CFLAGS = `$(PKG_CONFIG) --cflags $(PKGS) wlroots-0.20` \
-I. -Iinclude -Isrc -Iparser -Iprotocols \
@@ -59,9 +59,11 @@ release: CFLAGS += -Werror -Wpedantic -Wmissing-prototypes -Wstrict-prototypes \
-Wold-style-definition -Wmissing-declarations -Wimplicit-fallthrough \
-Wno-gnu-zero-variadic-macro-arguments \
-Wno-c23-extensions \
-Wno-extra-semi \
-march=native
release: peachwm peachmsg/peachmsg
debug: CC = clang
debug: CC = clang
debug: CFLAGS += -Werror -Weverything \
-Wno-padded -Wno-unsafe-buffer-usage \
@@ -80,6 +82,7 @@ debug: CFLAGS += -Werror -Weverything \
-Wno-tentative-definition-compat -Wno-missing-variable-declarations \
-Wno-gnu-zero-variadic-macro-arguments \
-Wno-c23-extensions \
-Wno-extra-semi \
-g3 -fno-omit-frame-pointer -fsanitize=address,undefined,leak \
-fsanitize-trap=all
debug: LDLIBS += -fsanitize=address,undefined,leak
+1
View File
@@ -53,6 +53,7 @@ typedef struct Client {
struct wl_listener set_hints;
#endif
unsigned int bw;
int corner_radius;
uint32_t tags;
int isfloating, isurgent, isfullscreen, isscratchpad;
uint32_t resize; /* configure serial of a pending resize */
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <scenefx/types/wlr_scene.h>
#include <wlr/types/wlr_session_lock_v1.h>
#include "wlr_ext_workspace_v1.h"
+32
View File
@@ -618,6 +618,37 @@ parse_autostart(lua_State *L, Config *cfg)
lua_pop(L, 1);
}
static void
parse_effects(lua_State *L, CfgEffects *e)
{
/* Defaults */
e->windows.rounded = false;
strcpy(e->windows.rounding, "off");
lua_getglobal(L, "effects");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
lua_getfield(L, -1, "windows");
if (!lua_istable(L, -1)) {
lua_pop(L, 2); /* pop windows + effects */
return;
}
e->windows.rounded = lua_get_bool(L, "rounded", false);
char scratch[CFG_MAX_STRLEN] = {0};
lua_get_string(L, "rounding", scratch, sizeof(scratch));
if (!strcmp(scratch, "light") || !strcmp(scratch, "heavy"))
strncpy(e->windows.rounding, scratch, CFG_MAX_STRLEN - 1);
else
strcpy(e->windows.rounding, "off");
lua_pop(L, 2); /* pop windows + effects */
}
/* Public API */
char *
@@ -686,6 +717,7 @@ config_load(const char *path, Config *cfg)
parse_buttons (L, cfg);
parse_scrolls (L, cfg);
parse_autostart (L, cfg);
parse_effects (L, &cfg->effects);
lua_close(L);
return 0;
+13
View File
@@ -104,6 +104,17 @@ typedef struct {
char cmd[CFG_MAX_AUTOSTART_CMD];
} CfgAutostart;
/* Effects */
typedef struct {
bool rounded;
char rounding[CFG_MAX_STRLEN];
} CfgEffectsWindows;
typedef struct {
CfgEffectsWindows windows;
} CfgEffects;
/* the whole shebang */
typedef struct {
@@ -135,6 +146,8 @@ typedef struct {
/* Per-workspace (tag) default layouts, indexed by tag index 0-8 */
char workspace_layouts[9][CFG_MAX_STRLEN];
CfgEffects effects;
} Config;
/* Public API */
+1 -1
View File
@@ -10,7 +10,7 @@
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_layer_shell_v1.h>
#include <wlr/types/wlr_scene.h>
#include <scenefx/types/wlr_scene.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/edges.h>
+90 -8
View File
@@ -20,6 +20,7 @@
#include <wlr/backend/libinput.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <scenefx/render/fx_renderer/fx_renderer.h>
#include <wlr/types/wlr_alpha_modifier_v1.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
@@ -49,7 +50,7 @@
#include <wlr/types/wlr_primary_selection.h>
#include <wlr/types/wlr_primary_selection_v1.h>
#include <wlr/types/wlr_relative_pointer_v1.h>
#include <wlr/types/wlr_scene.h>
#include <scenefx/types/wlr_scene.h>
#include <wlr/types/wlr_screencopy_v1.h>
#include <wlr/types/wlr_seat.h>
#include <wlr/types/wlr_server_decoration.h>
@@ -385,6 +386,18 @@ static char config_path[1024];
/* function implementations */
static int
config_get_corner_radius(void)
{
if (!cfg.effects.windows.rounded)
return 0;
if (strcmp(cfg.effects.windows.rounding, "light") == 0)
return 6;
if (strcmp(cfg.effects.windows.rounding, "heavy") == 0)
return 14;
return 0; /* "off" or unknown */
}
/* Apply a workspace default layout for the given tag index (0-based) on monitor
* m */
static void apply_workspace_layout(Monitor *m, int tag_idx) {
@@ -1664,7 +1677,7 @@ static void gpureset(struct wl_listener *listener, void *data) {
struct wlr_renderer *old_drw = drw;
struct wlr_allocator *old_alloc = alloc;
struct Monitor *m;
if (!(drw = wlr_renderer_autocreate(backend)))
if (!(drw = fx_renderer_create(backend)))
die("couldn't recreate renderer");
if (!(alloc = wlr_allocator_autocreate(backend, drw)))
@@ -2059,6 +2072,14 @@ static void locksession(struct wl_listener *listener, void *data) {
wlr_session_lock_v1_send_locked(session_lock);
}
static void
set_buffer_corner_radius(struct wlr_scene_buffer *buffer,
int sx, int sy, void *user_data)
{
int radius = *(int *)user_data;
wlr_scene_buffer_set_corner_radius(buffer, radius);
}
static void mapnotify(struct wl_listener *listener, void *data) {
/* Called when the surface is mapped, or ready to display on-screen. */
Client *p = nullptr;
@@ -2070,11 +2091,7 @@ static void mapnotify(struct wl_listener *listener, void *data) {
c->scene = client_surface(c)->data = wlr_scene_tree_create(layers[LyrTile]);
/* Enabled later by a call to arrange() */
wlr_scene_node_set_enabled(&c->scene->node, client_is_unmanaged(c));
c->scene_surface =
c->type == XDGShell
? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg)
: wlr_scene_subsurface_tree_create(c->scene, client_surface(c));
c->scene->node.data = c->scene_surface->node.data = c;
c->scene->node.data = c;
client_get_geometry(c, &c->geom);
@@ -2099,6 +2116,28 @@ static void mapnotify(struct wl_listener *listener, void *data) {
c->border[i]->node.data = c;
}
{
int r = config_get_corner_radius();
c->corner_radius = r;
if (r > 0) {
wlr_scene_rect_set_corner_radii(c->border[0], corner_radii_top(r));
wlr_scene_rect_set_corner_radii(c->border[1], corner_radii_bottom(r));
wlr_scene_rect_set_corner_radii(c->border[2], corner_radii_left(r));
wlr_scene_rect_set_corner_radii(c->border[3], corner_radii_right(r));
}
}
c->scene_surface =
c->type == XDGShell
? wlr_scene_xdg_surface_create(c->scene, c->surface.xdg)
: wlr_scene_subsurface_tree_create(c->scene, client_surface(c));
c->scene_surface->node.data = c;
if (c->corner_radius > 0) {
wlr_scene_node_for_each_buffer(&c->scene_surface->node,
set_buffer_corner_radius, &c->corner_radius);
}
/* Initialize client geometry with room for border */
client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT |
WLR_EDGE_RIGHT);
@@ -2552,6 +2591,14 @@ void resize(Client *c, struct wlr_box geo, int interact) {
wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw,
c->bw);
if (c->corner_radius > 0 && !c->isfullscreen) {
int r = c->corner_radius;
wlr_scene_rect_set_corner_radii(c->border[0], corner_radii_top(r));
wlr_scene_rect_set_corner_radii(c->border[1], corner_radii_bottom(r));
wlr_scene_rect_set_corner_radii(c->border[2], corner_radii_left(r));
wlr_scene_rect_set_corner_radii(c->border[3], corner_radii_right(r));
}
/* this is a no-op if size hasn't changed */
c->resize =
client_set_size(c, c->geom.width - 2 * c->bw, c->geom.height - 2 * c->bw);
@@ -2674,6 +2721,25 @@ static void setfullscreen(Client *c, int fullscreen) {
if (!c->mon || !client_surface(c)->mapped)
return;
c->bw = fullscreen ? 0 : cfg.appearance.border_px;
if (fullscreen) {
c->corner_radius = 0;
int zero = 0;
wlr_scene_node_for_each_buffer(&c->scene_surface->node,
set_buffer_corner_radius, &zero);
for (int i = 0; i < 4; i++)
wlr_scene_rect_set_corner_radius(c->border[i], 0);
} else {
int r = config_get_corner_radius();
c->corner_radius = r;
if (r > 0) {
wlr_scene_rect_set_corner_radii(c->border[0], corner_radii_top(r));
wlr_scene_rect_set_corner_radii(c->border[1], corner_radii_bottom(r));
wlr_scene_rect_set_corner_radii(c->border[2], corner_radii_left(r));
wlr_scene_rect_set_corner_radii(c->border[3], corner_radii_right(r));
wlr_scene_node_for_each_buffer(&c->scene_surface->node,
set_buffer_corner_radius, &r);
}
}
client_set_fullscreen(c, fullscreen);
if (c->isscratchpad) {
wlr_scene_node_reparent(&c->scene->node, layers[LyrScratch]);
@@ -2910,6 +2976,22 @@ reapply_client_appearance(void)
}
wl_list_for_each(c, &clients, link) {
c->bw = !c->isfullscreen ? cfg.appearance.border_px : 0;
if (!client_is_unmanaged(c)) {
int r = c->isfullscreen ? 0 : config_get_corner_radius();
c->corner_radius = r;
if (r > 0 && !c->isfullscreen) {
wlr_scene_rect_set_corner_radii(c->border[0], corner_radii_top(r));
wlr_scene_rect_set_corner_radii(c->border[1], corner_radii_bottom(r));
wlr_scene_rect_set_corner_radii(c->border[2], corner_radii_left(r));
wlr_scene_rect_set_corner_radii(c->border[3], corner_radii_right(r));
} else {
for (int i = 0; i < 4; i++)
wlr_scene_rect_set_corner_radius(c->border[i], 0);
}
int radius = r;
wlr_scene_node_for_each_buffer(&c->scene_surface->node,
set_buffer_corner_radius, &radius);
}
if (!client_is_unmanaged(c)) {
float *color = c->isurgent ? cfg.appearance.urgent_color
: c == focustop(c->mon) ? cfg.appearance.focus_color
@@ -3064,7 +3146,7 @@ static void setup(void) {
* can also specify a renderer using the WLR_RENDERER env var.
* The renderer is responsible for defining the various pixel formats it
* supports for shared memory, this configures that for clients. */
if (!(drw = wlr_renderer_autocreate(backend)))
if (!(drw = fx_renderer_create(backend)))
die("couldn't create renderer");
wl_signal_add(&drw->events.lost, &gpu_reset);
+1 -1
View File
@@ -6,7 +6,7 @@
* as a group, with monocle-style cycling within the scratchpad.
*/
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <scenefx/types/wlr_scene.h>
#include <wlr/util/log.h>
#include "client.h"
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include <wlr/types/wlr_scene.h>
#include <scenefx/types/wlr_scene.h>
#include "client.h"
#include "monitor.h"
#include "common.h"