step 2: extract wl_background.c from wl_backend.c (desktop background rendering, 304 lines)

This commit is contained in:
lkn 2026-06-06 09:40:20 +02:00
parent 8cca118517
commit 9178f8a903
3 changed files with 327 additions and 304 deletions

View File

@ -165,6 +165,7 @@ if USE_WAYLAND_BACKEND
wmaker_SOURCES += \
backend/wayland/wl_backend.h \
backend/wayland/wl_backend.c \
backend/wayland/wl_background.c \
backend/wayland/wl_context.c \
backend/wayland/wl_event.c \
backend/wayland/wl_stacking.c \

View File

@ -1799,310 +1799,6 @@ wl_screen_load_tech_font(WScreen *scr)
RImage *wl_screen_capture_window(WScreen *scr, WWindow *wwin) { (void)scr; (void)wwin; return NULL; }
void wl_refresh_desktop(WScreen *scr) { (void)scr; }
/* --- Desktop background rendering --- */
/* Simple wlr_buffer implementation backed by a pixman_image_t */
struct wl_bg_buffer {
struct wlr_buffer base;
pixman_image_t *image;
};
static void bg_buffer_destroy(struct wlr_buffer *wlr_buf)
{
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
if (buf->image)
pixman_image_unref(buf->image);
free(buf);
}
static bool bg_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buf,
uint32_t flags, void **data, uint32_t *format, size_t *stride)
{
(void)flags;
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
*data = pixman_image_get_data(buf->image);
*format = DRM_FORMAT_ARGB8888;
*stride = pixman_image_get_stride(buf->image);
return true;
}
static void bg_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buf)
{
(void)wlr_buf;
}
static const struct wlr_buffer_impl bg_buffer_impl = {
.destroy = bg_buffer_destroy,
.begin_data_ptr_access = bg_buffer_begin_data_ptr_access,
.end_data_ptr_access = bg_buffer_end_data_ptr_access,
};
static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out)
{
WMColorRGB wmc;
if (!name || !wGetColor(scr, name, &wmc))
return 0;
out->red = wmc.red >> 8;
out->green = wmc.green >> 8;
out->blue = wmc.blue >> 8;
out->alpha = 255;
return 1;
}
void wl_set_desktop_background(WScreen *scr, const char *texture_desc)
{
if (!scr || !texture_desc)
return;
if (!wl_state.output) {
/* Output not ready yet — cache the background for later */
wfree(wl_state.pending_background);
wl_state.pending_background = wstrdup(texture_desc);
return;
}
int width = wl_state.output->width;
int height = wl_state.output->height;
/* Parse the texture proplist */
WMPropList *texpl = WMCreatePropListFromDescription(texture_desc);
if (!texpl) {
return;
}
/* Render the texture to an RImage using wrlib */
RContext *rc = scr->rcontext;
RImage *bg = NULL;
if (WMIsPLArray(texpl) && WMGetPropListItemCount(texpl) > 0) {
WMPropList *type_pl = WMGetFromPLArray(texpl, 0);
const char *type = type_pl ? WMGetFromPLString(type_pl) : "";
if (strcasecmp(type, "solid") == 0 && WMGetPropListItemCount(texpl) >= 2) {
const char *colorstr = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
RColor color = {0x50, 0x50, 0x75, 0xFF};
if (colorstr)
parse_color_to_rcolor(scr, colorstr, &color);
/* For solid colors, just update the scene rect directly */
if (wl_state.bg_rect) {
float c[4] = { color.red/255.0f, color.green/255.0f, color.blue/255.0f, 1.0f };
wlr_scene_rect_set_color(wl_state.bg_rect, c);
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, true);
if (wl_state.bg_image)
wlr_scene_node_set_enabled(&wl_state.bg_image->node, false);
}
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
WMReleasePropList(texpl);
return;
} else if ((strcasecmp(type, "vgradient") == 0 ||
strcasecmp(type, "hgradient") == 0 ||
strcasecmp(type, "dgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
const char *c1str = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
const char *c2str = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
RColor color1 = {0, 0, 0, 255}, color2 = {0, 0, 0, 255};
if (c1str) parse_color_to_rcolor(scr, c1str, &color1);
if (c2str) parse_color_to_rcolor(scr, c2str, &color2);
int style = (type[0] == 'v' || type[0] == 'V') ? RVerticalGradient :
(type[0] == 'h' || type[0] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderGradient(width, height, &color1, &color2, style);
} else if ((strcasecmp(type, "mvgradient") == 0 ||
strcasecmp(type, "mhgradient") == 0 ||
strcasecmp(type, "mdgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
int ncolors = WMGetPropListItemCount(texpl) - 1;
RColor **colors = wmalloc((ncolors + 1) * sizeof(RColor *));
RColor *cbuf = wmalloc(ncolors * sizeof(RColor));
int ci;
for (ci = 0; ci < ncolors; ci++) {
const char *cs = WMGetFromPLString(WMGetFromPLArray(texpl, ci + 1));
cbuf[ci] = (RColor){0, 0, 0, 255};
if (cs) parse_color_to_rcolor(scr, cs, &cbuf[ci]);
colors[ci] = &cbuf[ci];
}
colors[ncolors] = NULL;
int style = (type[1] == 'v' || type[1] == 'V') ? RVerticalGradient :
(type[1] == 'h' || type[1] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderMultiGradient(width, height, colors, style);
wfree(cbuf);
wfree(colors);
} else if ((strcasecmp(type, "pixmap") == 0 ||
strcasecmp(type, "spixmap") == 0 ||
strcasecmp(type, "mpixmap") == 0 ||
strcasecmp(type, "cpixmap") == 0 ||
strcasecmp(type, "fpixmap") == 0 ||
strcasecmp(type, "tpixmap") == 0) &&
WMGetPropListItemCount(texpl) >= 2) {
const char *path = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
/* Background color (3rd argument, optional) */
RColor bgcolor = {0, 0, 0, 255};
if (WMGetPropListItemCount(texpl) >= 3) {
const char *cstr = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
if (cstr) parse_color_to_rcolor(scr, cstr, &bgcolor);
}
if (path) {
/* Try the path as-is first, then search pixmap_path */
RImage *img = RLoadImage(rc, path, 0);
if (!img && path[0] != '/') {
/* Relative path — search pixmap_path */
char *search_paths[] = { wPreferences.pixmap_path, PKGDATADIR "/Backgrounds", NULL };
for (int _pi = 0; search_paths[_pi]; _pi++) {
char _full[PATH_MAX];
snprintf(_full, sizeof(_full), "%s/%s", search_paths[_pi], path);
img = RLoadImage(rc, _full, 0);
if (img) break;
}
}
if (!img) {
} else {
/* Combine with background color if image has alpha */
if (img->format == RRGBAFormat)
RCombineImageWithColor(img, &bgcolor);
char mode = (type[0] >= 'a' && type[0] <= 'z') ? type[0] - 32 : type[0];
if (mode == 'S') {
/* Scaled: stretch to exact screen size */
bg = RSmoothScaleImage(img, width, height);
} else if (mode == 'F') {
/* Filled: scale preserving aspect, fill screen (crop) */
int w, h;
if (img->width * height > img->height * width) {
w = (height * img->width) / img->height;
h = height;
} else {
w = width;
h = (width * img->height) / img->width;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int sx = (scaled->width - width) / 2;
int sy = (scaled->height - height) / 2;
RCopyArea(bg, scaled, sx, sy, width, height, 0, 0);
}
RReleaseImage(scaled);
}
} else if (mode == 'M') {
/* Maximized: scale preserving aspect, fit inside (letterbox) */
int w, h;
if (img->width * height > img->height * width) {
w = width;
h = (width * img->height) / img->width;
} else {
w = (height * img->width) / img->height;
h = height;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - scaled->width) / 2;
int dy = (height - scaled->height) / 2;
RCombineArea(bg, scaled, 0, 0, scaled->width, scaled->height, dx, dy);
}
RReleaseImage(scaled);
}
} else if (mode == 'C') {
/* Centered: place at center, no scaling */
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - img->width) / 2;
int dy = (height - img->height) / 2;
/* Handle images larger than screen */
int sx = 0, sy = 0, cw = img->width, ch = img->height;
if (dx < 0) { sx = -dx; dx = 0; cw = width; }
if (dy < 0) { sy = -dy; dy = 0; ch = height; }
RCombineArea(bg, img, sx, sy, cw, ch, dx, dy);
}
} else if (mode == 'T') {
/* Tiled: repeat the image */
bg = RCreateImage(width, height, 0);
if (bg) {
for (int ty = 0; ty < height; ty += img->height) {
for (int tx = 0; tx < width; tx += img->width) {
int cw = img->width;
int ch = img->height;
if (tx + cw > width) cw = width - tx;
if (ty + ch > height) ch = height - ty;
RCombineArea(bg, img, 0, 0, cw, ch, tx, ty);
}
}
}
} else {
/* Default (plain "pixmap"): scale to fill */
bg = RSmoothScaleImage(img, width, height);
}
RReleaseImage(img);
}
}
}
}
WMReleasePropList(texpl);
if (!bg) {
return;
}
/* Convert RImage to pixman_image and set as scene buffer */
struct wl_bg_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { RReleaseImage(bg); return; }
buf->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, width * 4);
if (!buf->image) { free(buf); RReleaseImage(bg); return; }
/* Copy RImage (RGBA or RGB) into ARGB32 pixman buffer */
uint32_t *dst = (uint32_t *)pixman_image_get_data(buf->image);
unsigned char *src = bg->data;
int npixels = width * height;
if (bg->format == RRGBAFormat) {
for (int i = 0; i < npixels; i++) {
dst[i] = ((uint32_t)src[3] << 24) | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 4;
}
} else {
for (int i = 0; i < npixels; i++) {
dst[i] = 0xFF000000 | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 3;
}
}
RReleaseImage(bg);
wlr_buffer_init(&buf->base, &bg_buffer_impl, width, height);
/* Hide the solid-color rect */
if (wl_state.bg_rect)
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, false);
/* Replace or create the background scene buffer */
if (wl_state.bg_image) {
wlr_scene_buffer_set_buffer(wl_state.bg_image, &buf->base);
wlr_scene_node_set_enabled(&wl_state.bg_image->node, true);
} else {
wl_state.bg_image = wlr_scene_buffer_create(
wl_state.level_trees[WL_LAYER_BACKGROUND], &buf->base);
}
/* Release old buffer, keep new one */
if (wl_state.bg_wlr_buf)
wlr_buffer_drop(wl_state.bg_wlr_buf);
wl_state.bg_wlr_buf = &buf->base;
/* Schedule a frame to display the new background immediately */
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
}
void
wl_monitors_query(WScreen *scr, WHeadGeometry **heads_out, int *count_out)

View File

@ -0,0 +1,326 @@
/*
* wl_background.c -- Desktop background rendering (wlr_buffer + RImage backend)
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_buffer.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <libdrm/drm_fourcc.h>
#include <pixman.h>
#include "../../screen.h"
#include "../../resources.h"
#include "wl_types.h"
/* --- Desktop background rendering --- */
/* Simple wlr_buffer implementation backed by a pixman_image_t */
struct wl_bg_buffer {
struct wlr_buffer base;
pixman_image_t *image;
};
static void bg_buffer_destroy(struct wlr_buffer *wlr_buf)
{
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
if (buf->image)
pixman_image_unref(buf->image);
free(buf);
}
static bool bg_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buf,
uint32_t flags, void **data, uint32_t *format, size_t *stride)
{
(void)flags;
struct wl_bg_buffer *buf = wl_container_of(wlr_buf, buf, base);
*data = pixman_image_get_data(buf->image);
*format = DRM_FORMAT_ARGB8888;
*stride = pixman_image_get_stride(buf->image);
return true;
}
static void bg_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buf)
{
(void)wlr_buf;
}
static const struct wlr_buffer_impl bg_buffer_impl = {
.destroy = bg_buffer_destroy,
.begin_data_ptr_access = bg_buffer_begin_data_ptr_access,
.end_data_ptr_access = bg_buffer_end_data_ptr_access,
};
static int parse_color_to_rcolor(WScreen *scr, const char *name, RColor *out)
{
WMColorRGB wmc;
if (!name || !wGetColor(scr, name, &wmc))
return 0;
out->red = wmc.red >> 8;
out->green = wmc.green >> 8;
out->blue = wmc.blue >> 8;
out->alpha = 255;
return 1;
}
void wl_set_desktop_background(WScreen *scr, const char *texture_desc)
{
if (!scr || !texture_desc)
return;
if (!wl_state.output) {
/* Output not ready yet — cache the background for later */
wfree(wl_state.pending_background);
wl_state.pending_background = wstrdup(texture_desc);
return;
}
int width = wl_state.output->width;
int height = wl_state.output->height;
/* Parse the texture proplist */
WMPropList *texpl = WMCreatePropListFromDescription(texture_desc);
if (!texpl) {
return;
}
/* Render the texture to an RImage using wrlib */
RContext *rc = scr->rcontext;
RImage *bg = NULL;
if (WMIsPLArray(texpl) && WMGetPropListItemCount(texpl) > 0) {
WMPropList *type_pl = WMGetFromPLArray(texpl, 0);
const char *type = type_pl ? WMGetFromPLString(type_pl) : "";
if (strcasecmp(type, "solid") == 0 && WMGetPropListItemCount(texpl) >= 2) {
const char *colorstr = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
RColor color = {0x50, 0x50, 0x75, 0xFF};
if (colorstr)
parse_color_to_rcolor(scr, colorstr, &color);
/* For solid colors, just update the scene rect directly */
if (wl_state.bg_rect) {
float c[4] = { color.red/255.0f, color.green/255.0f, color.blue/255.0f, 1.0f };
wlr_scene_rect_set_color(wl_state.bg_rect, c);
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, true);
if (wl_state.bg_image)
wlr_scene_node_set_enabled(&wl_state.bg_image->node, false);
}
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
WMReleasePropList(texpl);
return;
} else if ((strcasecmp(type, "vgradient") == 0 ||
strcasecmp(type, "hgradient") == 0 ||
strcasecmp(type, "dgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
const char *c1str = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
const char *c2str = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
RColor color1 = {0, 0, 0, 255}, color2 = {0, 0, 0, 255};
if (c1str) parse_color_to_rcolor(scr, c1str, &color1);
if (c2str) parse_color_to_rcolor(scr, c2str, &color2);
int style = (type[0] == 'v' || type[0] == 'V') ? RVerticalGradient :
(type[0] == 'h' || type[0] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderGradient(width, height, &color1, &color2, style);
} else if ((strcasecmp(type, "mvgradient") == 0 ||
strcasecmp(type, "mhgradient") == 0 ||
strcasecmp(type, "mdgradient") == 0) &&
WMGetPropListItemCount(texpl) >= 3) {
int ncolors = WMGetPropListItemCount(texpl) - 1;
RColor **colors = wmalloc((ncolors + 1) * sizeof(RColor *));
RColor *cbuf = wmalloc(ncolors * sizeof(RColor));
int ci;
for (ci = 0; ci < ncolors; ci++) {
const char *cs = WMGetFromPLString(WMGetFromPLArray(texpl, ci + 1));
cbuf[ci] = (RColor){0, 0, 0, 255};
if (cs) parse_color_to_rcolor(scr, cs, &cbuf[ci]);
colors[ci] = &cbuf[ci];
}
colors[ncolors] = NULL;
int style = (type[1] == 'v' || type[1] == 'V') ? RVerticalGradient :
(type[1] == 'h' || type[1] == 'H') ? RHorizontalGradient :
RDiagonalGradient;
bg = RRenderMultiGradient(width, height, colors, style);
wfree(cbuf);
wfree(colors);
} else if ((strcasecmp(type, "pixmap") == 0 ||
strcasecmp(type, "spixmap") == 0 ||
strcasecmp(type, "mpixmap") == 0 ||
strcasecmp(type, "cpixmap") == 0 ||
strcasecmp(type, "fpixmap") == 0 ||
strcasecmp(type, "tpixmap") == 0) &&
WMGetPropListItemCount(texpl) >= 2) {
const char *path = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
/* Background color (3rd argument, optional) */
RColor bgcolor = {0, 0, 0, 255};
if (WMGetPropListItemCount(texpl) >= 3) {
const char *cstr = WMGetFromPLString(WMGetFromPLArray(texpl, 2));
if (cstr) parse_color_to_rcolor(scr, cstr, &bgcolor);
}
if (path) {
/* Try the path as-is first, then search pixmap_path */
RImage *img = RLoadImage(rc, path, 0);
if (!img && path[0] != '/') {
/* Relative path — search pixmap_path */
char *search_paths[] = { wPreferences.pixmap_path, PKGDATADIR "/Backgrounds", NULL };
for (int _pi = 0; search_paths[_pi]; _pi++) {
char _full[PATH_MAX];
snprintf(_full, sizeof(_full), "%s/%s", search_paths[_pi], path);
img = RLoadImage(rc, _full, 0);
if (img) break;
}
}
if (!img) {
} else {
/* Combine with background color if image has alpha */
if (img->format == RRGBAFormat)
RCombineImageWithColor(img, &bgcolor);
char mode = (type[0] >= 'a' && type[0] <= 'z') ? type[0] - 32 : type[0];
if (mode == 'S') {
/* Scaled: stretch to exact screen size */
bg = RSmoothScaleImage(img, width, height);
} else if (mode == 'F') {
/* Filled: scale preserving aspect, fill screen (crop) */
int w, h;
if (img->width * height > img->height * width) {
w = (height * img->width) / img->height;
h = height;
} else {
w = width;
h = (width * img->height) / img->width;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int sx = (scaled->width - width) / 2;
int sy = (scaled->height - height) / 2;
RCopyArea(bg, scaled, sx, sy, width, height, 0, 0);
}
RReleaseImage(scaled);
}
} else if (mode == 'M') {
/* Maximized: scale preserving aspect, fit inside (letterbox) */
int w, h;
if (img->width * height > img->height * width) {
w = width;
h = (width * img->height) / img->width;
} else {
w = (height * img->width) / img->height;
h = height;
}
RImage *scaled = RSmoothScaleImage(img, w, h);
if (scaled) {
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - scaled->width) / 2;
int dy = (height - scaled->height) / 2;
RCombineArea(bg, scaled, 0, 0, scaled->width, scaled->height, dx, dy);
}
RReleaseImage(scaled);
}
} else if (mode == 'C') {
/* Centered: place at center, no scaling */
bg = RCreateImage(width, height, 0);
if (bg) {
RClearImage(bg, &bgcolor);
int dx = (width - img->width) / 2;
int dy = (height - img->height) / 2;
/* Handle images larger than screen */
int sx = 0, sy = 0, cw = img->width, ch = img->height;
if (dx < 0) { sx = -dx; dx = 0; cw = width; }
if (dy < 0) { sy = -dy; dy = 0; ch = height; }
RCombineArea(bg, img, sx, sy, cw, ch, dx, dy);
}
} else if (mode == 'T') {
/* Tiled: repeat the image */
bg = RCreateImage(width, height, 0);
if (bg) {
for (int ty = 0; ty < height; ty += img->height) {
for (int tx = 0; tx < width; tx += img->width) {
int cw = img->width;
int ch = img->height;
if (tx + cw > width) cw = width - tx;
if (ty + ch > height) ch = height - ty;
RCombineArea(bg, img, 0, 0, cw, ch, tx, ty);
}
}
}
} else {
/* Default (plain "pixmap"): scale to fill */
bg = RSmoothScaleImage(img, width, height);
}
RReleaseImage(img);
}
}
}
}
WMReleasePropList(texpl);
if (!bg) {
return;
}
/* Convert RImage to pixman_image and set as scene buffer */
struct wl_bg_buffer *buf = calloc(1, sizeof(*buf));
if (!buf) { RReleaseImage(bg); return; }
buf->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, width * 4);
if (!buf->image) { free(buf); RReleaseImage(bg); return; }
/* Copy RImage (RGBA or RGB) into ARGB32 pixman buffer */
uint32_t *dst = (uint32_t *)pixman_image_get_data(buf->image);
unsigned char *src = bg->data;
int npixels = width * height;
if (bg->format == RRGBAFormat) {
for (int i = 0; i < npixels; i++) {
dst[i] = ((uint32_t)src[3] << 24) | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 4;
}
} else {
for (int i = 0; i < npixels; i++) {
dst[i] = 0xFF000000 | ((uint32_t)src[0] << 16) |
((uint32_t)src[1] << 8) | (uint32_t)src[2];
src += 3;
}
}
RReleaseImage(bg);
wlr_buffer_init(&buf->base, &bg_buffer_impl, width, height);
/* Hide the solid-color rect */
if (wl_state.bg_rect)
wlr_scene_node_set_enabled(&wl_state.bg_rect->node, false);
/* Replace or create the background scene buffer */
if (wl_state.bg_image) {
wlr_scene_buffer_set_buffer(wl_state.bg_image, &buf->base);
wlr_scene_node_set_enabled(&wl_state.bg_image->node, true);
} else {
wl_state.bg_image = wlr_scene_buffer_create(
wl_state.level_trees[WL_LAYER_BACKGROUND], &buf->base);
}
/* Release old buffer, keep new one */
if (wl_state.bg_wlr_buf)
wlr_buffer_drop(wl_state.bg_wlr_buf);
wl_state.bg_wlr_buf = &buf->base;
/* Schedule a frame to display the new background immediately */
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
}