Add transparency + Blur

This commit is contained in:
2026-07-03 02:34:29 -04:00
parent 5327a22a63
commit f5abfe5bc6
5 changed files with 323 additions and 2 deletions
+24
View File
@@ -75,6 +75,30 @@ effects = {
only_floating = false,
shadow_clip = true,
},
transparency = {
enabled = true,
opacity_fullscreen = 1.0,
opacity_focused = 0.98,
opacity_unfocused = 0.96,
nogaps_transparent = true,
only_floating = false,
fullscreen_transparent = false,
blur = {
enabled = true,
radius = 15,
passes = 3,
noise = 0.05,
nogaps_blur = true,
only_floating = false,
fullscreen_blur = false,
blur_always = true,
},
},
},
}
+1
View File
@@ -36,6 +36,7 @@ typedef struct Client {
unsigned int bw;
int corner_radius;
struct wlr_scene_shadow *shadow;
struct wlr_scene_blur *blur;
/* WARM — accessed less frequently */
struct wl_list link;
+67
View File
@@ -676,6 +676,71 @@ parse_shadows(lua_State *L, CfgEffectsWindowShadows *s)
lua_pop(L, 1); /* pop shadows table */
}
static void
parse_blur(lua_State *L, CfgEffectsWindowBlur *b)
{
lua_getfield(L, -1, "blur");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
b->enabled = lua_get_bool(L, "enabled", false);
b->radius = lua_get_int(L, "radius", 0);
if (b->radius < 0) b->radius = 0;
b->passes = lua_get_int(L, "passes", 0);
if (b->passes < 0) b->passes = 0;
double n = lua_get_double(L, "noise", 0.0);
if (n < 0.0) n = 0.0;
if (n > 1.0) n = 1.0;
b->noise = (float)n;
b->nogaps_blur = lua_get_bool(L, "nogaps_blur", false);
b->only_floating = lua_get_bool(L, "only_floating", false);
b->fullscreen_blur = lua_get_bool(L, "fullscreen_blur", false);
b->blur_always = lua_get_bool(L, "blur_always", false);
lua_pop(L, 1); /* pop blur table */
}
static void
parse_transparency(lua_State *L, CfgEffectsWindowTransparency *t)
{
lua_getfield(L, -1, "transparency");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
t->enabled = lua_get_bool(L, "enabled", false);
double op;
op = lua_get_double(L, "opacity_fullscreen", 1.0);
if (op < 0.0) op = 0.0;
if (op > 1.0) op = 1.0;
t->opacity_fullscreen = (float)op;
op = lua_get_double(L, "opacity_focused", 1.0);
if (op < 0.0) op = 0.0;
if (op > 1.0) op = 1.0;
t->opacity_focused = (float)op;
op = lua_get_double(L, "opacity_unfocused", 1.0);
if (op < 0.0) op = 0.0;
if (op > 1.0) op = 1.0;
t->opacity_unfocused = (float)op;
t->nogaps_transparent = lua_get_bool(L, "nogaps_transparent", false);
t->only_floating = lua_get_bool(L, "only_floating", false);
t->fullscreen_transparent = lua_get_bool(L, "fullscreen_transparent", false);
parse_blur(L, &t->blur);
lua_pop(L, 1); /* pop transparency table */
}
static void
parse_effects(lua_State *L, CfgEffects *e)
{
@@ -700,6 +765,8 @@ parse_effects(lua_State *L, CfgEffects *e)
parse_shadows(L, &e->windows.shadows);
parse_transparency(L, &e->windows.transparency);
lua_pop(L, 2); /* pop windows + effects */
}
+27 -2
View File
@@ -121,9 +121,34 @@ typedef struct {
bool shadow_clip;
} CfgEffectsWindowShadows;
/* Window transparency */
typedef struct {
int corner_radius; /* 0 = off, >0 = radius in pixels */
CfgEffectsWindowShadows shadows;
bool enabled;
int radius;
int passes;
float noise;
bool nogaps_blur;
bool only_floating;
bool fullscreen_blur;
bool blur_always;
} CfgEffectsWindowBlur;
typedef struct {
bool enabled;
float opacity_fullscreen;
float opacity_focused;
float opacity_unfocused;
bool nogaps_transparent;
bool only_floating;
bool fullscreen_transparent;
CfgEffectsWindowBlur blur;
} CfgEffectsWindowTransparency;
typedef struct {
int corner_radius; /* 0 = off, >0 = radius in pixels */
CfgEffectsWindowShadows shadows;
CfgEffectsWindowTransparency transparency;
} CfgEffectsWindows;
typedef struct {
+204
View File
@@ -1756,6 +1756,14 @@ static void gpureset(struct wl_listener *listener, void *data) {
wlr_compositor_set_renderer(compositor, drw);
if (cfg.effects.windows.transparency.blur.enabled) {
wlr_scene_set_blur_data(scene,
cfg.effects.windows.transparency.blur.passes,
cfg.effects.windows.transparency.blur.radius,
cfg.effects.windows.transparency.blur.noise,
1.0f, 1.0f, 1.0f);
}
wl_list_for_each(m, &mons, link) {
wlr_output_init_render(m->wlr_output, alloc, drw);
}
@@ -2158,6 +2166,22 @@ apply_surface_corners(struct wlr_scene_node *node, int radius)
}
}
static void
apply_surface_opacity(struct wlr_scene_node *node, float opacity)
{
opacity = fmaxf(0.0f, fminf(1.0f, opacity));
if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
wlr_scene_buffer_set_opacity(buffer, opacity);
} else if (node->type == WLR_SCENE_NODE_TREE) {
struct wlr_scene_tree *tree = wlr_scene_tree_from_node(node);
struct wlr_scene_node *child;
wl_list_for_each(child, &tree->children, link) {
apply_surface_opacity(child, opacity);
}
}
}
static void mapnotify(struct wl_listener *listener, void *data) {
/* Called when the surface is mapped, or ready to display on-screen. */
Client *p = nullptr;
@@ -2224,6 +2248,30 @@ static void mapnotify(struct wl_listener *listener, void *data) {
: wlr_scene_subsurface_tree_create(c->scene, client_surface(c));
c->scene_surface->node.data = c;
/* Create blur node (always created, visibility controlled via set_enabled) */
c->blur = wlr_scene_blur_create(c->scene,
(int)(c->geom.width - 2 * c->bw),
(int)(c->geom.height - 2 * c->bw));
if (c->blur) {
wlr_scene_node_set_position(&c->blur->node, c->bw, c->bw);
/* Place blur BELOW surface so it samples background BEFORE the sharp surface composites */
wlr_scene_node_place_below(&c->blur->node, &c->scene_surface->node);
if (c->shadow)
wlr_scene_node_place_above(&c->blur->node, &c->shadow->node);
wlr_scene_blur_set_corner_radius(c->blur, c->corner_radius);
wlr_scene_blur_set_alpha(c->blur, 1.0f);
wlr_scene_blur_set_strength(c->blur, 1.0f);
/* Set transparency mask source so SceneFX only renders blur where window has alpha < 1.0 */
struct wlr_scene_node *child;
wl_list_for_each(child, &c->scene_surface->children, link) {
if (child->type == WLR_SCENE_NODE_BUFFER) {
wlr_scene_blur_set_transparency_mask_source(c->blur,
wlr_scene_buffer_from_node(child));
break;
}
}
}
wlr_scene_node_raise_to_top(&c->border_bg->node);
/* Initialize client geometry with room for border */
@@ -2635,6 +2683,53 @@ static void rendermon(struct wl_listener *listener, void *data) {
}
}
/* Apply window transparency */
{
Client *c;
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (!cfg.effects.windows.transparency.enabled)
continue;
if (!VISIBLEON(c, m))
continue;
if (!c->scene_surface)
continue;
bool transparent = true;
/* fullscreen: only if fullscreen_transparent */
if (c->isfullscreen) {
transparent = cfg.effects.windows.transparency.fullscreen_transparent;
} else {
/* only_floating: don't apply to tiled */
if (cfg.effects.windows.transparency.only_floating && !c->isfloating)
transparent = false;
/* nogaps_transparent: don't apply if gaps are off */
if (!cfg.effects.windows.transparency.nogaps_transparent && !c->mon->gaps)
transparent = false;
/* smart_gaps edge case */
if (!cfg.effects.windows.transparency.nogaps_transparent
&& cfg.appearance.smart_gaps && !c->isfloating
&& c->geom.x == c->mon->w.x && c->geom.y == c->mon->w.y
&& c->geom.x + c->geom.width == c->mon->w.x + c->mon->w.width
&& c->geom.y + c->geom.height == c->mon->w.y + c->mon->w.height)
transparent = false;
}
float opacity = 1.0f;
if (transparent) {
if (c->isfullscreen)
opacity = cfg.effects.windows.transparency.opacity_fullscreen;
else if (c == focustop(selmon))
opacity = cfg.effects.windows.transparency.opacity_focused;
else
opacity = cfg.effects.windows.transparency.opacity_unfocused;
}
apply_surface_opacity(&c->scene_surface->node, opacity);
}
}
wlr_scene_output_commit(m->scene_output, nullptr);
skip:
@@ -2774,6 +2869,51 @@ void resize(Client *c, struct wlr_box geo, int interact) {
}
}
/* Blur re-apply */
if (c->blur) {
int cw = (int)c->geom.width;
int ch = (int)c->geom.height;
int bw_i = (int)c->bw;
wlr_scene_blur_set_size(c->blur, cw - 2*bw_i, ch - 2*bw_i);
wlr_scene_node_set_position(&c->blur->node, bw_i, bw_i);
if (effective_r > 0 && !c->isfullscreen)
wlr_scene_blur_set_corner_radii(c->blur, corner_radii_all(effective_r));
else
wlr_scene_blur_set_corner_radius(c->blur, 0);
struct clipped_region cr = {
.area = {bw_i, bw_i, cw - 2*bw_i, ch - 2*bw_i},
.corners = effective_r > 0 ? corner_radii_all(effective_r) : corner_radii_all(0),
};
wlr_scene_blur_set_clipped_region(c->blur, cr);
/* Evaluate blur visibility */
bool blur_enabled = cfg.effects.windows.transparency.blur.enabled;
if (!cfg.effects.windows.transparency.blur.nogaps_blur && !c->mon->gaps)
blur_enabled = false;
/* smart_gaps edge case */
if (!cfg.effects.windows.transparency.blur.nogaps_blur
&& cfg.appearance.smart_gaps && !c->isfloating
&& c->geom.x == c->mon->w.x && c->geom.y == c->mon->w.y
&& c->geom.x + c->geom.width == c->mon->w.x + c->mon->w.width
&& c->geom.y + c->geom.height == c->mon->w.y + c->mon->w.height)
blur_enabled = false;
/* fullscreen */
if (c->isfullscreen)
blur_enabled = cfg.effects.windows.transparency.blur.fullscreen_blur;
/* only_floating */
if (!c->isfullscreen && cfg.effects.windows.transparency.blur.only_floating && !c->isfloating)
blur_enabled = false;
/* blur_always: for inherently-transparent windows */
if (!blur_enabled && !c->isfullscreen
&& cfg.effects.windows.transparency.blur.blur_always)
blur_enabled = true;
/* scratchpad windows */
if (c->isscratchpad)
blur_enabled = false;
wlr_scene_node_set_enabled(&c->blur->node, blur_enabled);
}
/* 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);
@@ -2899,6 +3039,8 @@ static void setfullscreen(Client *c, int fullscreen) {
if (c->border_bg) {
wlr_scene_node_set_enabled(&c->border_bg->node, false);
}
if (c->blur)
wlr_scene_node_set_enabled(&c->blur->node, false);
} else {
c->corner_radius = config_get_corner_radius();
if (c->border_bg) {
@@ -3186,6 +3328,39 @@ reapply_client_appearance(void)
wlr_scene_node_set_enabled(&c->shadow->node, false);
}
}
/* Blur re-apply on config reload */
if (cfg.effects.windows.transparency.blur.enabled) {
if (!c->blur) {
c->blur = wlr_scene_blur_create(c->scene,
(int)(c->geom.width - 2 * (int)c->bw),
(int)(c->geom.height - 2 * (int)c->bw));
if (c->blur) {
wlr_scene_node_set_position(&c->blur->node, c->bw, c->bw);
if (c->scene_surface)
wlr_scene_node_place_below(&c->blur->node, &c->scene_surface->node);
if (c->shadow)
wlr_scene_node_place_above(&c->blur->node, &c->shadow->node);
wlr_scene_blur_set_alpha(c->blur, 1.0f);
wlr_scene_blur_set_strength(c->blur, 1.0f);
/* Set transparency mask source */
struct wlr_scene_node *child;
wl_list_for_each(child, &c->scene_surface->children, link) {
if (child->type == WLR_SCENE_NODE_BUFFER) {
wlr_scene_blur_set_transparency_mask_source(c->blur,
wlr_scene_buffer_from_node(child));
break;
}
}
}
}
if (c->blur && c->corner_radius > 0 && !c->isfullscreen)
wlr_scene_blur_set_corner_radius(c->blur, c->corner_radius);
} else {
if (c->blur) {
wlr_scene_node_destroy(&c->blur->node);
c->blur = nullptr;
}
}
}
if (!client_is_unmanaged(c)) {
float *color = c->isurgent ? cfg.appearance.urgent_color
@@ -3244,6 +3419,15 @@ on_config_reload(const Config *newcfg, void *ud)
nc->buttons = NULL;
nc->scrolls = NULL;
/* Re-apply global blur parameters */
if (cfg.effects.windows.transparency.blur.enabled) {
wlr_scene_set_blur_data(scene,
cfg.effects.windows.transparency.blur.passes,
cfg.effects.windows.transparency.blur.radius,
cfg.effects.windows.transparency.blur.noise,
1.0f, 1.0f, 1.0f);
}
reapply_monitor_config();
reapply_client_appearance();
reapply_client_rules();
@@ -3274,6 +3458,15 @@ do_reload(void)
fresh.buttons = NULL;
fresh.scrolls = NULL;
/* Re-apply global blur parameters */
if (cfg.effects.windows.transparency.blur.enabled) {
wlr_scene_set_blur_data(scene,
cfg.effects.windows.transparency.blur.passes,
cfg.effects.windows.transparency.blur.radius,
cfg.effects.windows.transparency.blur.noise,
1.0f, 1.0f, 1.0f);
}
reapply_monitor_config();
reapply_client_appearance();
reapply_client_rules();
@@ -3365,6 +3558,17 @@ static void setup(void) {
die("couldn't create renderer");
wl_signal_add(&drw->events.lost, &gpu_reset);
/* Set global blur parameters */
if (cfg.effects.windows.transparency.blur.enabled) {
wlr_scene_set_blur_data(scene,
cfg.effects.windows.transparency.blur.passes,
cfg.effects.windows.transparency.blur.radius,
cfg.effects.windows.transparency.blur.noise,
1.0f, /* brightness */
1.0f, /* contrast */
1.0f); /* saturation */
}
/* Create shm, drm and linux_dmabuf interfaces by ourselves.
* The simplest way is to call:
* wlr_renderer_init_wl_display(drw);