wings: phase ζ — formal W_PlatformBackend vtable for WMOpenScreen

Replaces the inline if-Wayland-else-X11 dispatch in WMOpenScreen with
a proper vtable.  Each platform-open backend now provides one
W_PlatformBackend instance:

  W_PlatformBackendX11       — widgets_x11.c
  W_PlatformBackendWLClient  — wclient_wl.c

WMOpenScreen walks an ordered registry of weak references to these
instances:

  static const W_PlatformBackend *const wm_platform_backends[] = {
      &W_PlatformBackendWLClient,
      &W_PlatformBackendX11,
      NULL
  };

It first asks each backend's .detect() whether it wants the supplied
display string, and calls .open() on the first to claim it.  If no
backend claims, it falls back to trying each .open() in order.

Weak references mean a build that excludes a backend (e.g. a future
--disable-x11-backend) link cleanly with the symbol resolving to
NULL — WMOpenScreen just skips it.

The detection logic moves out of shared widgets.c entirely:
  x11_detect: explicit non-wayland display string OR $DISPLAY set.
  wlclient_detect: 'wayland' prefix, WMAKER_USE_WAYLAND, or
                   $WAYLAND_DISPLAY set + $DISPLAY unset.

Also replaces WMCreateScreenWithRContext's getenv("WMAKER_USE_WAYLAND")
with an explicit W_SetNextScreenKind(WM_SCREEN_KIND_WAYLAND) flag the
platform backend sets before calling.  No env detection in shared code.

Updated wmaker compositor (src/backend/wayland/wl_backend.c) to call
W_SetNextScreenKind(WM_SCREEN_KIND_WAYLAND) before its own
WMCreateScreenWithRContext call.

Verified: wmaker still starts in compositor mode, version string OK,
build clean, vtable dispatch invokes the right backend.
This commit is contained in:
Window Maker 2026-05-24 11:49:34 +02:00
parent fb817a7b27
commit 9a02eb658e
5 changed files with 190 additions and 53 deletions

View File

@ -746,6 +746,46 @@ void W_InitIM(WMScreen *scr);
WMScreen *W_X11OpenDisplay(const char *display);
void W_X11PlatformInit(W_Screen *scrPtr);
/*
* Platform backend vtable (phase ζ).
*
* Each platform-open backend (X11 in widgets_x11.c, Wayland-client in
* wclient_wl.c) provides one instance of W_PlatformBackend. WMOpenScreen
* walks an ordered list of backends, asking each .detect() whether it
* wants to handle the requested display, and calls .open() on the first
* that does.
*
* Backends are referenced via weak symbols from widgets.c so excluded
* backends (e.g. --disable-x11-backend) link cleanly with the symbol
* resolving to NULL.
*/
typedef struct W_PlatformBackend {
const char *name; /* "x11" / "wayland-client" */
/* Return 1 if this backend should handle the given display string
* (and current env state). Return 0 otherwise. WMOpenScreen tries
* each backend in order; the first to return 1 wins. */
int (*detect)(const char *display);
/* Open the platform-native display and return a populated WMScreen.
* Returns NULL on failure (e.g. compositor refused the connection).
* The caller may then try the next backend in the list. */
WMScreen *(*open)(const char *display);
} W_PlatformBackend;
/*
* WMScreenKind — selects which widget backends WMCreateScreenWithRContext
* installs on the next screen creation. Set by the platform backend
* before calling WMCreateScreenWithRContext. This replaces the prior
* WMAKER_USE_WAYLAND env-var dispatch.
*/
typedef enum {
WM_SCREEN_KIND_X11 = 0,
WM_SCREEN_KIND_WAYLAND,
} WMScreenKind;
void W_SetNextScreenKind(WMScreenKind kind);
/*
* W_NativeInternAtom — wraps XInternAtom for the X11 backend so that
* shared code (e.g. wtextfield.c selection request handler) can intern

View File

@ -1020,6 +1020,66 @@ W_WLClientShutdown(struct W_Screen *scrPtr)
wfree(ctx);
}
/* ------------------------------------------------------------------ */
/* W_PlatformBackend instance — registered in widgets.c via weak ref. */
/* ------------------------------------------------------------------ */
/*
* wlclient_open — adapter that matches the W_PlatformBackend.open
* signature. Creates a Wayland-mode WMScreen and runs W_WLClientInit
* on it. Returns NULL on failure so WMOpenScreen can try the next
* backend.
*/
static WMScreen *wlclient_open(const char *display)
{
/* Tell WMCreateScreenWithRContext to install the Wayland WINGs
* backends (font/event/render/view/color all -> _WL variants). */
W_SetNextScreenKind(WM_SCREEN_KIND_WAYLAND);
WMScreen *scr = WMCreateScreenWithRContext(NULL, 0, NULL);
if (!scr) return NULL;
if (!W_WLClientInit(scr, display)) {
/* TODO: WMCloseScreen(scr) — currently leaks the failed screen. */
return NULL;
}
scr->aflags.hasAppIcon = 0;
scr->aflags.simpleApplication = 1;
return scr;
}
/*
* wlclient_detect — returns 1 when this backend should handle the
* requested display. The rules favour explicit configuration first,
* then fall back to standard Wayland-vs-X autodetection (matching
* GTK / Qt behaviour).
*/
static int wlclient_detect(const char *display)
{
/* Explicit "wayland" or "wayland:NAME". */
if (display && strncmp(display, "wayland", 7) == 0)
return 1;
/* Explicit X display string overrides everything else. */
if (display && display[0])
return 0;
/* WMAKER_USE_WAYLAND=1 env override. */
const char *force = getenv("WMAKER_USE_WAYLAND");
if (force && force[0] && force[0] != '0')
return 1;
/* Standard autodetect: WAYLAND_DISPLAY set, DISPLAY unset. */
if (getenv("WAYLAND_DISPLAY") && !getenv("DISPLAY"))
return 1;
return 0;
}
const W_PlatformBackend W_PlatformBackendWLClient = {
.name = "wayland-client",
.detect = wlclient_detect,
.open = wlclient_open,
};
#else /* !USE_WAYLAND_CLIENT */
#include "WINGsP.h"

View File

@ -518,68 +518,52 @@ static Bool loadPixmaps(WMScreen * scr)
}
/*
* Decide whether to attempt a native Wayland connection.
* Platform backends (phase ζ). Each is a W_PlatformBackend instance
* defined in its own backend file:
*
* The rules favour explicit configuration but fall back to the standard
* Wayland-vs-X autodetection used by toolkits like GTK/Qt:
* W_PlatformBackendX11 — widgets_x11.c
* W_PlatformBackendWLClient — wclient_wl.c (only when --enable-wayland-client)
*
* 1. If the application passed a display string starting with
* "wayland", use that (strip the prefix; the rest is the
* WAYLAND_DISPLAY socket name).
* 2. If WMAKER_USE_WAYLAND=1 is set, force Wayland.
* 3. Otherwise, if WAYLAND_DISPLAY is set and DISPLAY is not, use
* Wayland.
* 4. Otherwise, X11.
* Referenced as weak symbols here so a build that excludes a backend
* (e.g. --disable-x11-backend) links cleanly with the symbol resolving
* to NULL. WMOpenScreen iterates the registry below. Order matters:
* the first .detect() to return non-zero wins, so the more specific
* backend (Wayland-client) is listed before the X11 fallback.
*/
static const char *
wings_detect_wayland_display(const char *display)
{
if (display && strncmp(display, "wayland", 7) == 0) {
const char *rest = display + 7;
if (*rest == ':') return rest + 1; /* "wayland:NAME" */
if (*rest == 0) return NULL; /* "wayland"; default */
return display; /* something exotic — pass through */
}
if (display)
return NULL; /* explicit X display */
extern const W_PlatformBackend W_PlatformBackendWLClient __attribute__((weak));
extern const W_PlatformBackend W_PlatformBackendX11 __attribute__((weak));
const char *force = getenv("WMAKER_USE_WAYLAND");
if (force && force[0] && force[0] != '0')
return getenv("WAYLAND_DISPLAY");
if (getenv("WAYLAND_DISPLAY") && !getenv("DISPLAY"))
return getenv("WAYLAND_DISPLAY");
return NULL;
}
static const W_PlatformBackend *const wm_platform_backends[] = {
&W_PlatformBackendWLClient,
&W_PlatformBackendX11,
NULL
};
WMScreen *WMOpenScreen(const char *display)
{
const char *wl_disp = wings_detect_wayland_display(display);
#ifdef USE_WAYLAND_CLIENT
if (wl_disp != NULL || (display == NULL && getenv("WAYLAND_DISPLAY"))) {
/* Tell WMCreateScreenWithRContext to install Wayland backends. */
setenv("WMAKER_USE_WAYLAND", "1", 0);
WMScreen *scr = WMCreateScreenWithRContext(NULL, 0, NULL);
if (!scr) {
wwarning(_("WINGs: WMCreateScreenWithRContext failed in Wayland mode"));
/* Fall through to X11. */
} else if (W_WLClientInit(scr, wl_disp)) {
scr->aflags.hasAppIcon = 0;
scr->aflags.simpleApplication = 1;
return scr;
} else {
wwarning(_("WINGs: Wayland connection failed; falling back to X11"));
/* TODO: WMCloseScreen(scr) — currently leaks the failed screen. */
/* First pass: ask each available backend if this display is theirs. */
for (int i = 0; wm_platform_backends[i]; i++) {
const W_PlatformBackend *b = wm_platform_backends[i];
if (!b || !b->detect || !b->open)
continue; /* weak-undefined: backend not linked in */
if (b->detect(display)) {
WMScreen *scr = b->open(display);
if (scr) return scr;
wwarning(_("WINGs: %s backend failed to open display, trying next"),
b->name);
}
}
#else
(void)wl_disp;
#endif
/* X11 path lives in widgets_x11.c so this file stays Xlib-free. */
return W_X11OpenDisplay(display);
/* Second pass: nobody claimed it explicitly — try each in order. */
for (int i = 0; wm_platform_backends[i]; i++) {
const W_PlatformBackend *b = wm_platform_backends[i];
if (!b || !b->open) continue;
WMScreen *scr = b->open(display);
if (scr) return scr;
}
wwarning(_("WINGs: no platform backend could open the display"));
return NULL;
}
WMScreen *WMCreateSimpleApplicationScreen(Display * display)
@ -599,11 +583,31 @@ WMScreen *WMCreateScreen(Display * display, int screen)
return WMCreateScreenWithRContext(display, screen, RCreateContext(display, screen, NULL));
}
/*
* W_screen_next_kind — communicates which widget backends the next
* WMCreateScreenWithRContext call should install. Set by the platform
* backend (e.g. wlclient_open in wclient_wl.c) before it calls
* WMCreateScreenWithRContext. Default is X11.
*
* Replaces the previous WMAKER_USE_WAYLAND env-var dispatch — env
* detection belongs in the platform backend's .detect() function, not
* in shared code.
*/
static WMScreenKind wm_screen_next_kind = WM_SCREEN_KIND_X11;
void W_SetNextScreenKind(WMScreenKind kind)
{
wm_screen_next_kind = kind;
}
WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * context)
{
W_Screen *scrPtr;
static int initialized = 0;
int wayland = (getenv("WMAKER_USE_WAYLAND") != NULL);
int wayland = (wm_screen_next_kind == WM_SCREEN_KIND_WAYLAND);
/* Reset for the next call so leakage between WMOpenScreen invocations
* can't accidentally pick the wrong backend set. */
wm_screen_next_kind = WM_SCREEN_KIND_X11;
if (!initialized) {
initialized = 1;

View File

@ -231,6 +231,35 @@ WMScreen *W_X11OpenDisplay(const char *display)
return WMCreateSimpleApplicationScreen(dpy);
}
/*
* W_PlatformBackendX11.detect — return 1 if this is an X11 connection.
*
* Rules:
* - explicit display string that does NOT start with "wayland" → X11.
* - display==NULL but DISPLAY env set → X11.
* - everything else → 0 (let the next backend try).
*
* The Wayland-client backend's detect() is consulted FIRST (registered
* earlier in W_PlatformBackends), so the typical "WAYLAND_DISPLAY set,
* DISPLAY unset" case picks Wayland. X11 is the fallback.
*/
static int x11_detect(const char *display)
{
if (display && strncmp(display, "wayland", 7) == 0)
return 0;
if (display && display[0])
return 1; /* explicit X display */
if (getenv("DISPLAY"))
return 1; /* legacy: DISPLAY set */
return 0; /* no display info; not us */
}
const W_PlatformBackend W_PlatformBackendX11 = {
.name = "x11",
.detect = x11_detect,
.open = W_X11OpenDisplay,
};
/*
* W_NativeInternAtom — atom-intern wrapper used by shared code that
* needs an Atom value at run-time (e.g. wtextfield.c's selection

View File

@ -3954,6 +3954,10 @@ wl_screen_init_display(WScreen *scr, int screen_number)
scr->w_depth = 32;
scr->rcontext = RCreateContextWayland(scr->scr_width, scr->scr_height, NULL);
/* WINGs widget backends are selected via W_SetNextScreenKind — the
* compositor uses the Wayland-mode set even though wmaker itself is
* the compositor (rendering goes through pixman → wlroots scenes). */
W_SetNextScreenKind(WM_SCREEN_KIND_WAYLAND);
scr->wmscreen = WMCreateScreenWithRContext(NULL, 0, scr->rcontext);
if (!scr->wmscreen) {
wfatal(_("could not initialize WINGs widget set"));