33#include < gtk-layer-shell.h>
44#include < spdlog/spdlog.h>
55
6+ #include < algorithm>
67#include < ostream>
78#include < type_traits>
89
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
2026namespace waybar {
2127static 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
361377void 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+
497601void 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
0 commit comments