wl_backend: implement desktop background rendering

On X11, wmsetbg sets the root window pixmap. On Wayland there's no root
window, so the compositor must render the background directly.

Added wm_backend->set_desktop_background() vtable method:
- X11: NULL (wmsetbg continues to handle it)
- Wayland: parses the texture proplist description and renders:
  - solid colors
  - vertical/horizontal/diagonal gradients (via RRenderGradient)
  - pixmap/spixmap/cpixmap images (via RLoadImage + RScaleImage)

The rendered image is displayed as a wlr_scene_buffer in the
WL_LAYER_BACKGROUND layer, replacing the static solid-color rect.

setWorkspaceBack() in defaults.c now calls the backend method when
available, so background changes from WPrefs take effect immediately.
This commit is contained in:
Window Maker 2026-05-24 14:23:09 +02:00
parent aab89ab11a
commit 93587378d9
4 changed files with 201 additions and 4 deletions

View File

@ -656,6 +656,17 @@ typedef struct WMBackend {
*/
void (*refresh_desktop)(WScreen *scr);
/*
* Set the desktop background from a texture specification (proplist).
* On X11 this is a no-op (wmsetbg handles it).
* On Wayland the compositor renders the background directly.
* `texture_desc` is the proplist description string, e.g.:
* "(solid, \"rgb:50/50/75\")"
* "(pixmap, \"/path/to/image.jpg\")"
* "(vgradient, \"rgb:00/00/80\", \"rgb:00/00/00\")"
*/
void (*set_desktop_background)(WScreen *scr, const char *texture_desc);
/* ------------------------------------------------------------------ */
/* WNativeWindow event subscription (Phase 11) */
/* ------------------------------------------------------------------ */

View File

@ -553,6 +553,9 @@ struct wl_compositor_state {
#define WL_LAYER_OVERLAY 8
#define WL_NUM_LEVEL_BUCKETS 9
struct wlr_scene_tree *level_trees[WL_NUM_LEVEL_BUCKETS];
struct wlr_scene_rect *bg_rect; /* default solid background */
struct wlr_scene_buffer *bg_image; /* wallpaper image (if set) */
struct wlr_buffer *bg_wlr_buf; /* backing buffer for bg_image */
/* Phase 81: full-screen overlay surface for XOR animation drawing */
struct wm_shm_surface *overlay_surf; /* NULL until first draw */
@ -2209,8 +2212,9 @@ handle_backend_new_output(struct wl_listener *listener, void *data)
* surfaces. Default WindowMaker desktop colour: rgb:50/50/75. */
{
float bg[4] = { 0x50/255.0f, 0x50/255.0f, 0x75/255.0f, 1.0f };
wlr_scene_rect_create(wl_state.level_trees[WL_LAYER_BACKGROUND],
(int)output->width, (int)output->height, bg);
wl_state.bg_rect = wlr_scene_rect_create(
wl_state.level_trees[WL_LAYER_BACKGROUND],
(int)output->width, (int)output->height, bg);
wwarning("wl_backend: root background created (%dx%d)",
(int)output->width, (int)output->height);
}
@ -7645,8 +7649,179 @@ static RImage *wl_screen_capture_area(WScreen *scr) { (void)scr; return NULL; }
static RImage *wl_screen_capture_window(WScreen *scr, WWindow *wwin) { (void)scr; (void)wwin; return NULL; }
static void wl_refresh_desktop(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
/* Monitors */
/* --- 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 void wl_set_desktop_background(WScreen *scr, const char *texture_desc)
{
if (!scr || !texture_desc || !wl_state.output)
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};
unsigned int r, g, b;
if (colorstr) {
if (sscanf(colorstr, "rgb:%x/%x/%x", &r, &g, &b) == 3) {
color.red = r; color.green = g; color.blue = b;
} else if (sscanf(colorstr, "#%02x%02x%02x", &r, &g, &b) == 3) {
color.red = r; color.green = g; color.blue = b;
}
}
bg = RCreateImage(width, height, 0);
if (bg) RClearImage(bg, &color);
} 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};
unsigned int r, g, b;
if (c1str && sscanf(c1str, "rgb:%x/%x/%x", &r, &g, &b) == 3) {
color1.red = r; color1.green = g; color1.blue = b;
}
if (c2str && sscanf(c2str, "rgb:%x/%x/%x", &r, &g, &b) == 3) {
color2.red = r; color2.green = g; color2.blue = b;
}
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, "pixmap") == 0 ||
strcasecmp(type, "spixmap") == 0 ||
strcasecmp(type, "mpixmap") == 0 ||
strcasecmp(type, "cpixmap") == 0 ||
strcasecmp(type, "tpixmap") == 0) &&
WMGetPropListItemCount(texpl) >= 2) {
const char *path = WMGetFromPLString(WMGetFromPLArray(texpl, 1));
if (path) {
RImage *img = RLoadImage(rc, path, 0);
if (img) {
if (type[0] == 's' || type[0] == 'S') {
/* Scale to fill */
bg = RScaleImage(img, width, height);
} else if (type[0] == 'c' || type[0] == 'C') {
/* Center */
bg = RCreateImage(width, height, 0);
if (bg) {
RColor black = {0, 0, 0, 255};
RClearImage(bg, &black);
int x = (width - img->width) / 2;
int y = (height - img->height) / 2;
RCombineArea(bg, img, 0, 0, img->width, img->height, x, y);
}
} else {
/* Default: scale to fill */
bg = RScaleImage(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);
} 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;
}
/* ------------------------------------------------------------------ */
static void
@ -9321,6 +9496,7 @@ const WMBackend wl_backend = {
.screen_capture_area = wl_screen_capture_area,
.screen_capture_window = wl_screen_capture_window,
.refresh_desktop = wl_refresh_desktop,
.set_desktop_background = wl_set_desktop_background,
.monitors_query = wl_monitors_query,
.monitors_select_events = wl_monitors_select_events,

View File

@ -4574,6 +4574,7 @@ const WMBackend x11_backend = {
.window_get_rect = x11_window_get_rect,
.client_snapshot = x11_client_snapshot,
.refresh_desktop = x11_refresh_desktop,
.set_desktop_background = NULL, /* X11: wmsetbg handles this */
.window_select_events = x11_window_select_events,
.window_set_border = x11_window_set_border,
.window_set_border_width = x11_window_set_border_width,

View File

@ -2961,6 +2961,15 @@ static int setWorkspaceBack(WScreen * scr, WDefaultEntry * entry, void *tdata, v
(void) entry;
(void) bar;
/* If the backend can render backgrounds directly (Wayland), use it */
if (wm_backend->set_desktop_background && WMGetPropListItemCount(value) > 0) {
char *str = WMGetPropListDescription(value, False);
if (str) {
wm_backend->set_desktop_background(scr, str);
wfree(str);
}
}
if (scr->flags.backimage_helper_launched) {
char *str;