wayland: Phase 33 — event_loop_run/atom_intern vtable; wmaker starts and exits cleanly under Wayland

Add display_default_screen, atom_intern, and event_loop_run vtable slots.
Guard all X11-only paths in startup.c/screen.c/shutdown.c/event.c/xdnd.c
with if (!dpy) so the Wayland path reaches wl_display_run() and exits
cleanly (exit 0) on SIGTERM.  wl-smoke-test.sh now PASSES.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-27 23:02:26 +01:00
parent d07362b90e
commit f7dfc50f26
10 changed files with 195 additions and 35 deletions

View File

@ -72,6 +72,29 @@ typedef struct WMBackend {
*/
int (*display_screen_count)(void);
/*
* Return the default screen index for the display connection.
* X11: DefaultScreen(dpy). Wayland: 0.
* Called from StartUp() to pass to wScreenInit().
*/
int (*display_default_screen)(void);
/*
* Intern an atom by name.
* X11: XInternAtom(dpy, name, False) -- returns Atom (unsigned long).
* Wayland: returns 0 (atoms have no meaning in native Wayland).
* Called from StartUp() to populate w_global.atom.*.
*/
unsigned long (*atom_intern)(const char *name);
/*
* Run the backend's main event loop until shutdown.
* X11: no-op; EventLoop() drives the WINGs/X11 event pump.
* Wayland: wl_display_run(display) -- blocks until wl_display_terminate().
* Called from EventLoop() when dpy == NULL (Wayland path).
*/
void (*event_loop_run)(void);
/* ------------------------------------------------------------------ */
/* Screen lifecycle (Phase 2) */
/* ------------------------------------------------------------------ */

View File

@ -20,6 +20,7 @@
#include <wlr/backend.h>
#include <wlr/backend/headless.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/log.h>
#include "../../WindowMaker.h"
@ -36,6 +37,7 @@ struct wl_compositor_state {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_output *output; /* headless output (screen 0) */
int started; /* non-zero after successful display_open */
};
@ -87,6 +89,9 @@ wl_display_open(const char *name)
return NULL;
}
/* Add a single 1920×1080 headless output (screen 0). */
wl_state.output = wlr_headless_add_output(wl_state.backend, 1920, 1080);
wl_state.started = 1;
return NULL; /* no X11 Display* in the Wayland path */
}
@ -114,11 +119,43 @@ wl_display_screen_count(void)
return wl_state.started ? 1 : 0;
}
static int
wl_display_default_screen(void)
{
return 0;
}
static unsigned long
wl_atom_intern(const char *name)
{
(void)name;
return 0; /* atoms have no meaning in native Wayland */
}
static void
wl_event_loop_run(void)
{
if (wl_state.display)
wl_display_run(wl_state.display);
}
/* ------------------------------------------------------------------ */
/* Screen open/close */
/* ------------------------------------------------------------------ */
static void wl_screen_open(WScreen *scr) { (void)scr; }
static void
wl_screen_open(WScreen *scr)
{
/* Set geometry from the headless output created in display_open(). */
scr->scr_width = wl_state.output ?
(int)wl_state.output->width : 1920;
scr->scr_height = wl_state.output ?
(int)wl_state.output->height : 1080;
scr->depth = 24;
scr->root_win = WNATIVE_NONE;
scr->colormap = 0;
}
static void wl_screen_close(WScreen *scr) { (void)scr; }
/* ------------------------------------------------------------------ */
@ -468,8 +505,10 @@ static void wl_fake_leader_destroy(WNativeWindow win) { (void)win; }
/* Screen internals */
/* ------------------------------------------------------------------ */
static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 0; }
static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 0; }
/* Wayland compositor is always the sole WM — succeed unconditionally. */
static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; }
/* No X11 event mask selection needed; succeed. */
static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 1; }
static void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; }
static void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; }
static void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; }
@ -530,9 +569,12 @@ static void wl_register_error_handlers(void) {}
/* ------------------------------------------------------------------ */
const WMBackend wl_backend = {
.display_open = wl_display_open,
.display_close = wl_display_close,
.display_screen_count = wl_display_screen_count,
.display_open = wl_display_open,
.display_close = wl_display_close,
.display_screen_count = wl_display_screen_count,
.display_default_screen = wl_display_default_screen,
.atom_intern = wl_atom_intern,
.event_loop_run = wl_event_loop_run,
.screen_open = wl_screen_open,
.screen_close = wl_screen_close,

View File

@ -102,6 +102,24 @@ x11_display_screen_count(void)
return dpy ? ScreenCount(dpy) : 0;
}
static int
x11_display_default_screen(void)
{
return dpy ? DefaultScreen(dpy) : 0;
}
static unsigned long
x11_atom_intern(const char *name)
{
return (unsigned long)XInternAtom(dpy, name, False);
}
static void
x11_event_loop_run(void)
{
/* no-op: EventLoop() in event.c drives the X11/WINGs event pump */
}
/* -------------------------------------------------------------------- */
/* Screen lifecycle */
/* -------------------------------------------------------------------- */
@ -2358,9 +2376,12 @@ x11_register_error_handlers(void)
/* -------------------------------------------------------------------- */
const WMBackend x11_backend = {
.display_open = x11_display_open,
.display_close = x11_display_close,
.display_screen_count = x11_display_screen_count,
.display_open = x11_display_open,
.display_close = x11_display_close,
.display_screen_count = x11_display_screen_count,
.display_default_screen = x11_display_default_screen,
.atom_intern = x11_atom_intern,
.event_loop_run = x11_event_loop_run,
.screen_open = x11_screen_open,
.screen_close = x11_screen_close,
.frame_create_toplevel = x11_frame_create_toplevel,

View File

@ -366,7 +366,15 @@ noreturn void EventLoop(void)
struct timeval time;
fd_set rfds;
int retVal = 0;
#endif
/* Wayland path: delegate to the backend's event loop (wl_display_run). */
if (!dpy) {
wm_backend->event_loop_run();
Exit(0);
}
#ifdef HAVE_INOTIFY
if (w_global.inotify.fd_event_queue < 0 || w_global.inotify.wd_defaults < 0)
retVal = -1;
#endif

View File

@ -198,6 +198,9 @@ static int initWVisualID(const char *user_str)
noreturn void Exit(int status)
{
if (wm_backend)
wm_backend->display_close();
if (dpy)
XCloseDisplay(dpy);

View File

@ -158,6 +158,10 @@ 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. */
if (!dpy)
goto screen_done;
/* screen descriptor for raster graphic library */
rattr.flags = RC_RenderMode | RC_ColorsPerChannel | RC_StandardColormap;
rattr.render_mode = wPreferences.no_dithering ? RBestMatchRendering : RDitheredRendering;
@ -265,6 +269,7 @@ WScreen *wScreenInit(int screen_number)
wScreenUpdateUsableArea(scr);
screen_done:
return scr;
}

View File

@ -73,7 +73,8 @@ void Shutdown(WShutdownMode mode)
if (scr->helper_pid)
kill(scr->helper_pid, SIGKILL);
wScreenSaveState(scr);
if (dpy) /* Wayland: skip X11 session state save */
wScreenSaveState(scr);
if (mode == WSKillMode)
wipeDesktop(scr);
@ -99,7 +100,8 @@ void Shutdown(WShutdownMode mode)
if (scr) {
if (scr->helper_pid)
kill(scr->helper_pid, SIGKILL);
wScreenSaveState(scr);
if (dpy) /* Wayland: skip X11 session state save */
wScreenSaveState(scr);
RestoreDesktop(scr);
}
}
@ -157,6 +159,9 @@ static void restoreWindows(WMBag * bag, WMBagIterator iter)
*/
void RestoreDesktop(WScreen * scr)
{
if (!dpy)
return; /* Wayland: no X11 clients to unmanage */
if (scr->helper_pid > 0) {
kill(scr->helper_pid, SIGTERM);
scr->helper_pid = 0;
@ -192,6 +197,9 @@ static void wipeDesktop(WScreen * scr)
{
WWindow *wwin;
if (!dpy)
return; /* Wayland: no X11 clients to wipe */
wwin = scr->focused_window;
while (wwin) {
if (wwin->protocols.DELETE_WINDOW)

View File

@ -177,6 +177,9 @@ static void getOffendingModifiers(void)
Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
};
if (!dpy)
return; /* Wayland: no modifier mapping to query */
nlock = wm_backend->keysym_to_keycode(WM_KEY_NUM_LOCK);
slock = wm_backend->keysym_to_keycode(WM_KEY_SCROLL_LOCK);
@ -376,18 +379,11 @@ void StartUp(Bool defaultScreenOnly)
w_global.context.app_win = XUniqueContext();
w_global.context.stack = XUniqueContext();
/* _XA_VERSION = XInternAtom(dpy, "VERSION", False); */
#ifdef HAVE_XINTERNATOMS
XInternAtoms(dpy, atomNames, wlengthof(atomNames), False, atom);
#else
{
int i;
for (i = 0; i < wlengthof(atomNames); i++)
atom[i] = XInternAtom(dpy, atomNames[i], False);
int ai;
for (ai = 0; ai < (int)wlengthof(atomNames); ai++)
atom[ai] = (Atom)wm_backend->atom_intern(atomNames[ai]);
}
#endif
w_global.atom.wm.state = atom[0];
w_global.atom.wm.change_state = atom[1];
@ -478,8 +474,9 @@ void StartUp(Bool defaultScreenOnly)
/* handle X shutdowns and errors via backend */
wm_backend->register_error_handlers();
/* set hook for out event dispatcher in WINGs event dispatcher */
WMHookEventHandler(DispatchEvent);
/* set hook for our event dispatcher in WINGs event dispatcher (X11 only) */
if (dpy)
WMHookEventHandler(DispatchEvent);
/* initialize defaults stuff */
w_global.domain.wmaker = wDefaultsInitDomain("WindowMaker", True);
@ -535,7 +532,7 @@ void StartUp(Bool defaultScreenOnly)
/* manage the screens */
for (j = 0; j < max; j++) {
if (defaultScreenOnly || max == 1) {
wScreen[w_global.screen_count] = wScreenInit(DefaultScreen(dpy));
wScreen[w_global.screen_count] = wScreenInit(wm_backend->display_default_screen());
if (!wScreen[w_global.screen_count]) {
wfatal(_("it seems that there is already a window manager running"));
Exit(1);
@ -550,9 +547,11 @@ void StartUp(Bool defaultScreenOnly)
w_global.screen_count++;
}
InitializeSwitchMenu();
if (dpy)
InitializeSwitchMenu();
/* initialize/restore state for the screens */
/* initialize/restore state for the screens (X11 only for now) */
if (dpy) {
for (j = 0; j < w_global.screen_count; j++) {
int lastDesktop;
@ -605,6 +604,7 @@ void StartUp(Bool defaultScreenOnly)
else
wSessionRestoreLastWorkspace(wScreen[j]);
}
} /* end if (dpy) */
if (w_global.screen_count == 0) {
wfatal(_("could not manage any screen"));
@ -612,8 +612,8 @@ void StartUp(Bool defaultScreenOnly)
}
#ifndef HAVE_INOTIFY
/* setup defaults file polling */
if (!wPreferences.flags.noupdates)
/* setup defaults file polling (X11/WINGs timer only) */
if (dpy && !wPreferences.flags.noupdates)
WMAddTimerHandler(3000, wDefaultsCheckDomains, NULL);
#endif
@ -647,7 +647,10 @@ static void manageAllWindows(WScreen * scr, int crashRecovery)
unsigned int i, j;
WWindow *wwin;
XGrabServer(dpy);
if (!dpy)
return; /* Wayland: no pre-existing X11 clients to adopt */
wm_backend->server_grab();
XQueryTree(dpy, scr->root_win, &root, &parent, &children, &nchildren);
scr->flags.startup = 1;
@ -709,7 +712,7 @@ static void manageAllWindows(WScreen * scr, int crashRecovery)
}
}
}
XUngrabServer(dpy);
wm_backend->server_ungrab();
/* hide apps */
wwin = scr->focused_window;

View File

@ -49,6 +49,9 @@ static Atom selected_typelist;
void wXDNDInitializeAtoms(void)
{
if (!dpy)
return; /* Wayland: XDND is X11-only */
_XA_XdndAware = XInternAtom(dpy, "XdndAware", False);
_XA_XdndEnter = XInternAtom(dpy, "XdndEnter", False);
_XA_XdndLeave = XInternAtom(dpy, "XdndLeave", False);

View File

@ -7,11 +7,10 @@
# Currently verifies:
# 1. wmaker was built with Wayland backend support (symbol present)
# 2. wmaker links against wlroots
# 3. wmaker starts with WMAKER_USE_WAYLAND=1 and reaches the event loop
# without crashing (verified by sending SIGTERM and checking exit status)
#
# This test grows as wl_backend vtable slots are implemented.
# When the compositor can open a headless display, tests for window
# management, EWMH properties, etc. will be added here in the same
# style as tests/x11-smoke-test.sh.
#
# Exit 0 = PASS, non-zero = FAIL.
@ -21,6 +20,13 @@ set -e
WMAKER=src/.libs/wmaker
[ -x "$WMAKER" ] || WMAKER=src/wmaker
WM_PID=""
cleanup() {
[ -n "$WM_PID" ] && kill "$WM_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM
die() {
echo "FAIL: $*" >&2
exit 1
@ -36,6 +42,44 @@ nm "$WMAKER" 2>/dev/null | grep -q 'wl_backend' \
ldd "$WMAKER" 2>/dev/null | grep -qi 'wlroots\|libwlr' \
|| die "wmaker does not link against wlroots"
# -- Done (headless compositor tests to be added as vtable is implemented) --
echo "PASS (wl_backend symbol + wlroots link verified; full headless tests pending)"
# -- 3. Start wmaker with Wayland backend; verify it reaches event loop ---
#
# We use --no-dock --no-clip --no-autolaunch --static to avoid config
# file writes and autolaunch; --dont-restore skips session state reads.
# WMAKER_USE_WAYLAND=1 selects wl_backend.
#
# The test waits for the process to be alive (in the event loop) then
# sends SIGTERM and checks for clean exit.
WMAKER_USE_WAYLAND=1 src/wmaker \
--no-dock --no-clip --no-autolaunch \
--dont-restore --static \
--for-real &
WM_PID=$!
# Wait up to 5s for wmaker to reach the event loop (stay alive)
i=0
while [ $i -lt 50 ]; do
kill -0 "$WM_PID" 2>/dev/null || break
sleep 0.1
i=$(( i + 1 ))
done
kill -0 "$WM_PID" 2>/dev/null \
|| die "wmaker exited prematurely before SIGTERM (crashed during startup)"
# Send SIGTERM and wait for clean exit
kill -TERM "$WM_PID" 2>/dev/null
WM_EXIT=0
wait "$WM_PID" 2>/dev/null || WM_EXIT=$?
WM_PID=""
# SIGTERM causes exit(0) via the Shutdown(WSExitMode) path;
# shell reports signal-terminated processes as 128+signal, but
# wmaker catches SIGTERM and calls Exit(0) so we expect 0.
[ "$WM_EXIT" -eq 0 ] \
|| die "wmaker exited with status $WM_EXIT after SIGTERM (expected 0)"
# -- Done -----------------------------------------------------------------
echo "PASS (wl_backend compositor starts and exits cleanly)"
exit 0