wayland: WMBackend vtable — port policy files off direct X11

Move X11-specific calls in policy code behind the wm_backend dispatch
table so the same code paths work with both X11 and Wayland backends.

  backend.h    — extend WMBackend vtable with new entry points
  event.c      — route input events through backend dispatch
  framewin.c   — title-bar rendering via backend surface ops
  icon.c       — icon pixmap handling via backend abstraction
  main.c       — backend selection + initialisation at startup
  misc.c       — miscellaneous X11 helpers moved behind vtable
  pixmap.c     — pixmap creation/destruction via backend ops
  resources.c  — resource loading abstracted over backend
  screen.c     — screen setup routed through WMBackend vtable
  startup.c    — startup sequence uses backend entry points
  texture.c    — texture rendering abstracted for both backends

No functional change on X11; Wayland backend stubs return safe no-ops.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-31 13:34:05 +02:00
parent 1dde83e05e
commit 00deab12f2
11 changed files with 193 additions and 69 deletions

View File

@ -46,6 +46,18 @@ struct WObjDescriptor;
*/
typedef struct WMBackend {
/* ------------------------------------------------------------------ */
/* Backend identity flag (Path 1) */
/* ------------------------------------------------------------------ */
/*
* Non-zero if this is the Wayland compositor backend.
* Allows policy code to take the Wayland fast-path even when dpy != NULL
* (i.e. when WindowMaker's X11 UI is bridged through XWayland).
* X11 backend: is_wayland = 0. Wayland backend: is_wayland = 1.
*/
int is_wayland;
/* ------------------------------------------------------------------ */
/* Display lifecycle (Phase 32) */
/* ------------------------------------------------------------------ */
@ -95,6 +107,14 @@ typedef struct WMBackend {
*/
void (*event_loop_run)(void);
/*
* Request the backend event loop to stop.
* X11: no-op (X11 EventLoop exits via exit()).
* Wayland: wl_display_terminate(display) -- causes event_loop_run() to return.
* Called from signal handlers so that cleanup happens outside signal context.
*/
void (*event_loop_terminate)(void);
/* ------------------------------------------------------------------ */
/* Screen lifecycle (Phase 2) */
/* ------------------------------------------------------------------ */

View File

@ -159,7 +159,18 @@ void DispatchEvent(XEvent * event)
* WMHandleEvent() can't be called from anything
* executed inside here, or we can get in a infinite
* recursive loop.
*
* In Wayland mode, calling wl_display_destroy() from a signal
* handler while wl_event_loop_dispatch() is active causes a
* double-free (glibc detects it as SIGABRT). Instead, write a
* byte to the self-pipe; the event loop reads it and calls
* wl_display_terminate() from within loop context. EventLoop()
* then calls Exit(0) after wl_display_run() returns.
*/
if (!dpy) {
wm_backend->event_loop_terminate();
return;
}
Shutdown(WSExitMode);
} else if (WCHECK_STATE(WSTATE_NEED_RESTART)) {
@ -368,8 +379,14 @@ noreturn void EventLoop(void)
int retVal = 0;
#endif
/* Wayland path: delegate to the backend's event loop (wl_display_run). */
if (!dpy) {
/* Wayland path: delegate to the backend's event loop (wl_display_run).
* When SIGTERM is received, event_loop_terminate() writes to a self-pipe
* which causes the event loop to call wl_display_terminate() from within
* loop context; wl_display_run() then returns here, and Exit(0) performs
* the final cleanup safely outside signal-handler and event-loop context.
* Path 1: use wm_backend->is_wayland rather than !dpy because dpy is set
* to the XWayland Display* even in Wayland-compositor mode. */
if (!dpy || wm_backend->is_wayland) {
wm_backend->event_loop_run();
Exit(0);
}

View File

@ -862,6 +862,10 @@ static void remakeTexture(WFrameWindow * fwin, int state)
Pixmap tpmap;
#endif
/* Phase 19: Wayland — no X11 rendering context; RConvertImage would crash */
if (!fwin->screen_ptr->rcontext)
return;
if (fwin->title_texture[state] && fwin->titlebar) {
FREE_PIXMAP(fwin->title_back[state]);
if (wPreferences.new_style == TS_NEW) {
@ -1045,7 +1049,7 @@ void wFrameWindowPaint(WFrameWindow * fwin)
fwin->languagebutton_image = scr->b_pixmaps[WBUT_XKBGROUP1 + fwin->languagemode];
#endif
if (fwin->title) {
if (dpy && fwin->title) {
Drawable buf;
char *title;
@ -1125,10 +1129,12 @@ static void reconfigure(WFrameWindow * fwin, int x, int y, int width, int height
int k = (wPreferences.new_style == TS_NEW ? 4 : 3);
int resizedHorizontally = 0;
if (dontMove)
XResizeWindow(dpy, fwin->core->window, width, height);
else
XMoveResizeWindow(dpy, fwin->core->window, x, y, width, height);
if (dpy) {
if (dontMove)
XResizeWindow(dpy, fwin->core->window, width, height);
else
XMoveResizeWindow(dpy, fwin->core->window, x, y, width, height);
}
if (fwin->core->width != width) {
fwin->flags.need_texture_remake = 1;
@ -1144,12 +1150,12 @@ static void reconfigure(WFrameWindow * fwin, int x, int y, int width, int height
*/
if (fwin->left_button) {
if (width < fwin->top_width * k && !fwin->flags.lbutton_dont_fit) {
if (!fwin->flags.hide_left_button)
if (!fwin->flags.hide_left_button && dpy)
XUnmapWindow(dpy, fwin->left_button->window);
fwin->flags.lbutton_dont_fit = 1;
} else if (width >= fwin->top_width * k && fwin->flags.lbutton_dont_fit) {
if (!fwin->flags.hide_left_button)
if (!fwin->flags.hide_left_button && dpy)
XMapWindow(dpy, fwin->left_button->window);
fwin->flags.lbutton_dont_fit = 0;
@ -1158,12 +1164,12 @@ static void reconfigure(WFrameWindow * fwin, int x, int y, int width, int height
#ifdef XKB_BUTTON_HINT
if (fwin->language_button) {
if (width < fwin->top_width * k && !fwin->flags.languagebutton_dont_fit) {
if (!fwin->flags.hide_language_button)
if (!fwin->flags.hide_language_button && dpy)
XUnmapWindow(dpy, fwin->language_button->window);
fwin->flags.languagebutton_dont_fit = 1;
} else if (width >= fwin->top_width * k && fwin->flags.languagebutton_dont_fit) {
if (!fwin->flags.hide_language_button)
if (!fwin->flags.hide_language_button && dpy)
XMapWindow(dpy, fwin->language_button->window);
fwin->flags.languagebutton_dont_fit = 0;
@ -1173,27 +1179,29 @@ static void reconfigure(WFrameWindow * fwin, int x, int y, int width, int height
if (fwin->right_button) {
if (width < fwin->top_width * 2 && !fwin->flags.rbutton_dont_fit) {
if (!fwin->flags.hide_right_button)
if (!fwin->flags.hide_right_button && dpy)
XUnmapWindow(dpy, fwin->right_button->window);
fwin->flags.rbutton_dont_fit = 1;
} else if (width >= fwin->top_width * 2 && fwin->flags.rbutton_dont_fit) {
if (!fwin->flags.hide_right_button)
if (!fwin->flags.hide_right_button && dpy)
XMapWindow(dpy, fwin->right_button->window);
fwin->flags.rbutton_dont_fit = 0;
}
}
if (wPreferences.new_style == TS_NEW) {
if (fwin->right_button)
XMoveWindow(dpy, fwin->right_button->window,
width - fwin->right_button->width + 1, 0);
} else {
if (fwin->right_button)
XMoveWindow(dpy, fwin->right_button->window,
width - fwin->right_button->width - 3,
(fwin->titlebar->height - fwin->right_button->height) / 2);
if (dpy) {
if (wPreferences.new_style == TS_NEW) {
if (fwin->right_button)
XMoveWindow(dpy, fwin->right_button->window,
width - fwin->right_button->width + 1, 0);
} else {
if (fwin->right_button)
XMoveWindow(dpy, fwin->right_button->window,
width - fwin->right_button->width - 3,
(fwin->titlebar->height - fwin->right_button->height) / 2);
}
}
updateTitlebar(fwin);
checkTitleSize(fwin);
@ -1237,7 +1245,7 @@ int wFrameWindowChangeTitle(WFrameWindow *fwin, const char *new_title)
fwin->title = wstrdup(new_title);
if (fwin->titlebar) {
XClearWindow(dpy, fwin->titlebar->window);
if (dpy) XClearWindow(dpy, fwin->titlebar->window);
wFrameWindowPaint(fwin);
}
@ -1278,6 +1286,12 @@ static void checkTitleSize(WFrameWindow * fwin)
return;
}
/* Phase 19: Wayland — no wmscreen for WMWidthOfString */
if (!fwin->screen_ptr->wmscreen) {
fwin->flags.incomplete_title = 0;
return;
}
if (!fwin->titlebar) {
fwin->flags.incomplete_title = 1;
return;
@ -1309,6 +1323,10 @@ static void paintButton(WCoreWindow * button, WTexture * texture, unsigned long
{
WScreen *scr = button->screen_ptr;
GC copy_gc = scr->copy_gc;
/* Phase 19: Wayland — no X11 display or GCs for button rendering */
if (!dpy)
return;
int x = 0, y = 0, d = 0;
int left = 0, width = 0;

View File

@ -220,10 +220,10 @@ void wIconDestroy(WIcon *icon)
if (icon->icon_name)
XFree(icon->icon_name);
if (icon->pixmap)
if (icon->pixmap && dpy)
XFreePixmap(dpy, icon->pixmap);
if (icon->mini_preview)
if (icon->mini_preview && dpy)
XFreePixmap(dpy, icon->mini_preview);
unset_icon_image(icon);
@ -621,7 +621,7 @@ void set_icon_minipreview(WIcon *icon, RImage *image)
wPreferences.minipreview_size - 2 * MINIPREVIEW_BORDER);
if (RConvertImage(scr->rcontext, scaled_mini_preview, &tmp)) {
if (icon->mini_preview != None)
if (icon->mini_preview != None && dpy)
XFreePixmap(dpy, icon->mini_preview);
icon->mini_preview = tmp;
}

View File

@ -815,7 +815,15 @@ static int real_main(int argc, char **argv)
DisplayName = XDisplayName(DisplayName);
setenv("DISPLAY", DisplayName, 1);
wXModifierInitialize();
/* Path 1: skip XGetModifierMapping() when dpy is an XWayland
* display. XWayland can only service X11 requests when the
* Wayland event loop (wl_display_run) is running, which has not
* started yet at this point in main.c. Calling XGetModifierMapping
* before EventLoop() would deadlock waiting for a reply that
* XWayland cannot deliver. Modifier masks are handled via XKB
* in wl_backend (wSetModifierMasks), so this call is not needed. */
if (!wm_backend->is_wayland)
wXModifierInitialize();
}
StartUp(!multiHead);

View File

@ -76,6 +76,10 @@ static void eatExpose(void)
{
XEvent event, foo;
/* Phase 19: Wayland — no X11 event queue to drain */
if (!dpy)
return;
/* compress all expose events into a single one */
if (XCheckMaskEvent(dpy, ExposureMask, &event)) {
@ -91,6 +95,11 @@ static void eatExpose(void)
void move_window(Window win, int from_x, int from_y, int to_x, int to_y)
{
/* Phase 19: Wayland — frame windows are virtual; skip X11 move */
if (!dpy) {
(void) win; (void) from_x; (void) from_y; (void) to_x; (void) to_y;
return;
}
#ifdef USE_ANIMATIONS
if (wPreferences.no_animations)
XMoveWindow(dpy, win, to_x, to_y);
@ -110,6 +119,12 @@ void move_window(Window win, int from_x, int from_y, int to_x, int to_y)
* following windows are going to be offset by (ICON_SIZE*i,0) */
void slide_windows(Window wins[], int n, int from_x, int from_y, int to_x, int to_y)
{
/* Phase 19: Wayland — frame windows are virtual; skip X11 slide */
if (!dpy) {
(void)wins; (void)n; (void)from_x; (void)from_y; (void)to_x; (void)to_y;
return;
}
time_t time0 = time(NULL);
float dx, dy, x = from_x, y = from_y, px, py;
Bool is_dx_nul, is_dy_nul;
@ -291,6 +306,10 @@ static char *getTextSelection(WScreen * screen, Atom selection)
{
int buffer = -1;
/* Phase 19: Wayland — no X11 selection/clipboard available */
if (!dpy)
return NULL;
switch (selection) {
case WA_CUT_BUFFER0:
buffer = 0;
@ -675,15 +694,20 @@ void ExecuteInputCommand(WScreen *scr, const char *cmdline)
cmd = ExpandOptions(scr, cmdline);
if (cmd) {
XGrabPointer(dpy, scr->root_win, True, 0,
GrabModeAsync, GrabModeAsync, None, wPreferences.cursor[WCUR_WAIT], CurrentTime);
XSync(dpy, False);
/* Phase 19: Wayland — skip cursor grab visual feedback */
if (dpy) {
XGrabPointer(dpy, scr->root_win, True, 0,
GrabModeAsync, GrabModeAsync, None, wPreferences.cursor[WCUR_WAIT], CurrentTime);
XSync(dpy, False);
}
ExecuteShellCommand(scr, cmd);
wfree(cmd);
XUngrabPointer(dpy, CurrentTime);
XSync(dpy, False);
if (dpy) {
XUngrabPointer(dpy, CurrentTime);
XSync(dpy, False);
}
}
}
@ -790,6 +814,9 @@ char *GetShortcutKey(WShortKey key)
}
}
/* Phase 19: Wayland — no X11 keycode-to-keysym mapping available */
if (!dpy)
return NULL;
key_name = XKeysymToString(W_KeycodeToKeysym(dpy, key.keycode, 0));
if (!key_name)
return NULL;

View File

@ -130,11 +130,11 @@ void wPixmapDestroy(WPixmap * pix)
{
if (!pix->shared) {
if (pix->mask && !pix->client_owned_mask) {
XFreePixmap(dpy, pix->mask);
if (dpy) XFreePixmap(dpy, pix->mask);
}
if (pix->image && !pix->client_owned) {
XFreePixmap(dpy, pix->image);
if (dpy) XFreePixmap(dpy, pix->image);
}
}
wfree(pix);

View File

@ -35,6 +35,10 @@
int wGetColorForColormap(Colormap colormap, const char *color_name, XColor *color)
{
/* Phase 19: Wayland — no X11 colormap available */
if (!dpy)
return False;
if (!XParseColor(dpy, colormap, color_name, color)) {
wwarning(_("could not parse color \"%s\""), color_name);
return False;
@ -53,6 +57,9 @@ int wGetColor(WScreen *scr, const char *color_name, XColor *color)
void wFreeColor(WScreen * scr, unsigned long pixel)
{
if (!dpy)
return; /* Phase 19: Wayland — no colormap to free from */
if (pixel != scr->white_pixel && pixel != scr->black_pixel) {
unsigned long colors[1];

View File

@ -158,10 +158,11 @@ WScreen *wScreenInit(int screen_number)
wm_backend->monitors_select_events(scr);
wm_backend->screen_set_root_cursor(scr, WCUR_ROOT);
/* Wayland path: screen_open() sets geometry; X11/wrlib context not needed.
* Allocate workspace 0 directly (wWorkspaceNew is unsafe here because it
* calls wDockCreate which requires X11 rendering resources). */
if (!dpy) {
/* Pure Wayland path (no XWayland display): skip X11/wrlib context setup
* and allocate workspace 0 directly. When XWayland is available (dpy is
* set, Path 1), fall through to the full X11 init so WINGs, RContext,
* GCs, and menus work correctly on the XWayland display. */
if (wm_backend->is_wayland && !dpy) {
WWorkspace *wspace = wmalloc(sizeof(WWorkspace));
wspace->name = wstrdup("Workspace 1");
wspace->clip = NULL;

View File

@ -177,8 +177,8 @@ static void getOffendingModifiers(void)
Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
};
if (!dpy)
return; /* Wayland: no modifier mapping to query */
if (!dpy || wm_backend->is_wayland)
return; /* Wayland: modifier mapping handled by wlroots/XKB */
nlock = wm_backend->keysym_to_keycode(WM_KEY_NUM_LOCK);
slock = wm_backend->keysym_to_keycode(WM_KEY_SCROLL_LOCK);
@ -474,8 +474,10 @@ void StartUp(Bool defaultScreenOnly)
/* handle X shutdowns and errors via backend */
wm_backend->register_error_handlers();
/* set hook for our event dispatcher in WINGs event dispatcher (X11 only) */
if (dpy)
/* set hook for our event dispatcher in WINGs event dispatcher (X11 only).
* Skip for Wayland: we run a wlroots event loop, not a WINGs/X11 loop.
* dpy may be set (XWayland bridge), but X11 event dispatch is not used. */
if (dpy && !wm_backend->is_wayland)
WMHookEventHandler(DispatchEvent);
/* initialize defaults stuff */
@ -550,7 +552,7 @@ void StartUp(Bool defaultScreenOnly)
if (dpy)
InitializeSwitchMenu();
/* initialize/restore state for the screens (X11 only for now) */
/* initialize/restore state for the screens */
if (dpy) {
for (j = 0; j < w_global.screen_count; j++) {
int lastDesktop;
@ -648,7 +650,7 @@ static void manageAllWindows(WScreen * scr, int crashRecovery)
WWindow *wwin;
if (!dpy)
return; /* Wayland: no pre-existing X11 clients to adopt */
return; /* no display — nothing to adopt */
wm_backend->server_grab();
XQueryTree(dpy, scr->root_win, &root, &parent, &children, &nchildren);

View File

@ -47,6 +47,12 @@ WTexSolid *wTextureMakeSolid(WScreen * scr, XColor * color)
texture->type = WTEX_SOLID;
texture->subtype = 0;
/* Phase 19: Wayland — skip X11 color allocation and GC creation */
if (!dpy) {
texture->normal = *color;
return texture;
}
XAllocColor(dpy, scr->w_colormap, color);
texture->normal = *color;
if (color->red == 0 && color->blue == 0 && color->green == 0) {
@ -127,9 +133,11 @@ void wTextureDestroy(WScreen * scr, WTexture * texture)
#define CANFREE(c) (c!=scr->black_pixel && c!=scr->white_pixel && c!=0)
switch (texture->any.type) {
case WTEX_SOLID:
XFreeGC(dpy, texture->solid.light_gc);
XFreeGC(dpy, texture->solid.dark_gc);
XFreeGC(dpy, texture->solid.dim_gc);
if (dpy) {
XFreeGC(dpy, texture->solid.light_gc);
XFreeGC(dpy, texture->solid.dark_gc);
XFreeGC(dpy, texture->solid.dim_gc);
}
if (CANFREE(texture->solid.light.pixel))
colors[count++] = texture->solid.light.pixel;
if (CANFREE(texture->solid.dim.pixel))
@ -160,7 +168,8 @@ void wTextureDestroy(WScreen * scr, WTexture * texture)
if (CANFREE(texture->any.color.pixel))
colors[count++] = texture->any.color.pixel;
if (count > 0) {
/* Phase 19: Wayland — skip X11 color/GC cleanup when no display */
if (dpy && count > 0) {
XErrorHandler oldhandler;
/* ignore error from buggy servers that don't know how
@ -171,7 +180,8 @@ void wTextureDestroy(WScreen * scr, WTexture * texture)
XSync(dpy, 0);
XSetErrorHandler(oldhandler);
}
XFreeGC(dpy, texture->any.gc);
if (dpy)
XFreeGC(dpy, texture->any.gc);
wfree(texture);
#undef CANFREE
}
@ -192,10 +202,12 @@ WTexGradient *wTextureMakeGradient(WScreen *scr, int style, const RColor *from,
texture->normal.green = (from->green + to->green) << 7;
texture->normal.blue = (from->blue + to->blue) << 7;
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
if (dpy) {
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
}
return texture;
}
@ -224,10 +236,12 @@ WTexIGradient *wTextureMakeIGradient(WScreen *scr, int thickness1, const RColor
texture->normal.green = (colors2[0].green + colors2[1].green) << 7;
texture->normal.blue = (colors2[0].blue + colors2[1].blue) << 7;
}
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
if (dpy) {
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
}
return texture;
}
@ -252,10 +266,12 @@ WTexMGradient *wTextureMakeMGradient(WScreen * scr, int style, RColor ** colors)
texture->colors = colors;
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
if (dpy) {
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
}
return texture;
}
@ -276,10 +292,12 @@ WTexPixmap *wTextureMakePixmap(WScreen *scr, int style, const char *pixmap_file,
texture->normal = *color;
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
if (dpy) {
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
}
texture->pixmap = image;
@ -309,10 +327,12 @@ WTexTGradient *wTextureMakeTGradient(WScreen *scr, int style, const RColor *from
texture->normal.green = (from->green + to->green) << 7;
texture->normal.blue = (from->blue + to->blue) << 7;
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
if (dpy) {
XAllocColor(dpy, scr->w_colormap, &texture->normal);
gcv.background = gcv.foreground = texture->normal.pixel;
gcv.graphics_exposures = False;
texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
}
texture->pixmap = image;
@ -535,6 +555,10 @@ void wDrawBevel(Drawable d, unsigned width, unsigned height, WTexSolid * texture
if (relief == WREL_FLAT)
return;
/* Phase 19: Wayland — no X11 display, skip bevel drawing */
if (!dpy)
return;
light = texture->light_gc;
dim = texture->dim_gc;
dark = texture->dark_gc;