A4: workspace switching badge for Wayland backend

Workspace switching (map/unmap) already worked via the vtable.
This commit adds the workspace name badge overlay:

- Create badge frame_buf in WL_LAYER_OVERLAY during screen init
- Implement wl_workspace_badge_render (pixman text rendering)
- Implement wl_workspace_badge_update (scene buffer refresh)
- Add workspace_badge_get_background vtable slot
- X11 backend: captures root window background (RGetXImage)
- Wayland backend: returns solid dark background
- workspace.c: replace direct RGetXImage/RCreateImageFromXImage
  with vtable call (zero backend guards in policy code)
This commit is contained in:
lkn 2026-05-23 13:24:58 +02:00
parent cc9fc26817
commit de0300d0fe
4 changed files with 152 additions and 9 deletions

View File

@ -543,6 +543,18 @@ typedef struct WMBackend {
void (*workspace_badge_update)(WScreen *scr, WNativeWindow badge_win,
RImage *img);
/*
* Capture the background behind the workspace badge for the fade effect.
*
* X11: RGetXImage + RCreateImageFromXImage on the root window.
* Wayland: returns a solid dark RImage (no screen capture needed).
*
* Returns an RImage* owned by the caller (must be freed with RReleaseImage).
* Returns NULL on failure.
*/
RImage *(*workspace_badge_get_background)(WScreen *scr, int x, int y,
int width, int height);
/* ------------------------------------------------------------------ */
/* Server grab (Phase 11) */
/* ------------------------------------------------------------------ */

View File

@ -7076,6 +7076,33 @@ wl_screen_create_internals(WScreen *scr)
/* Wayland has no X11-style window border; force to 0 so policy code
* does not offset client positions by a non-existent border. */
scr->frame_border_width = 0;
/* Create workspace name badge overlay (initially 10x10, resized on use) */
if (wl_state.scene) {
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, 10, 10, NULL, 0);
if (fb->image) {
wlr_buffer_init(&fb->base, &frame_buf_impl, 10, 10);
fb->id = wl_state.next_synthetic_id++;
fb->width = 10;
fb->height = 10;
struct wlr_scene_tree *parent = wl_state.level_trees[WL_LAYER_OVERLAY]
? wl_state.level_trees[WL_LAYER_OVERLAY] : &wl_state.scene->tree;
fb->tree = wlr_scene_tree_create(parent);
if (fb->tree) {
fb->scene_buf = wlr_scene_buffer_create(fb->tree, &fb->base);
if (fb->scene_buf)
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
wlr_scene_node_set_enabled(&fb->tree->node, false);
}
wl_list_insert(&wl_state.frame_list, &fb->link);
W_RegisterBacking(fb->id, fb->image);
scr->workspace_name = fb->id;
} else {
wfree(fb);
}
}
}
static void wl_screen_create_pixmaps(WScreen *scr) { (void)scr; }
static void
@ -7147,11 +7174,106 @@ static RImage *
wl_workspace_badge_render(WScreen *scr, WNativeWindow badge_win,
const char *name, int name_len,
int width, int height)
{ (void)scr; (void)badge_win; (void)name; (void)name_len; (void)width; (void)height; return NULL; }
{
struct wl_frame_buf *fb = frame_buf_find(badge_win);
if (!fb) return NULL;
/* Resize pixman buffer if needed */
if (width > 0 && height > 0 && (width != fb->width || height != fb->height)) {
W_UnregisterBacking(fb->id);
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, 0);
if (!fb->image) return NULL;
fb->width = width;
fb->height = height;
fb->base.width = width;
fb->base.height = height;
W_RegisterBacking(fb->id, fb->image);
}
/* Fill black */
pixman_image_t *black_fill = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, 0);
if (black_fill) {
uint32_t *data = pixman_image_get_data(black_fill);
for (int i = 0; i < width * height; i++)
data[i] = 0xFF000000;
pixman_image_composite32(PIXMAN_OP_SRC, black_fill, NULL, fb->image,
0, 0, 0, 0, 0, 0, width, height);
pixman_image_unref(black_fill);
}
/* Draw text (shadow passes + final) */
for (int x = 0; x <= 4; x++)
for (int y = 0; y <= 4; y++)
WMDrawString(scr->wmscreen, (WMDrawable)badge_win, scr->white,
scr->workspace_name_font, x, y, name, name_len);
/* Clear and redraw final text centered */
uint32_t *px = pixman_image_get_data(fb->image);
for (int i = 0; i < width * height; i++)
px[i] = 0xFF000000;
WMDrawString(scr->wmscreen, (WMDrawable)badge_win, scr->white,
scr->workspace_name_font, 2, 2, name, name_len);
/* Update scene buffer */
if (fb->scene_buf)
wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base);
/* Return RImage copy for fade animation */
RImage *result = RCreateImage(width, height, False);
if (result) {
uint32_t *src = pixman_image_get_data(fb->image);
for (int i = 0; i < width * height; i++) {
uint32_t p = src[i];
result->data[i * 3 + 0] = (p >> 16) & 0xFF; /* R */
result->data[i * 3 + 1] = (p >> 8) & 0xFF; /* G */
result->data[i * 3 + 2] = p & 0xFF; /* B */
}
}
return result;
}
static RImage *
wl_workspace_badge_get_background(WScreen *scr, int x, int y, int width, int height)
{
(void)scr; (void)x; (void)y;
RImage *img = RCreateImage(width, height, False);
if (img)
memset(img->data, 8, width * height * 3);
return img;
}
static void
wl_workspace_badge_update(WScreen *scr, WNativeWindow badge_win, RImage *img)
{ (void)scr; (void)badge_win; (void)img; }
{
(void)scr;
if (!img) return;
struct wl_frame_buf *fb = frame_buf_find(badge_win);
if (!fb || !fb->image) return;
int w = img->width;
int h = img->height;
if (w > fb->width) w = fb->width;
if (h > fb->height) h = fb->height;
/* Blit RImage (RGB or RGBA) into pixman ARGB32 buffer */
uint32_t *dst = pixman_image_get_data(fb->image);
int bpp = img->format == RRGBAFormat ? 4 : 3;
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int si = (y * img->width + x) * bpp;
uint8_t r = img->data[si];
uint8_t g = img->data[si + 1];
uint8_t b = img->data[si + 2];
dst[y * fb->width + x] = 0xFF000000 | (r << 16) | (g << 8) | b;
}
}
if (fb->scene_buf)
wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base);
if (wl_state.output)
wlr_output_schedule_frame(wl_state.output);
}
/* ------------------------------------------------------------------ */
/* XKB */
@ -8329,6 +8451,7 @@ const WMBackend wl_backend = {
.workspace_badge_render = wl_workspace_badge_render,
.workspace_badge_update = wl_workspace_badge_update,
.workspace_badge_get_background = wl_workspace_badge_get_background,
.keysym_to_keycode = wl_keysym_to_keycode,
.keysym_from_keycode = wl_keysym_from_keycode,

View File

@ -718,6 +718,17 @@ x11_workspace_badge_update(WScreen *scr, WNativeWindow badge_win, RImage *img)
XFlush(dpy);
}
static RImage *
x11_workspace_badge_get_background(WScreen *scr, int x, int y, int width, int height)
{
RXImage *ximg = RGetXImage(scr->rcontext, scr->root_win, x, y, width, height);
if (!ximg)
return NULL;
RImage *result = RCreateImageFromXImage(scr->rcontext, ximg->image, NULL);
RDestroyXImage(scr->rcontext, ximg);
return result;
}
/* -------------------------------------------------------------------- */
/* Keyboard grab */
/* -------------------------------------------------------------------- */
@ -4258,6 +4269,7 @@ const WMBackend x11_backend = {
.pointer_query_full = x11_pointer_query_full,
.workspace_badge_render = x11_workspace_badge_render,
.workspace_badge_update = x11_workspace_badge_update,
.workspace_badge_get_background = x11_workspace_badge_get_background,
.keyboard_grab = x11_keyboard_grab,
.keyboard_ungrab = x11_keyboard_ungrab,
.keysym_to_keycode = x11_keysym_to_keycode,

View File

@ -244,7 +244,6 @@ static void hideWorkspaceName(void *data)
static void showWorkspaceName(WScreen * scr, int workspace)
{
WorkspaceNameData *data;
RXImage *ximg;
int w, h;
int px, py;
char *name = scr->workspaces[workspace]->name;
@ -330,15 +329,12 @@ static void showWorkspaceName(WScreen * scr, int workspace)
goto erro;
}
ximg = RGetXImage(scr->rcontext, scr->root_win, px, py, data->text->width, data->text->height);
if (!ximg)
goto erro;
wm_backend->window_map_raised(scr->workspace_name);
wm_backend->event_flush();
data->back = RCreateImageFromXImage(scr->rcontext, ximg->image, NULL);
RDestroyXImage(scr->rcontext, ximg);
data->back = wm_backend->workspace_badge_get_background(scr, px, py,
data->text->width,
data->text->height);
if (!data->back) {
goto erro;