Browse Source

feat(wayland): add --layer CLI flag to choose wlr-layer-shell layer (#585)

* feat(wayland): add --layer CLI flag to choose wlr-layer-shell layer

By default linux-wallpaperengine anchors its wallpaper surface to the
wlr-layer-shell `bottom` layer. Some compositors treat `background` and
`bottom` differently. The most prominent example is niri, where the
`place-within-backdrop` layer-rule (which prevents the wallpaper from
being duplicated on every workspace in the overview) only applies to
`background` surfaces. With the current hard-coded `bottom`, niri users
see one copy of the wallpaper inside each workspace card instead of one
shared backdrop.

This commit introduces a new `--layer {background,bottom,top,overlay}`
flag. Default remains `bottom` so existing setups are unaffected.

Usage on niri:

    linux-wallpaperengine --screen-root DP-1 --bg 12345 --layer background

paired with:

    layer-rule {
        match namespace="^linux-wallpaperengine$"
        place-within-backdrop true
    }

* perf(wayland): mark wallpaper surface as fully opaque

Wallpapers are the bottommost visible content, so it's always safe to
tell the compositor the surface is fully opaque. This lets the
compositor skip rendering anything below (nothing relevant lives there)
and replace alpha-blending with a plain copy, which is meaningfully
cheaper for large 4K shader-heavy Wallpaper Engine scenes.

No visible behavior change: the surface was already drawn opaque, the
compositor just had to assume otherwise because RGBA8888 EGL surfaces
may contain transparency.

---------

Co-authored-by: Semyon Zhukov <zhukov.s@goulash.tech>
Co-authored-by: Alexis Maiquez <wpengine@alma.mu>
Semyon Zhukov 4 tháng trước cách đây
mục cha
commit
9e200e47e3

+ 23 - 0
src/WallpaperEngine/Application/ApplicationContext.cpp

@@ -396,6 +396,29 @@ void ApplicationContext::loadSettingsFromArgv () {
 	    }
 	});
 
+    backgroundGroup.add_argument ("--layer")
+	.help (
+	    "Wayland-only: which wlr-layer-shell layer to anchor the wallpaper to "
+	    "(background, bottom, top, overlay). Default: bottom. "
+	    "Use 'background' on niri to pair with the `place-within-backdrop` layer-rule, "
+	    "otherwise the wallpaper will be cloned to every workspace in the overview."
+	)
+	.choices ("background", "bottom", "top", "overlay")
+	.default_value (std::string ("bottom"))
+	.action ([this] (const std::string& value) -> void {
+	    if (value == "background") {
+		this->settings.render.wayland.layer = WAYLAND_LAYER_BACKGROUND;
+	    } else if (value == "bottom") {
+		this->settings.render.wayland.layer = WAYLAND_LAYER_BOTTOM;
+	    } else if (value == "top") {
+		this->settings.render.wayland.layer = WAYLAND_LAYER_TOP;
+	    } else if (value == "overlay") {
+		this->settings.render.wayland.layer = WAYLAND_LAYER_OVERLAY;
+	    } else {
+		sLog.exception ("Invalid wlr-layer-shell layer: ", value);
+	    }
+	});
+
     auto& performanceGroup = program.add_group ("Performance options");
 
     performanceGroup.add_argument ("-f", "--fps")

+ 20 - 0
src/WallpaperEngine/Application/ApplicationContext.h

@@ -40,6 +40,18 @@ public:
 	EXPLICIT_WINDOW = 2,
     };
 
+    /**
+     * Wayland-only: which wlr-layer-shell layer to anchor the wallpaper surface to.
+     * Different compositors treat layers differently; e.g. niri's
+     * `place-within-backdrop` layer-rule only applies to BACKGROUND surfaces.
+     */
+    enum WAYLAND_LAYER {
+	WAYLAND_LAYER_BACKGROUND = 0,
+	WAYLAND_LAYER_BOTTOM = 1,
+	WAYLAND_LAYER_TOP = 2,
+	WAYLAND_LAYER_OVERLAY = 3,
+    };
+
     struct PlaylistSettings {
 	uint32_t delayMinutes = 60;
 	std::string mode = "timer";
@@ -119,6 +131,11 @@ public:
 		TextureFlags clamp;
 		WallpaperEngine::Render::WallpaperState::TextureUVsScaling scalingMode;
 	    } window;
+
+	    struct {
+		/** Which wlr-layer-shell layer to use for desktop backgrounds */
+		WAYLAND_LAYER layer;
+	    } wayland;
 	} render;
 
 	/**
@@ -188,6 +205,9 @@ public:
                 .clamp = TextureFlags_ClampUVs,
                 .scalingMode = WallpaperEngine::Render::WallpaperState::TextureUVsScaling::DefaultUVs,
             },
+            .wayland = {
+                .layer = WAYLAND_LAYER_BOTTOM,
+            },
         },
         .audio = {
             .enabled = true,

+ 27 - 2
src/WallpaperEngine/Render/Drivers/Output/WaylandOutputViewport.cpp

@@ -116,9 +116,26 @@ WaylandOutputViewport::WaylandOutputViewport (
 
 void WaylandOutputViewport::setupLS () {
     surface = wl_compositor_create_surface (m_driver->getWaylandContext ()->compositor);
+
+    zwlr_layer_shell_v1_layer wlrLayer;
+    switch (m_driver->getApp ().getContext ().settings.render.wayland.layer) {
+	case WallpaperEngine::Application::ApplicationContext::WAYLAND_LAYER_BACKGROUND:
+	    wlrLayer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
+	    break;
+	case WallpaperEngine::Application::ApplicationContext::WAYLAND_LAYER_TOP:
+	    wlrLayer = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
+	    break;
+	case WallpaperEngine::Application::ApplicationContext::WAYLAND_LAYER_OVERLAY:
+	    wlrLayer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
+	    break;
+	case WallpaperEngine::Application::ApplicationContext::WAYLAND_LAYER_BOTTOM:
+	default:
+	    wlrLayer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
+	    break;
+    }
+
     layerSurface = zwlr_layer_shell_v1_get_layer_surface (
-	// Background layer lets desktop shells keep icons and panels above the wallpaper surface.
-	m_driver->getWaylandContext ()->layerShell, surface, output, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
+	m_driver->getWaylandContext ()->layerShell, surface, output, wlrLayer,
 	"linux-wallpaperengine"
     );
 
@@ -131,6 +148,14 @@ void WaylandOutputViewport::setupLS () {
 	wl_region_add (region, 0, 0, INT32_MAX, INT32_MAX);
     }
 
+    // Mark the surface as fully opaque so the compositor can skip rendering
+    // anything below it and avoid alpha-blending. Wallpapers are by definition
+    // the bottommost visible content, so this is always a win.
+    wl_region* opaqueRegion = wl_compositor_create_region (m_driver->getWaylandContext ()->compositor);
+    wl_region_add (opaqueRegion, 0, 0, INT32_MAX, INT32_MAX);
+    wl_surface_set_opaque_region (surface, opaqueRegion);
+    wl_region_destroy (opaqueRegion);
+
     zwlr_layer_surface_v1_set_size (layerSurface, 0, 0);
     zwlr_layer_surface_v1_set_anchor (
 	layerSurface,