git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commit1b8e9a53c7df075f6b155c901866e1f0f752ae21
parent9e83ff6140
authorLucas Galante <[email protected]>
date2026-05-28 22:03
Add backdrop blur for transparent windows via SceneFX

 build.rs                  | 19 ++++++++++++++++++-
 src/layer_shell.rs        |  5 +++++
 src/scene.rs              |  9 ++++++---
 src/window.rs             |  7 ++++++-
 src/wlroots_log_wrapper.c | 13 ++++++++++++-
 src/xdg_toplevel.rs       |  2 ++
 wrapper.h                 |  4 +++-
 7 files changed, 52 insertions(+), 7 deletions(-)

diff --git a/build.rs b/build.rs
index ec38264..077c025 100644
--- a/build.rs
+++ b/build.rs
@@ -11,6 +11,11 @@ fn main() {
         .probe("wlroots-0.20")
         .expect("wlroots-0.20 is required");
 
+    let scenefx = pkg_config::Config::new()
+        .atleast_version("0.4.0")
+        .probe("scenefx-0.4")
+        .expect("scenefx-0.4 is required");
+
     let wl_server = pkg_config::probe_library("wayland-server")
         .expect("wayland-server is required");
 
@@ -115,7 +120,13 @@ fn main() {
         build.file(c_file);
     }
 
-    // Add include paths for wlroots and wayland-server
+    // Add include paths for scenefx, wlroots, and wayland-server
+    for path in &scenefx.include_paths {
+        if path.to_string_lossy().contains("wlroots-0.19") {
+            continue;
+        }
+        build.include(path);
+    }
     for path in &wlroots.include_paths {
         build.include(path);
     }
@@ -134,6 +145,12 @@ fn main() {
 
     // Pass include paths to bindgen clang argument parser
     let mut include_paths = vec![out_dir.clone()];
+    for path in scenefx.include_paths.clone() {
+        if path.to_string_lossy().contains("wlroots-0.19") {
+            continue;
+        }
+        include_paths.push(path);
+    }
     include_paths.extend(wlroots.include_paths.clone());
     include_paths.extend(wl_server.include_paths.clone());
     include_paths.extend(xkb.include_paths.clone());
diff --git a/src/layer_shell.rs b/src/layer_shell.rs
index aa8e837..5264cf6 100644
--- a/src/layer_shell.rs
+++ b/src/layer_shell.rs
@@ -462,6 +462,11 @@ unsafe extern "C" fn handle_layer_surface_commit(listener: *mut ffi::wl_listener
     let layer_surface = crate::container_of!(listener, LayerSurface, commit);
     let wlr_layer_surface = (*layer_surface).wlr_layer_surface;
 
+    ffi::river_scene_node_enable_blur(
+        (*(*layer_surface).scene_layer_surface).tree as *mut ffi::wlr_scene_node,
+        true,
+    );
+
     assert!(!(*wlr_layer_surface).output.is_null());
 
     let server = (*layer_surface).server;
diff --git a/src/scene.rs b/src/scene.rs
index c7239cb..3f98d19 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -61,9 +61,12 @@ impl Scene {
         if !linux_dmabuf.is_null() {
             ffi::wlr_scene_set_linux_dmabuf_v1(wlr_scene, linux_dmabuf);
         }
-        if !color_manager.is_null() {
-            ffi::wlr_scene_set_color_manager_v1(wlr_scene, color_manager);
-        }
+        // SceneFX 0.4 does not support set_color_manager_v1
+        // if !color_manager.is_null() {
+        //     ffi::wlr_scene_set_color_manager_v1(wlr_scene, color_manager);
+        // }
+
+        ffi::wlr_scene_set_blur_data(wlr_scene, 3, 5, 0.0, 1.0, 1.0, 1.0);
 
         let interactive_tree = ffi::wlr_scene_tree_create(&mut (*wlr_scene).tree);
         let drag_icons = ffi::wlr_scene_tree_create(&mut (*wlr_scene).tree);
diff --git a/src/window.rs b/src/window.rs
index 41566e2..b73340f 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -236,7 +236,8 @@ impl Window {
             ffi::wlr_scene_node_destroy(popup_tree as *mut ffi::wlr_scene_node);
             return Err("Failed to create capture_scene");
         }
-        (*capture_scene).restack_xwayland_surfaces = false;
+        // SceneFX 0.4 does not support restack_xwayland_surfaces
+        // (*capture_scene).restack_xwayland_surfaces = false;
 
         let black_color = [0.0f32, 0.0f32, 0.0f32, 1.0f32];
         let fullscreen_background = ffi::wlr_scene_rect_create(tree, 0, 0, black_color.as_ptr());
@@ -1121,6 +1122,10 @@ impl Window {
         ffi::wlr_scene_node_set_enabled(self.tree as *mut ffi::wlr_scene_node, enabled);
         ffi::wlr_scene_node_set_enabled(self.popup_tree as *mut ffi::wlr_scene_node, enabled);
 
+        if enabled {
+            ffi::river_scene_node_enable_blur(self.surfaces.tree as *mut ffi::wlr_scene_node, true);
+        }
+
         self.box_geom.width = self.rendering_sent.width as i32;
         self.box_geom.height = self.rendering_sent.height as i32;
 
diff --git a/src/wlroots_log_wrapper.c b/src/wlroots_log_wrapper.c
index 0e83f5a..0e9c1fd 100644
--- a/src/wlroots_log_wrapper.c
+++ b/src/wlroots_log_wrapper.c
@@ -46,7 +46,7 @@ void river_init_wlroots_log(enum wlr_log_importance importance) {
 }
 
 #include <time.h>
-#include <wlr/types/wlr_scene.h>
+#include <scenefx/types/wlr_scene.h>
 #include <wlr/types/wlr_output.h>
 #include <wlr/types/wlr_compositor.h>
 #include <wlr/types/wlr_input_device.h>
@@ -646,5 +646,16 @@ void river_wlr_keyboard_init(struct wlr_keyboard *keyboard,
 	wlr_keyboard_init(keyboard, &impl, name);
 }
 
+static void enable_blur_iterator(struct wlr_scene_buffer *buffer, int sx, int sy, void *user_data) {
+	bool enabled = *(bool *)user_data;
+	wlr_scene_buffer_set_backdrop_blur(buffer, enabled);
+	wlr_scene_buffer_set_backdrop_blur_optimized(buffer, enabled);
+	wlr_scene_buffer_set_backdrop_blur_ignore_transparent(buffer, enabled);
+}
+
+void river_scene_node_enable_blur(struct wlr_scene_node *node, bool enabled) {
+	wlr_scene_node_for_each_buffer(node, enable_blur_iterator, &enabled);
+}
+
 
 
diff --git a/src/xdg_toplevel.rs b/src/xdg_toplevel.rs
index 6cf1231..de98fa6 100644
--- a/src/xdg_toplevel.rs
+++ b/src/xdg_toplevel.rs
@@ -403,6 +403,8 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
     let window = (*toplevel).window;
     let base = ffi::river_wlr_xdg_toplevel_get_base((*toplevel).wlr_toplevel);
 
+    ffi::river_scene_node_enable_blur((*window).surfaces.tree as *mut ffi::wlr_scene_node, true);
+
     let capture_node = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node;
     let mut geom = std::mem::zeroed();
     ffi::river_wlr_xdg_surface_get_geometry(base, &mut geom);
diff --git a/wrapper.h b/wrapper.h
index 412b77b..41cf574 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -72,7 +72,7 @@
 #include <wlr/types/wlr_color_representation_v1.h>
 #include <wlr/types/wlr_linux_dmabuf_v1.h>
 #include <wlr/types/wlr_linux_drm_syncobj_v1.h>
-#include <wlr/types/wlr_scene.h>
+#include <scenefx/types/wlr_scene.h>
 
 #include <wlr/util/addon.h>
 #include <wlr/xwayland.h>
@@ -255,4 +255,6 @@ struct wlr_seat_client *river_wlr_drag_get_seat_client(struct wlr_drag *drag);
 
 void river_wlr_keyboard_init(struct wlr_keyboard *keyboard, void (*led_update)(struct wlr_keyboard *keyboard, uint32_t leds), const char *name);
 
+void river_scene_node_enable_blur(struct wlr_scene_node *node, bool enabled);
+
 #endif // WRAPPER_H