wayland: Phase 21 — lazy XWayland + lock file cleanup on shutdown

Switch wlr_xwayland_create from lazy=false to lazy=true.  With eager
start the Xwayland process forks before the compositor's event loop is
running; Xwayland's Wayland client tries to negotiate globals immediately,
gets no response, and closes its connection ("Broken pipe" / "xcb connect
failed: 1").  Lazy mode defers the fork until the first X11 client
connects, by which time the event loop is fully running.

The pre-allocated display number is still available immediately so DISPLAY
is set for child processes regardless of whether Xwayland has started.

On shutdown, after SIGKILL-ing the Xwayland process, explicitly remove
/tmp/.X{n}-lock and /tmp/.X11-unix/X{n}.  Without this, if the dead
process's PID is reused by another process before the next wmaker run,
wlroots' stale-lock detection (kill(old_pid,0)) wrongly reports the
display as in-use and the next run fails to claim the same display number.

Smoke test updated: check for "XWayland DISPLAY=.*lazy" instead of the
old "XWayland started DISPLAY=" pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-30 13:05:40 +02:00
parent aa294ed13c
commit 632c093355
2 changed files with 34 additions and 14 deletions

View File

@ -1301,11 +1301,16 @@ wl_display_open(const char *name)
wl_signal_add(&wl_state.xdg_shell->events.new_surface,
&wl_state.xdg_new_surface_listener);
/* Phase 17: start XWayland so X11 clients (e.g. GNUstep apps) can run
* under the Wayland compositor. The third argument (lazy=false) starts
* the Xwayland process eagerly so DISPLAY is available immediately. */
/* Phase 17: register XWayland for X11 client support.
* Using lazy=true: the Xwayland process does NOT start at compositor
* boot. It starts automatically on the first X11 client connect.
* This avoids a startup race where Xwayland's Wayland client tries to
* negotiate globals before the compositor's event loop is running,
* causing "Broken pipe" and "xcb connect failed" on the first frame.
* The display number is pre-allocated immediately so DISPLAY can be
* set for child processes even before any X11 client connects. */
wl_state.xwayland = wlr_xwayland_create(wl_state.display,
wl_state.compositor, false);
wl_state.compositor, true);
if (wl_state.xwayland) {
wl_state.xwayland_new_surface_listener.notify = handle_xwayland_new_surface;
wl_signal_add(&wl_state.xwayland->events.new_surface,
@ -1313,7 +1318,7 @@ wl_display_open(const char *name)
wlr_xwayland_set_seat(wl_state.xwayland, wl_state.seat);
if (wl_state.xwayland->display_name) {
setenv("DISPLAY", wl_state.xwayland->display_name, 1);
wwarning("wl_backend: XWayland started DISPLAY=%s",
wwarning("wl_backend: XWayland DISPLAY=%s (lazy: starts on first X11 client)",
wl_state.xwayland->display_name);
} else {
wwarning("wl_backend: XWayland created (display_name pending)");
@ -1405,6 +1410,7 @@ wl_display_close(void)
* still valid because the event loop is stopped and the socket closure
* has not been dispatched, so wlroots' wl_client_destroy() succeeds. */
if (wl_state.xwayland->server) {
int xdisp = wl_state.xwayland->server->display;
if (wl_state.xwayland->server->pid > 0)
kill(wl_state.xwayland->server->pid, SIGKILL);
/* wlroots 0.15 bug: after any Wayland client has connected and
@ -1417,6 +1423,17 @@ wl_display_close(void)
* clean it up; if it is stale, it has already been removed from
* the display's client list and nothing more is needed. */
wl_state.xwayland->server->client = NULL;
/* Clean up X lock file and Unix socket left by SIGKILL'd Xwayland.
* Without this, the next run may find a stale lock file whose PID
* has been reused by an unrelated process, causing wlroots to
* refuse to reclaim the display number → "xcb connect failed". */
{
char path[64];
snprintf(path, sizeof(path), "/tmp/.X%d-lock", xdisp);
unlink(path);
snprintf(path, sizeof(path), "/tmp/.X11-unix/X%d", xdisp);
unlink(path);
}
}
wl_state.xwayland = NULL;
unsetenv("DISPLAY");

View File

@ -118,10 +118,11 @@
# so handle_backend_new_output finds a valid scene when it fires.
# Verified by "scene graph created" + "scene output created" +
# "headless compositor mode" logs.
# 39. (Phase 17) XWayland started: wlr_xwayland_create() is called in
# wl_display_open(); compositor logs "XWayland started DISPLAY=:<n>"
# so X11 clients (GNUstep apps) can connect. DISPLAY env var is set.
# Verified by "XWayland started DISPLAY=" log.
# 39. (Phase 17) XWayland display allocated (lazy start): wlr_xwayland_create()
# is called with lazy=true; Xwayland only starts on first X11 client.
# Compositor logs "XWayland DISPLAY=:<n> (lazy: starts on first X11 client)".
# DISPLAY env var is set from the pre-allocated display number.
# Verified by "XWayland DISPLAY=.*lazy" log.
# 38. (Phase 11) noclip/nodock set in wl_screen_create_internals: wDockCreate
# requires X11 rendering resources unavailable in Wayland mode; disabling
# the dock/clip prevents crashes when wWorkspaceNew is called to create
@ -267,14 +268,16 @@ grep -q 'dock/clip disabled in Wayland mode' /tmp/wl-smoke-stderr \
|| die "compositor did not log dock/clip disabled (noclip/nodock not set in wl_screen_create_internals)"
echo " nodock check: dock/clip disabled in Wayland mode (noclip=1 nodock=1)"
# -- 4g. Phase 17: XWayland started and DISPLAY set ----------------------
# -- 4g. Phase 17: XWayland display number allocated (lazy start) --------
#
# wl_display_open() logs "XWayland started DISPLAY=:<n>" when
# wlr_xwayland_create() succeeds; verify the log line is present.
# wl_display_open() logs "XWayland DISPLAY=:<n> (lazy: starts on first
# X11 client)" when wlr_xwayland_create() succeeds with lazy=true.
# The Xwayland process does not start until the first X11 client connects,
# avoiding the startup race that caused "Broken pipe" with lazy=false.
# (Falls back gracefully if XWayland is unavailable.)
if grep -q 'XWayland started DISPLAY=' /tmp/wl-smoke-stderr; then
if grep -q 'XWayland DISPLAY=.*lazy' /tmp/wl-smoke-stderr; then
XDISP=$(grep -o 'DISPLAY=:[0-9]*' /tmp/wl-smoke-stderr | head -1 | sed 's/DISPLAY=//')
echo " xwayland check: XWayland started DISPLAY=$XDISP"
echo " xwayland check: XWayland DISPLAY=$XDISP (lazy start)"
elif grep -q 'XWayland unavailable' /tmp/wl-smoke-stderr; then
echo " xwayland check: XWayland unavailable (skipped)"
else