Skip to content

Commit 6e163a1

Browse files
authored
Merge pull request #5348 from AntonyLittle/feat/bar-blur
Add bar blur option
2 parents 791da38 + 16f0c60 commit 6e163a1

7 files changed

Lines changed: 168 additions & 2 deletions

File tree

‎include/bar.hpp‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#include "util/kill_signal.hpp"
1818
#include "xdg-output-unstable-v1-client-protocol.h"
1919

20+
#ifdef HAVE_BAR_BLUR
21+
struct ext_background_effect_surface_v1;
22+
#endif
23+
2024
namespace waybar {
2125

2226
class Factory;
@@ -79,6 +83,12 @@ class Bar : public sigc::trackable {
7983

8084
void toggleSuspend(bool suspend);
8185

86+
#ifdef HAVE_BAR_BLUR
87+
// Re-applies the blur region; called after a CSS reload since the bar's
88+
// border-radius may have changed without a resize event.
89+
void updateBlurRegion();
90+
#endif
91+
8292
struct waybar_output* output;
8393
Json::Value config;
8494
struct wl_surface* surface;
@@ -107,6 +117,9 @@ class Bar : public sigc::trackable {
107117
void onConfigure(GdkEventConfigure* ev);
108118
void configureGlobalOffset(int width, int height);
109119
void onOutputGeometryChanged();
120+
#ifdef HAVE_BAR_BLUR
121+
void setupBackgroundBlur();
122+
#endif
110123

111124
/* Copy initial set of modes to allow customization */
112125
bar_mode_map configured_modes = PRESET_MODES;
@@ -115,6 +128,10 @@ class Bar : public sigc::trackable {
115128
struct bar_margins margins_;
116129
uint32_t width_, height_;
117130
bool passthrough_;
131+
#ifdef HAVE_BAR_BLUR
132+
bool blur_enabled_ = false;
133+
struct ext_background_effect_surface_v1* background_effect_surface_ = nullptr;
134+
#endif
118135

119136
Gtk::Box left_;
120137
Gtk::Box center_;

‎include/client.hpp‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
struct zwp_idle_inhibitor_v1;
1414
struct zwp_idle_inhibit_manager_v1;
1515
struct ext_idle_notifier_v1;
16+
#ifdef HAVE_BAR_BLUR
17+
struct ext_background_effect_manager_v1;
18+
#endif
1619

1720
namespace waybar {
1821

@@ -29,6 +32,9 @@ class Client {
2932
struct zxdg_output_manager_v1* xdg_output_manager = nullptr;
3033
struct zwp_idle_inhibit_manager_v1* idle_inhibit_manager = nullptr;
3134
struct ext_idle_notifier_v1* idle_notifier = nullptr;
35+
#ifdef HAVE_BAR_BLUR
36+
struct ext_background_effect_manager_v1* background_effect_manager = nullptr;
37+
#endif
3238
std::vector<std::unique_ptr<Bar>> bars;
3339
Config config;
3440
std::string bar_id;

‎man/waybar.5.scd.in‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ The visual display elements for waybar use a CSS stylesheet. Waybar supports *st
169169
Possible values: *show*, *hide*, *toggle*, *reload*, *noop*. ++
170170
Default value: *reload*.
171171

172+
*blur* ++
173+
typeof: bool ++
174+
default: false ++
175+
Requests blur-behind for the bar's own background, via the standard
176+
*ext-background-effect-v1* Wayland protocol. Has no effect if the
177+
compositor doesn't support it.
178+
172179
# MODULE FORMAT
173180

174181
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).

‎meson.build‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ if get_option('mango')
392392
)
393393
endif
394394

395+
if wayland_protos.version().version_compare('>=1.41')
396+
# Bar-level blur-behind support (ext-background-effect-v1)
397+
add_project_arguments('-DHAVE_BAR_BLUR', language: 'cpp')
398+
endif
395399
if true
396400
add_project_arguments('-DHAVE_WAYFIRE', language: 'cpp')
397401
src_files += files(

‎protocol/meson.build‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ if wayland_protos.version().version_compare('>=1.39')
3838
]
3939
endif
4040

41+
if wayland_protos.version().version_compare('>=1.41')
42+
client_protocols += [
43+
[wl_protocol_dir, 'staging/ext-background-effect/ext-background-effect-v1.xml'],
44+
]
45+
endif
46+
4147
client_protos_src = []
4248
client_protos_headers = []
4349

‎src/bar.cpp‎

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <gtk-layer-shell.h>
44
#include <spdlog/spdlog.h>
55

6+
#include <algorithm>
67
#include <ostream>
78
#include <type_traits>
89

@@ -16,6 +17,11 @@
1617
#ifdef HAVE_SWAY
1718
#include "modules/sway/bar.hpp"
1819
#endif
20+
#ifdef HAVE_BAR_BLUR
21+
#include <gtk/gtk.h>
22+
23+
#include "ext-background-effect-v1-client-protocol.h"
24+
#endif
1925

2026
namespace waybar {
2127
static constexpr const char* MIN_HEIGHT_MSG =
@@ -181,6 +187,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
181187
height_ = config["height"].isUInt() ? config["height"].asUInt() : 0;
182188
width_ = config["width"].isUInt() ? config["width"].asUInt() : 0;
183189

190+
#ifdef HAVE_BAR_BLUR
191+
blur_enabled_ = config["blur"].isBool() && config["blur"].asBool();
192+
#endif
193+
184194
if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
185195
config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
186196
margins_ = {
@@ -301,7 +311,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
301311
auto strSigusr1 = configSigusr1.asString();
302312
try {
303313
onSigusr1 =
304-
util::parseStringToEnum<util::KillSignalAction>(strSigusr1, util::userKillSignalActions);
314+
util::parseStringToEnum<util::KillSignalAction>(strSigusr1, util::userKillSignalActions);
305315
} catch (const std::invalid_argument& e) {
306316
onSigusr1 = util::SIGNALACTION_DEFAULT_SIGUSR1;
307317
spdlog::warn(
@@ -313,7 +323,7 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
313323
auto strSigusr2 = configSigusr2.asString();
314324
try {
315325
onSigusr2 =
316-
util::parseStringToEnum<util::KillSignalAction>(strSigusr2, util::userKillSignalActions);
326+
util::parseStringToEnum<util::KillSignalAction>(strSigusr2, util::userKillSignalActions);
317327
} catch (const std::invalid_argument& e) {
318328
onSigusr2 = util::SIGNALACTION_DEFAULT_SIGUSR2;
319329
spdlog::warn(
@@ -356,6 +366,12 @@ waybar::Bar::~Bar() {
356366
* modules_all_ -- already freed by this point. Disconnect first (#5182). */
357367
unmap_conn_.disconnect();
358368
map_conn_.disconnect();
369+
#ifdef HAVE_BAR_BLUR
370+
if (background_effect_surface_) {
371+
ext_background_effect_surface_v1_destroy(background_effect_surface_);
372+
background_effect_surface_ = nullptr;
373+
}
374+
#endif
359375
}
360376

361377
void waybar::Bar::setMode(const std::string& mode) {
@@ -492,8 +508,96 @@ void waybar::Bar::onMap(GdkEventAny* /*unused*/) {
492508
configureGlobalOffset(gdk_window_get_width(gdk_window), gdk_window_get_height(gdk_window));
493509

494510
setPassThrough(passthrough_);
511+
#ifdef HAVE_BAR_BLUR
512+
setupBackgroundBlur();
513+
#endif
514+
}
515+
516+
#ifdef HAVE_BAR_BLUR
517+
// Requests blur-behind for this bar's own surface, via the standard
518+
// ext-background-effect-v1 protocol.
519+
void waybar::Bar::setupBackgroundBlur() {
520+
if (!blur_enabled_ || background_effect_surface_ || !surface) {
521+
return;
522+
}
523+
auto* manager = Client::inst()->background_effect_manager;
524+
if (!manager) {
525+
spdlog::warn("[bar]: Compositor does not support ext-background-effect-v1; blur disabled");
526+
return;
527+
}
528+
background_effect_surface_ =
529+
ext_background_effect_manager_v1_get_background_effect(manager, surface);
530+
updateBlurRegion();
495531
}
496532

533+
// Re-applies the blur region to match the bar's current pixel size, clipped to the bar's
534+
// actual rendered CSS background shape (rounded corners, including asymmetric top/bottom
535+
// radii) so blur doesn't leak past it as a rectangular "ghost".
536+
void waybar::Bar::updateBlurRegion() {
537+
if (!blur_enabled_ || !background_effect_surface_ || !surface) {
538+
return;
539+
}
540+
// Query the live GDK window size rather than width_/height_: those track the
541+
// user-requested config size and may still be unset (0) the first time we're
542+
// called, from onMap, before any configure event has landed.
543+
auto gdk_window_ref = window.get_window();
544+
if (!gdk_window_ref) {
545+
return;
546+
}
547+
auto* gdk_window = gdk_window_ref->gobj();
548+
const int32_t w = gdk_window_get_width(gdk_window);
549+
const int32_t h = gdk_window_get_height(gdk_window);
550+
if (w <= 0 || h <= 0) {
551+
return;
552+
}
553+
auto* compositor = gdk_wayland_display_get_wl_compositor(window.get_display()->gobj());
554+
if (!compositor) {
555+
return;
556+
}
557+
558+
auto* style_context = window.get_style_context()->gobj();
559+
gint radius = 0;
560+
gtk_style_context_get(style_context, GTK_STATE_FLAG_NORMAL, GTK_STYLE_PROPERTY_BORDER_RADIUS,
561+
&radius, nullptr);
562+
radius = std::clamp<gint>(radius, 0, std::min(w, h) / 2);
563+
564+
wl_region* region = wl_compositor_create_region(compositor);
565+
if (radius <= 0) {
566+
wl_region_add(region, 0, 0, w, h);
567+
} else {
568+
// Middle band, full width, between the two corner strips.
569+
wl_region_add(region, 0, radius, w, h - 2 * radius);
570+
// Rasterize the actual CSS background to find each corner row's opaque span, so
571+
// top and bottom corners are clipped correctly even when their radii differ.
572+
cairo_surface_t* mask = cairo_image_surface_create(CAIRO_FORMAT_A8, w, h);
573+
cairo_t* cr = cairo_create(mask);
574+
gtk_render_background(style_context, cr, 0, 0, w, h);
575+
cairo_surface_flush(mask);
576+
const unsigned char* data = cairo_image_surface_get_data(mask);
577+
const int stride = cairo_image_surface_get_stride(mask);
578+
579+
auto add_opaque_span = [&](int32_t y) {
580+
const unsigned char* row = data + static_cast<size_t>(y) * stride;
581+
int32_t left = 0;
582+
while (left < w && row[left] == 0) ++left;
583+
int32_t right = w;
584+
while (right > left && row[right - 1] == 0) --right;
585+
if (right > left) {
586+
wl_region_add(region, left, y, right - left, 1);
587+
}
588+
};
589+
for (int32_t y = 0; y < radius; ++y) add_opaque_span(y);
590+
for (int32_t y = h - radius; y < h; ++y) add_opaque_span(y);
591+
592+
cairo_destroy(cr);
593+
cairo_surface_destroy(mask);
594+
}
595+
ext_background_effect_surface_v1_set_blur_region(background_effect_surface_, region);
596+
wl_region_destroy(region);
597+
wl_surface_commit(surface);
598+
}
599+
#endif
600+
497601
void waybar::Bar::setVisible(bool value) {
498602
visible = value;
499603
if (auto mode = config.get("mode", {}); mode.isString()) {
@@ -700,6 +804,9 @@ void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
700804

701805
configureGlobalOffset(ev->width, ev->height);
702806
spdlog::info(BAR_SIZE_MSG, ev->width, ev->height, output->name);
807+
#ifdef HAVE_BAR_BLUR
808+
updateBlurRegion();
809+
#endif
703810

704811
/*
705812
* gtk-layer-shell waits for the compositor's initial configure while realizing the window. On a

‎src/client.cpp‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include "util/clara.hpp"
1414
#include "util/format.hpp"
1515
#include "util/hex_checker.hpp"
16+
#ifdef HAVE_BAR_BLUR
17+
#include "ext-background-effect-v1-client-protocol.h"
18+
#endif
1619

1720
waybar::Client* waybar::Client::inst() {
1821
static auto* c = new Client();
@@ -47,6 +50,16 @@ void waybar::Client::handleGlobal(void* data, struct wl_registry* registry, uint
4750
client->idle_notifier = static_cast<struct ext_idle_notifier_v1 *>(
4851
wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, bind_version));
4952
spdlog::debug("Bound ext-idle-notifier-v1 at version {}", bind_version);
53+
#ifdef HAVE_BAR_BLUR
54+
} else if (strcmp(interface, ext_background_effect_manager_v1_interface.name) == 0) {
55+
if (client->background_effect_manager != nullptr) {
56+
ext_background_effect_manager_v1_destroy(client->background_effect_manager);
57+
client->background_effect_manager = nullptr;
58+
}
59+
60+
client->background_effect_manager = static_cast<struct ext_background_effect_manager_v1*>(
61+
wl_registry_bind(registry, name, &ext_background_effect_manager_v1_interface, 1));
62+
#endif
5063
}
5164
}
5265

@@ -260,6 +273,12 @@ auto waybar::Client::setupCss(const std::string& css_file) -> void {
260273
}
261274
Gtk::StyleContext::add_provider_for_screen(screen, css_provider_,
262275
GTK_STYLE_PROVIDER_PRIORITY_USER);
276+
#ifdef HAVE_BAR_BLUR
277+
// The bar's border-radius may have changed without a resize event.
278+
for (auto& bar : bars) {
279+
bar->updateBlurRegion();
280+
}
281+
#endif
263282
}
264283

265284
void waybar::Client::bindInterfaces() {

0 commit comments

Comments
 (0)