Add support for background blur

This patch adds a new config option: colors{,2}.blur=no|yes. When
enabled, transparent background are also blurred.

Note that this requires the brand new ext-background-effect-v1
protocol, and specifically, that the compositor implements the blur
effect.
This commit is contained in:
Daniel Eklöf 2025-10-16 13:43:33 +02:00
parent aa26676c43
commit e63150305e
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
13 changed files with 154 additions and 43 deletions

View file

@ -78,6 +78,12 @@
* `[colors-light]` section to `foot.ini`. Replaces `[colors2]`.
* `XTGETTCAP`: added `query-os-name`, returning the OS foot is
compiled for (e.g. _'Linux'_) ([#2209][2209]).
* Preliminary (untested) support for background blur via the new
`ext-background-effect-v1` protocol. Enable by setting
`colors-{dark,light}.blur=yes`. Foot needs to have been **built**
against `wayland-protocols >= 1.45`, and the compositor **must**
implement the `ext-background-effect-v1` protocol, **and** the
`blur` effect.
[2212]: https://codeberg.org/dnkl/foot/issues/2212
[2209]: https://codeberg.org/dnkl/foot/issues/2209

View file

@ -1576,6 +1576,9 @@ parse_color_theme(struct context *ctx, struct color_theme *theme)
(int *)&theme->dim_blend_towards);
}
else if (streq(key, "blur"))
return value_to_bool(ctx, &theme->blur);
else {
LOG_CONTEXTUAL_ERR("not valid option");
return false;
@ -3526,6 +3529,7 @@ config_load(struct config *conf, const char *conf_path,
.scrollback_indicator = false,
.url = false,
},
.blur = false,
},
.initial_color_theme = COLOR_THEME_DARK,
.cursor = {

View file

@ -192,6 +192,8 @@ struct color_theme {
bool search_box_match:1;
uint8_t dim;
} use_custom;
bool blur;
};
enum which_color_theme {

View file

@ -1097,6 +1097,14 @@ The default theme used is *colors-dark*, unless
Default: _default_
*blur*
Boolean. When enabled, foot will blur the background, when it is
transparent. This feature requires the compositor to implement the
_ext-background-effect-v1_ protocol (and specifically, the _blur_
effect).
Default: _no_
*dim-blend-towards*
Which color to blend towards when "auto" dimming a color (see
*dim0*..*dim7* above). One of *black* or *white*. Blending towards

View file

@ -28,6 +28,12 @@ const char version_and_features[] =
" -toplevel-tag"
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
" +blur"
#else
" -blur"
#endif
#if !defined(NDEBUG)
" +assertions"
#else

View file

@ -188,6 +188,10 @@ if (wayland_protocols.version().version_compare('>=1.43'))
wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml']
add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c')
endif
if (wayland_protocols.version().version_compare('>=1.45'))
wl_proto_xml += [wayland_protocols_datadir / 'staging/ext-background-effect/ext-background-effect-v1.xml']
add_project_arguments('-DHAVE_EXT_BACKGROUND_EFFECT=1', language: 'c')
endif
foreach prot : wl_proto_xml
wl_proto_headers += custom_target(

42
osc.c
View file

@ -1458,11 +1458,8 @@ osc_dispatch(struct terminal *term)
case 11:
term->colors.bg = color;
if (!have_alpha) {
alpha = term->colors.active_theme == COLOR_THEME_DARK
? term->conf->colors_dark.alpha
: term->conf->colors_light.alpha;
}
if (!have_alpha)
alpha = term_theme_get(term)->alpha;
const bool changed = term->colors.alpha != alpha;
term->colors.alpha = alpha;
@ -1515,10 +1512,7 @@ osc_dispatch(struct terminal *term)
case 104: {
/* Reset Color Number 'c' (whole table if no parameter) */
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
if (string[0] == '\0') {
LOG_DBG("resetting all colors");
@ -1558,11 +1552,7 @@ osc_dispatch(struct terminal *term)
case 110: /* Reset default text foreground color */
LOG_DBG("resetting foreground color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
term->colors.fg = theme->fg;
term_damage_color(term, COLOR_DEFAULT, 0);
break;
@ -1570,11 +1560,7 @@ osc_dispatch(struct terminal *term)
case 111: { /* Reset default text background color */
LOG_DBG("resetting background color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
bool alpha_changed = term->colors.alpha != theme->alpha;
term->colors.bg = theme->bg;
@ -1593,11 +1579,7 @@ osc_dispatch(struct terminal *term)
case 112: {
LOG_DBG("resetting cursor color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
term->colors.cursor_fg = theme->cursor.text;
term->colors.cursor_bg = theme->cursor.cursor;
@ -1613,11 +1595,7 @@ osc_dispatch(struct terminal *term)
case 117: {
LOG_DBG("resetting selection background color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
term->colors.selection_bg = theme->selection_bg;
break;
}
@ -1625,11 +1603,7 @@ osc_dispatch(struct terminal *term)
case 119: {
LOG_DBG("resetting selection foreground color");
const struct color_theme *theme =
term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
term->colors.selection_fg = theme->selection_fg;
break;
}

View file

@ -312,9 +312,7 @@ color_dim(const struct terminal *term, uint32_t color)
}
}
const struct color_theme *theme = term->colors.active_theme == COLOR_THEME_DARK
? &conf->colors_dark
: &conf->colors_light;
const struct color_theme *theme = term_theme_get(term);
return color_blend_towards(
color,

View file

@ -4809,3 +4809,11 @@ term_theme_toggle(struct terminal *term)
term_damage_margins(term);
render_refresh(term);
}
const struct color_theme *
term_theme_get(const struct terminal *term)
{
return term->colors.active_theme == COLOR_THEME_DARK
? &term->conf->colors_dark
: &term->conf->colors_light;
}

View file

@ -997,6 +997,7 @@ void term_send_size_notification(struct terminal *term);
void term_theme_switch_to_dark(struct terminal *term);
void term_theme_switch_to_light(struct terminal *term);
void term_theme_toggle(struct terminal *term);
const struct color_theme *term_theme_get(const struct terminal *term);
static inline void term_reset_grapheme_state(struct terminal *term)
{

View file

@ -774,6 +774,8 @@ test_section_colors_dark(void)
&conf.colors_dark.table[i]);
}
test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_dark.blur);
test_invalid_key(&ctx, &parse_section_colors_dark, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */
@ -853,6 +855,8 @@ test_section_colors_light(void)
&conf.colors_light.table[i]);
}
test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_light.blur);
test_invalid_key(&ctx, &parse_section_colors_light, "256");
/* TODO: alpha (float in range 0-1, converted to uint16_t) */

View file

@ -1196,6 +1196,27 @@ static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration
.configure = &xdg_toplevel_decoration_configure,
};
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
static void
ext_background_capabilities(
void *data,
struct ext_background_effect_manager_v1 *ext_background_effect_manager_v1,
uint32_t flags)
{
struct wayland *wayl = data;
wayl->have_background_blur =
!!(flags & EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR);
LOG_DBG("compositor supports background blur: %s",
wayl->have_background_blur ? "yes" : "no");
}
static const struct ext_background_effect_manager_v1_listener background_manager_listener = {
.capabilities = &ext_background_capabilities,
};
#endif /* HAVE_EXT_BACKGROUND_EFFECT */
static bool
fdm_repeat(struct fdm *fdm, int fd, int events, void *data)
{
@ -1558,6 +1579,20 @@ handle_global(void *data, struct wl_registry *registry,
wayl->registry, name, &xdg_toplevel_tag_manager_v1_interface, required);
}
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
else if (streq(interface, ext_background_effect_manager_v1_interface.name)) {
const uint32_t required = 1;
if (!verify_iface_version(interface, version, required))
return;
wayl->background_effect_manager = wl_registry_bind(
wayl->registry, name,
&ext_background_effect_manager_v1_interface, required);
ext_background_effect_manager_v1_add_listener(
wayl->background_effect_manager, &background_manager_listener, wayl);
}
#endif
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
else if (streq(interface, zwp_text_input_manager_v3_interface.name)) {
@ -1572,6 +1607,7 @@ handle_global(void *data, struct wl_registry *registry,
seat_add_text_input(&it->item);
}
#endif
}
static void
@ -1885,6 +1921,10 @@ wayl_destroy(struct wayland *wayl)
if (wayl->toplevel_tag_manager != NULL)
xdg_toplevel_tag_manager_v1_destroy(wayl->toplevel_tag_manager);
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
if (wayl->background_effect_manager != NULL)
ext_background_effect_manager_v1_destroy(wayl->background_effect_manager);
#endif
if (wayl->color_management.img_description != NULL)
wp_image_description_v1_destroy(wayl->color_management.img_description);
@ -1989,8 +2029,6 @@ wayl_win_init(struct terminal *term, const char *token)
goto out;
}
wayl_win_alpha_changed(win);
wl_surface_add_listener(win->surface.surf, &surface_listener, win);
if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) {
@ -2003,6 +2041,16 @@ wayl_win_init(struct terminal *term, const char *token)
win->fractional_scale, &fractional_scale_listener, win);
}
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
if (wayl->background_effect_manager != NULL) {
win->surface.background_effect =
ext_background_effect_manager_v1_get_background_effect(
wayl->background_effect_manager, win->surface.surf);
}
#endif
wayl_win_alpha_changed(win);
win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface.surf);
xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, win);
@ -2207,7 +2255,12 @@ wayl_win_destroy(struct wl_window *win)
free(it->item);
tll_remove(win->xdg_tokens, it);
}
}
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
if (win->surface.background_effect != NULL)
ext_background_effect_surface_v1_destroy(win->surface.background_effect);
#endif
if (win->surface.color_management != NULL)
wp_color_management_surface_v1_destroy(win->surface.color_management);
@ -2447,16 +2500,16 @@ void
wayl_win_alpha_changed(struct wl_window *win)
{
struct terminal *term = win->term;
struct wayland *wayl = term->wl;
/*
* When fullscreened, transparency is disabled (see render.c).
* Update the opaque region to match.
*/
bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen;
const bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen;
if (is_opaque) {
struct wl_region *region = wl_compositor_create_region(
term->wl->compositor);
struct wl_region *region = wl_compositor_create_region(wayl->compositor);
if (region != NULL) {
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
@ -2465,6 +2518,38 @@ wayl_win_alpha_changed(struct wl_window *win)
}
} else
wl_surface_set_opaque_region(win->surface.surf, NULL);
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
if (term_theme_get(term)->blur) {
if (wayl->have_background_blur) {
xassert(win->surface.background_effect != NULL);
if (is_opaque) {
/* No transparency, disable blur */
LOG_DBG("disabling background blur");
ext_background_effect_surface_v1_set_blur_region(
win->surface.background_effect, NULL);
} else {
/* We have transparency, enable blur if user has enabled it */
struct wl_region *region = wl_compositor_create_region(wayl->compositor);
if (region != NULL) {
LOG_DBG("enabling background blur");
wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
ext_background_effect_surface_v1_set_blur_region(
win->surface.background_effect, region);
wl_region_destroy(region);
}
}
} else {
static bool have_warned = false;
if (!have_warned) {
LOG_WARN("background blur requested, but compositor does not support it");
have_warned = true;
}
}
}
#endif /* HAVE_EXT_BACKGROUND_EFFECT */
}
static void

View file

@ -26,6 +26,9 @@
#if defined(HAVE_XDG_TOPLEVEL_TAG)
#include <xdg-toplevel-tag-v1.h>
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
#include <ext-background-effect-v1.h>
#endif
#include <fcft/fcft.h>
#include <tllist.h>
@ -62,6 +65,10 @@ struct wayl_surface {
struct wl_surface *surf;
struct wp_viewport *viewport;
struct wp_color_management_surface_v1 *color_management;
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
struct ext_background_effect_surface_v1 *background_effect;
#endif
};
struct wayl_sub_surface {
@ -488,6 +495,10 @@ struct wayland {
#if defined(HAVE_XDG_TOPLEVEL_TAG)
struct xdg_toplevel_tag_manager_v1 *toplevel_tag_manager;
#endif
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
struct ext_background_effect_manager_v1 *background_effect_manager;
bool have_background_blur;
#endif
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
struct zwp_text_input_manager_v3 *text_input_manager;