Wayland backend: native frame rendering via pixman + wlr_scene_buffer

Architecture: frames are pixman images committed to wlr_scene_buffer
nodes — no XWayland dependency for WM UI rendering.

Key changes:
- Add wl_frame_buf struct (pixman_image_t + wlr_buffer + scene_buffer)
- Implement wlr_buffer_impl for frame buffers (begin_data_ptr_access)
- frame_create_toplevel: allocates pixman buffer + scene node
- frame_create_child: returns synthetic ID (sub-regions of parent)
- frame_configure: repositions scene node, reallocates on resize
- frame_destroy: destroys scene node and drops buffer
- frame_paint: renders titlebar texture (via wTextureRenderImage),
  title text (via WMDrawString on pixman WMDrawable), and resizebar
  into the pixman buffer, then commits to scene graph
- Add frame_paint vtable slot to WMBackend (backend.h)
- wFrameWindowPaint: calls wm_backend->frame_paint first; falls
  through to Xlib code only if backend returns 0
- window_map/unmap/move: handle frame buffers via scene node ops
- stacking_raise/lower: handle frame buffers
- window_get_rect: return frame buffer geometry
- Remove all XWayland calls from frame/window lifecycle

The OR (override_redirect) surface handling remains for XWayland
client windows that use override_redirect (tooltips, menus from
X11 apps). The WM's own UI is now pure Wayland.
This commit is contained in:
Levi Neely 2026-05-13 07:15:57 +02:00
parent 9b2448dd7f
commit 499d4fac7e
4 changed files with 343 additions and 104 deletions

View File

@ -37,6 +37,7 @@ typedef struct _WScreen WScreen;
typedef struct WWindow WWindow;
typedef struct GNUstepWMAttributes GNUstepWMAttributes;
struct WObjDescriptor;
struct WFrameWindow;
/*
* WMBackend -- vtable for all display-system operations.
@ -168,6 +169,16 @@ typedef struct WMBackend {
*/
void (*frame_configure)(WNativeWindow win, int x, int y, int width, int height);
/*
* Render the complete frame decoration (titlebar, buttons, resizebar)
* into the frame's backing store and make it visible.
* X11: uses Xlib drawing on the frame sub-windows (the traditional path).
* Wayland: renders into a pixman buffer and commits to the scene graph.
* Returns 1 if the backend handled painting, 0 if the caller should
* fall through to legacy Xlib code (X11 backend returns 0).
*/
int (*frame_paint)(struct WFrameWindow *fwin);
/* ------------------------------------------------------------------ */
/* Window context table (Phase 2) */
/* */

View File

@ -182,6 +182,9 @@
#include "../../WindowMaker.h"
#include "../../screen.h"
#include "../../window.h"
#include "../../framewin.h" /* WFrameWindow for frame_paint */
#include "../../texture.h" /* WTexture, wTextureRenderImage */
#include "../../misc.h" /* ShrinkString */
#include "../../actions.h" /* Phase 16: wMaximizeWindow, wIconifyWindow, etc. */
#include "../../stacking.h" /* Phase 24: wRaiseFrame for click-to-raise */
#include "../../xinerama.h" /* Phase 16: wGetHeadForWindow */
@ -265,6 +268,21 @@ struct wl_or_view {
struct wl_list link; /* wl_state.or_list */
};
/*
* wl_frame_buf — a pixman-backed scene buffer for WM frame windows.
* Each frame (titlebar + buttons + resizebar) is rendered into a single
* ARGB pixman image and displayed via a wlr_scene_buffer node.
*/
struct wl_frame_buf {
struct wlr_buffer base; /* wlr_buffer (must be first) */
pixman_image_t *image; /* ARGB32 pixel data */
struct wlr_scene_buffer *scene_buf; /* scene graph node */
WNativeWindow id; /* synthetic ID (>= 0x10000000) */
int x, y; /* position in output coords */
int width, height;
struct wl_list link; /* wl_state.frame_list */
};
/*
* Phase 36: wl_layer_view — one entry per mapped zwlr_layer_shell_v1 surface.
* Tracks the wlroots layer surface, its scene node, and lifecycle listeners.
@ -315,6 +333,7 @@ struct wl_compositor_state {
/* Phase 38: per-toplevel view list and context store */
struct wl_list toplevel_list; /* wl_toplevel_view.link */
struct wl_list or_list; /* wl_or_view.link */
struct wl_list frame_list; /* wl_frame_buf.link */
struct wl_list layer_list; /* wl_layer_view.link */
struct wl_list ctx_lists[3]; /* one per WContextKey */
unsigned long next_synthetic_id; /* >= 0x10000000 */
@ -2353,6 +2372,7 @@ wl_display_open(const char *name)
/* Phase 38: initialise per-toplevel tracking and context store. */
wl_list_init(&wl_state.toplevel_list);
wl_list_init(&wl_state.or_list);
wl_list_init(&wl_state.frame_list);
wl_list_init(&wl_state.layer_list);
{
int ki;
@ -3429,6 +3449,50 @@ wl_screen_open(WScreen *scr)
static void wl_screen_close(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
/* Frame buffer (pixman + wlr_scene_buffer) */
/* ------------------------------------------------------------------ */
static void frame_buf_destroy(struct wlr_buffer *wlr_buf)
{
struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base);
if (fb->image)
pixman_image_unref(fb->image);
wfree(fb);
}
static bool frame_buf_begin_data_ptr_access(struct wlr_buffer *wlr_buf,
uint32_t flags, void **data, uint32_t *format, size_t *stride)
{
struct wl_frame_buf *fb = wl_container_of(wlr_buf, fb, base);
(void)flags;
*data = pixman_image_get_data(fb->image);
*format = DRM_FORMAT_ARGB8888;
*stride = (size_t)pixman_image_get_stride(fb->image);
return true;
}
static void frame_buf_end_data_ptr_access(struct wlr_buffer *wlr_buf)
{
(void)wlr_buf;
}
static const struct wlr_buffer_impl frame_buf_impl = {
.destroy = frame_buf_destroy,
.begin_data_ptr_access = frame_buf_begin_data_ptr_access,
.end_data_ptr_access = frame_buf_end_data_ptr_access,
};
static struct wl_frame_buf *
frame_buf_find(WNativeWindow id)
{
struct wl_frame_buf *fb;
wl_list_for_each(fb, &wl_state.frame_list, link) {
if (fb->id == id) return fb;
}
return NULL;
}
/* ------------------------------------------------------------------ */
/* Frame / window lifecycle */
/* ------------------------------------------------------------------ */
@ -3441,82 +3505,239 @@ wl_frame_create_toplevel(WScreen *scr,
unsigned long colormap,
WMPixel border_pixel)
{
if (!dpy)
(void)border_width; (void)depth; (void)visual;
(void)colormap; (void)border_pixel; (void)scr;
if (width <= 0) width = 1;
if (height <= 0) height = 1;
struct wl_frame_buf *fb = wmalloc(sizeof(*fb));
memset(fb, 0, sizeof(*fb));
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
width, height, NULL, 0);
if (!fb->image) {
wfree(fb);
return WNATIVE_NONE;
int vmask;
XSetWindowAttributes attribs;
Window win;
vmask = CWBorderPixel | CWCursor | CWEventMask | CWOverrideRedirect | CWColormap;
attribs.override_redirect = True;
attribs.cursor = wPreferences.cursor[WCUR_NORMAL];
attribs.background_pixmap = None;
attribs.background_pixel = scr->black_pixel;
attribs.border_pixel = border_pixel;
attribs.event_mask = SubstructureRedirectMask | ButtonPressMask |
ButtonReleaseMask | ButtonMotionMask |
ExposureMask | EnterWindowMask | LeaveWindowMask;
attribs.colormap = (Colormap)colormap;
if (wPreferences.use_saveunders) {
vmask |= CWSaveUnder;
attribs.save_under = True;
}
win = XCreateWindow(dpy, (Window)scr->root_win,
x, y, width, height, border_width,
depth, CopyFromParent, (Visual *)visual, vmask, &attribs);
XClearWindow(dpy, win);
return (WNativeWindow)win;
wlr_buffer_init(&fb->base, &frame_buf_impl, width, height);
fb->id = wl_state.next_synthetic_id++;
fb->x = x;
fb->y = y;
fb->width = width;
fb->height = height;
/* Create scene buffer node. */
if (wl_state.scene) {
fb->scene_buf = wlr_scene_buffer_create(&wl_state.scene->tree,
&fb->base);
if (fb->scene_buf) {
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
}
}
wl_list_insert(&wl_state.frame_list, &fb->link);
return fb->id;
}
static WNativeWindow
wl_frame_create_child(WNativeWindow parent_win, WScreen *scr,
int x, int y, int width, int height)
{
if (!dpy)
return WNATIVE_NONE;
int vmask;
XSetWindowAttributes attribs;
Window win;
vmask = CWBorderPixel | CWCursor | CWEventMask | CWColormap;
attribs.cursor = wPreferences.cursor[WCUR_NORMAL];
attribs.background_pixmap = None;
attribs.background_pixel = scr->black_pixel;
attribs.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask |
ButtonReleaseMask | ButtonMotionMask |
ExposureMask | EnterWindowMask | LeaveWindowMask;
attribs.colormap = scr->w_colormap;
win = XCreateWindow(dpy, (Window)parent_win,
x, y, width, height, 0,
scr->w_depth, CopyFromParent,
scr->w_visual, vmask, &attribs);
return (WNativeWindow)win;
(void)parent_win; (void)scr;
(void)x; (void)y; (void)width; (void)height;
/* Child windows (titlebar, buttons, resizebar) are sub-regions of the
* parent frame buffer. Return a synthetic ID so context_save works,
* but no separate buffer is needed — frame_paint renders everything. */
return wl_state.next_synthetic_id++;
}
static void
wl_frame_destroy(WScreen *scr, WNativeWindow win)
{
(void)scr;
if (dpy && win != WNATIVE_NONE)
XDestroyWindow(dpy, (Window)win);
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb) return;
if (fb->scene_buf)
wlr_scene_node_destroy(&fb->scene_buf->node);
wl_list_remove(&fb->link);
wlr_buffer_drop(&fb->base);
}
static void
wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
{
if (!dpy || win == WNATIVE_NONE)
return;
XWindowChanges xwc;
xwc.x = x;
xwc.y = y;
xwc.width = width;
xwc.height = height;
XConfigureWindow(dpy, (Window)win, CWX | CWY | CWWidth | CWHeight, &xwc);
struct wl_frame_buf *fb = frame_buf_find(win);
if (!fb) return;
fb->x = x;
fb->y = y;
if (fb->scene_buf)
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
/* Resize: reallocate the pixman image if dimensions changed. */
if (width > 0 && height > 0 && (width != fb->width || height != fb->height)) {
pixman_image_unref(fb->image);
fb->image = pixman_image_create_bits(PIXMAN_a8r8g8b8,
width, height, NULL, 0);
fb->width = width;
fb->height = height;
fb->base.width = width;
fb->base.height = height;
}
}
/*
* wl_frame_paint — render the entire frame decoration into the pixman buffer.
*
* Uses wTextureRenderImage() (pure CPU) for titlebar/resizebar backgrounds,
* pixman for bevel lines, and the WINGs font backend for title text.
* Returns 1 (handled) so wFrameWindowPaint skips the Xlib code path.
*/
static int
wl_frame_paint(struct WFrameWindow *fwin)
{
struct wl_frame_buf *fb = frame_buf_find(fwin->core->window);
if (!fb || !fb->image)
return 0; /* not our frame — shouldn't happen */
int state = fwin->flags.state;
int total_w = fb->width;
int total_h = fb->height;
/* Clear the buffer to transparent black. */
pixman_color_t clear = {0, 0, 0, 0xFFFF};
pixman_image_fill_rectangles(PIXMAN_OP_SRC, fb->image, &clear,
1, &(pixman_rectangle16_t){0, 0, total_w, total_h});
/* --- Titlebar --- */
if (fwin->titlebar && fwin->top_width > 0) {
int tb_w = total_w;
int tb_h = fwin->top_width;
WTexture *tex = fwin->title_texture[state];
/* Render texture to RImage. */
RImage *img = wTextureRenderImage(tex, tb_w, tb_h, WREL_RAISED);
if (img) {
/* Blit RImage into the pixman buffer at (0, 0).
* RImage is RGBA (4 bytes/pixel) or RGB (3 bytes/pixel). */
uint32_t *dst = pixman_image_get_data(fb->image);
int dst_stride = pixman_image_get_stride(fb->image) / 4;
int x, y;
if (img->format == RRGBAFormat) {
for (y = 0; y < tb_h && y < img->height; y++) {
unsigned char *src_row = img->data + y * img->width * 4;
uint32_t *dst_row = dst + y * dst_stride;
for (x = 0; x < tb_w && x < img->width; x++) {
unsigned char r = src_row[x*4+0];
unsigned char g = src_row[x*4+1];
unsigned char b = src_row[x*4+2];
unsigned char a = src_row[x*4+3];
dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
}
} else {
/* RGB format */
for (y = 0; y < tb_h && y < img->height; y++) {
unsigned char *src_row = img->data + y * img->width * 3;
uint32_t *dst_row = dst + y * dst_stride;
for (x = 0; x < tb_w && x < img->width; x++) {
unsigned char r = src_row[x*3+0];
unsigned char g = src_row[x*3+1];
unsigned char b = src_row[x*3+2];
dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
}
}
RReleaseImage(img);
}
/* Draw title text. */
if (fwin->title && *fwin->font && fwin->title_color[state]) {
int lofs = 6, rofs = 6;
int titlelen, tw, tx, ty, th;
char *title;
if (fwin->left_button && !fwin->flags.hide_left_button &&
!fwin->flags.lbutton_dont_fit)
lofs += fwin->left_button->width + 3;
if (fwin->right_button && !fwin->flags.hide_right_button &&
!fwin->flags.rbutton_dont_fit)
rofs += fwin->right_button->width + 3;
title = ShrinkString(*fwin->font, fwin->title, tb_w - lofs - rofs);
titlelen = strlen(title);
tw = WMWidthOfString(*fwin->font, title, titlelen);
th = WMFontHeight(*fwin->font);
switch (fwin->flags.justification) {
case WTJ_LEFT: tx = lofs; break;
case WTJ_RIGHT: tx = tb_w - tw - rofs; break;
default: tx = (tb_w - tw) / 2; break;
}
ty = (tb_h - th) / 2;
/* Draw text into the frame buffer using WMDrawable = pixman_image_t* */
WMDrawString(fwin->screen_ptr->wmscreen, (WMDrawable)fb->image,
fwin->title_color[state], *fwin->font,
tx, ty, title, titlelen);
wfree(title);
}
}
/* --- Resizebar --- */
if (fwin->resizebar && fwin->bottom_width > 0) {
int rb_y = total_h - fwin->bottom_width;
int rb_w = total_w;
int rb_h = fwin->bottom_width;
WTexture *tex = fwin->resizebar_texture[0];
RImage *img = wTextureRenderImage(tex, rb_w, rb_h, WREL_RAISED);
if (img) {
uint32_t *dst = pixman_image_get_data(fb->image);
int dst_stride = pixman_image_get_stride(fb->image) / 4;
int x, y;
if (img->format == RRGBAFormat) {
for (y = 0; y < rb_h && y < img->height; y++) {
unsigned char *src_row = img->data + y * img->width * 4;
uint32_t *dst_row = dst + (rb_y + y) * dst_stride;
for (x = 0; x < rb_w && x < img->width; x++) {
unsigned char r = src_row[x*4+0];
unsigned char g = src_row[x*4+1];
unsigned char b = src_row[x*4+2];
unsigned char a = src_row[x*4+3];
dst_row[x] = ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
}
} else {
for (y = 0; y < rb_h && y < img->height; y++) {
unsigned char *src_row = img->data + y * img->width * 3;
uint32_t *dst_row = dst + (rb_y + y) * dst_stride;
for (x = 0; x < rb_w && x < img->width; x++) {
unsigned char r = src_row[x*3+0];
unsigned char g = src_row[x*3+1];
unsigned char b = src_row[x*3+2];
dst_row[x] = 0xFF000000U | ((uint32_t)r << 16) |
((uint32_t)g << 8) | (uint32_t)b;
}
}
}
RReleaseImage(img);
}
}
/* Commit the buffer to the scene graph. */
if (fb->scene_buf)
wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base);
return 1;
}
/* ------------------------------------------------------------------ */
@ -4702,7 +4923,6 @@ static void wl_server_ungrab(void) { overlay_flush(); }
static void
wl_stacking_raise(WNativeWindow win)
{
/* Phase 58: move view to head of list (top of Z-order). */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
wl_list_remove(&v->link);
@ -4711,15 +4931,16 @@ wl_stacking_raise(WNativeWindow win)
wlr_scene_node_raise_to_top(&v->scene_node->node);
return;
}
/* Frame/icon windows on XWayland. */
if (dpy && win != WNATIVE_NONE)
XRaiseWindow(dpy, (Window)win);
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
return;
}
}
static void
wl_stacking_lower(WNativeWindow win)
{
/* Phase 58: move view to tail of list (bottom of Z-order). */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
wl_list_remove(&v->link);
@ -4728,8 +4949,11 @@ wl_stacking_lower(WNativeWindow win)
wlr_scene_node_lower_to_bottom(&v->scene_node->node);
return;
}
if (dpy && win != WNATIVE_NONE)
XLowerWindow(dpy, (Window)win);
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_lower_to_bottom(&fb->scene_buf->node);
return;
}
}
static void
@ -4978,16 +5202,17 @@ wl_prop_get_wm_state(WNativeWindow win, long **data_out)
static void
wl_window_map(WNativeWindow win)
{
/* Show the scene node for this window (workspace switch arriving on workspace). */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, true);
return;
}
/* Frame/icon/menu windows live on XWayland — map them via Xlib.
* XWayland will fire associate → handle_or_map adds the scene node. */
if (dpy && win != WNATIVE_NONE)
XMapWindow(dpy, (Window)win);
/* Frame buffer: enable its scene node. */
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
return;
}
}
static void
@ -4999,36 +5224,38 @@ wl_window_map_raised(WNativeWindow win)
wlr_scene_node_raise_to_top(&v->scene_node->node);
return;
}
if (dpy && win != WNATIVE_NONE)
XMapRaised(dpy, (Window)win);
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_set_enabled(&fb->scene_buf->node, true);
wlr_scene_node_raise_to_top(&fb->scene_buf->node);
return;
}
}
static void wl_window_map_subwindows(WNativeWindow parent)
{
if (dpy && parent != WNATIVE_NONE)
XMapSubwindows(dpy, (Window)parent);
(void)parent;
/* Child windows are sub-regions of the frame buffer — no-op. */
}
static void
wl_window_unmap(WNativeWindow win)
{
/* Hide the scene node for this window (workspace switch leaving workspace). */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, false);
return;
}
/* Frame/icon/menu windows on XWayland. */
if (dpy && win != WNATIVE_NONE)
XUnmapWindow(dpy, (Window)win);
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb && fb->scene_buf) {
wlr_scene_node_set_enabled(&fb->scene_buf->node, false);
return;
}
}
static void wl_window_move(WNativeWindow win, int x, int y)
{
/* Phase 55: record the WM-assigned position in the view struct.
* Phase 17: also inform XWayland of the new position. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
v->x = x; v->y = y;
/* Phase 18: keep scene node in sync with WM position. */
if (v->scene_node)
wlr_scene_node_set_position(&v->scene_node->node, x, y);
if (v->xw_surface)
@ -5036,15 +5263,17 @@ static void wl_window_move(WNativeWindow win, int x, int y)
(int16_t)x, (int16_t)y, v->xw_surface->width, v->xw_surface->height);
return;
}
/* Frame/icon windows on XWayland. */
if (dpy && win != WNATIVE_NONE)
XMoveWindow(dpy, (Window)win, x, y);
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb) {
fb->x = x; fb->y = y;
if (fb->scene_buf)
wlr_scene_node_set_position(&fb->scene_buf->node, x, y);
return;
}
}
static void wl_window_resize(WNativeWindow win, int w, int h)
{
/* Phase 44: send a configure with the new size.
* Phase 17: XWayland path uses wlr_xwayland_surface_configure. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) {
if (w <= 0 || h <= 0) return;
@ -5058,25 +5287,20 @@ static void wl_window_resize(WNativeWindow win, int w, int h)
wlr_xdg_surface_schedule_configure(v->xdg_surface);
return;
}
/* Frame/icon windows on XWayland. */
if (dpy && win != WNATIVE_NONE && w > 0 && h > 0)
XResizeWindow(dpy, (Window)win, (unsigned)w, (unsigned)h);
/* Frame buffer resize is handled by frame_configure. */
}
static void wl_window_set_border(WNativeWindow win, unsigned long pixel)
{
if (dpy && win != WNATIVE_NONE)
XSetWindowBorder(dpy, (Window)win, pixel);
(void)win; (void)pixel; /* borders are drawn into the frame buffer */
}
static void wl_window_set_border_width(WNativeWindow win, int width)
{
if (dpy && win != WNATIVE_NONE)
XSetWindowBorderWidth(dpy, (Window)win, (unsigned)width);
(void)win; (void)width; /* no-op for native frames */
}
static void wl_window_set_save_under(WNativeWindow win, int save_under) { (void)win; (void)save_under; }
static void wl_window_clear(WNativeWindow win)
{
if (dpy && win != WNATIVE_NONE)
XClearWindow(dpy, (Window)win);
(void)win; /* frame buffer is cleared in frame_paint */
}
static int
@ -5107,18 +5331,16 @@ wl_window_get_rect(WNativeWindow win,
if (win_h) { *win_h = h > 0 ? h : 1; }
return 1;
}
/* Non-synthetic: query XWayland for frame/icon windows. */
if (dpy) {
Window root_ret;
int rx, ry;
unsigned int rw, rh, rbw, rdepth;
if (XGetGeometry(dpy, (Window)win, &root_ret, &rx, &ry, &rw, &rh, &rbw, &rdepth)) {
if (win_x) { *win_x = rx; } if (win_y) { *win_y = ry; }
if (win_w) { *win_w = (int)rw > 0 ? (int)rw : 1; }
if (win_h) { *win_h = (int)rh > 0 ? (int)rh : 1; }
return 1;
}
/* Frame buffer geometry. */
struct wl_frame_buf *fb = frame_buf_find(win);
if (fb) {
if (win_x) { *win_x = fb->x; }
if (win_y) { *win_y = fb->y; }
if (win_w) { *win_w = fb->width; }
if (win_h) { *win_h = fb->height; }
return 1;
}
/* Synthetic child IDs (titlebar, buttons) — return minimal geometry. */
if (win_x) { *win_x = 0; } if (win_y) { *win_y = 0; }
if (win_w) { *win_w = 1; } if (win_h) { *win_h = 1; }
return 0;
@ -5804,6 +6026,7 @@ const WMBackend wl_backend = {
.frame_create_child = wl_frame_create_child,
.frame_destroy = wl_frame_destroy,
.frame_configure = wl_frame_configure,
.frame_paint = wl_frame_paint,
.context_save = wl_context_save,
.context_find = wl_context_find,

View File

@ -2413,6 +2413,7 @@ const WMBackend x11_backend = {
.frame_create_child = x11_frame_create_child,
.frame_destroy = x11_frame_destroy,
.frame_configure = x11_frame_configure,
.frame_paint = NULL, /* X11: use legacy Xlib code in framewin.c */
.context_save = x11_context_save,
.context_find = x11_context_find,
.context_delete = x11_context_delete,

View File

@ -952,6 +952,10 @@ void wFrameWindowPaint(WFrameWindow * fwin)
WScreen *scr = fwin->screen_ptr;
int state;
/* Let the backend handle painting if it can (Wayland: pixman rendering). */
if (wm_backend->frame_paint && wm_backend->frame_paint(fwin))
return;
state = fwin->flags.state;
if (fwin->flags.is_client_window_frame)