wayland: XWayland bridge — block on ready signal, return Display* for full WM UI

wl_display_open now pumps the Wayland event loop until XWayland fires its
"ready" signal, then calls XOpenDisplay() and returns the Display* to the
core WM.  This makes dpy non-NULL so screen.c takes the full X11 code path:
RCreateContext, WINGs screen, workspaces, root menus, and EWMH hints all
initialise via the WM's own XWayland connection.

- wl_handle_x11_events: new fd watcher registered on ConnectionNumber(dpy)
  so WINGs menus, dialogs, and keyboard events are dispatched during
  wl_display_run() via XPending/XNextEvent/WMHandleEvent
- wl_screen_open: sets root_win/depth/colormap from DefaultRootWindow(dpy)
  when bridge is active so RCreateContext gets valid X11 screen values
- wl_screen_create_internals: creates a 1x1 info_window on XWayland for
  _NET_SUPPORTING_WM_CHECK and clipboard selection ownership
- handle_xwayland_map: guard against w_global.screen_count==0 (fires before
  StartUp() in edge cases) to avoid assert in wScreenWithNumber
- wl_display_close: removes fd watcher and calls XCloseDisplay before
  killing XWayland to avoid stale fd events

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-05-11 06:52:29 +02:00
parent 398d467880
commit 2fc7e45ffa
1 changed files with 96 additions and 8 deletions

View File

@ -307,6 +307,9 @@ struct wl_compositor_state {
struct wl_listener xwayland_new_surface_listener;
struct wl_listener xwayland_ready_listener;
struct wl_listener xwayland_server_destroy_listener;
bool xwayland_ready; /* set by handle_xwayland_ready */
Display *x_display; /* WM's Xlib connection to own XWayland */
struct wl_event_source *x11_event_source; /* wl_event_loop fd watcher for x_display */
/* Phase 20: real input devices from nested backend + their listeners */
struct wlr_input_device *real_keyboard_dev;
struct wlr_input_device *real_pointer_dev;
@ -1063,7 +1066,10 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
}
/* Pure Wayland mode: manage as WM client. */
/* Manage as WM client. Guard: wScreenWithNumber asserts screen_count > 0,
* so skip if StartUp() hasn't initialised the screen yet. */
if (w_global.screen_count == 0)
return;
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
@ -1189,13 +1195,24 @@ handle_xwayland_ready(struct wl_listener *listener, void *data)
if (!wl_state.xwayland || !wl_state.xwayland->display_name) {
wwarning("wl_backend: XWayland ready but display_name unavailable");
wl_state.xwayland_ready = true;
return;
}
/* Advertise the XWayland display so X11 client apps can connect. */
setenv("DISPLAY", wl_state.xwayland->display_name, 1);
wwarning("wl_backend: XWayland ready on %s (X11 clients can now connect)",
wwarning("wl_backend: XWayland ready on %s — opening WM Xlib connection",
wl_state.xwayland->display_name);
/* Open the WM's own Xlib connection to its XWayland instance.
* This Display* is returned from wl_display_open so WINGs, title bars,
* root menus, and the dock all work via the X11 code path. */
wl_state.x_display = XOpenDisplay(wl_state.xwayland->display_name);
if (!wl_state.x_display)
wwarning("wl_backend: XOpenDisplay(%s) failed — WM UI limited to native Wayland",
wl_state.xwayland->display_name);
wl_state.xwayland_ready = true;
}
/*
@ -1567,6 +1584,23 @@ handle_backend_new_output(struct wl_listener *listener, void *data)
/* Display lifecycle (Phase 32 / Phase 34) */
/* ------------------------------------------------------------------ */
/* Called by the Wayland event loop whenever the X11 connection fd is readable.
* Drains all pending X11 events and dispatches them through WINGs so that
* menus, dialogs, and other WINGs widgets work during wl_display_run(). */
static int
wl_handle_x11_events(int fd, uint32_t mask, void *data)
{
(void)fd; (void)mask; (void)data;
if (!wl_state.x_display)
return 0;
XEvent event;
while (XPending(wl_state.x_display)) {
XNextEvent(wl_state.x_display, &event);
WMHandleEvent(&event);
}
return 0;
}
static void *
wl_display_open(const char *name)
{
@ -2117,12 +2151,36 @@ wl_display_open(const char *name)
wl_state.started = 1;
/* XWayland is started but we do not block on its ready signal here.
* The ready callback (handle_xwayland_ready) will set DISPLAY when it
* fires so X11 client apps can connect. The WM's own UI renders via
* native Wayland surfaces — no X11 Display* needed by the WM itself. */
/* Block until XWayland fires the "ready" signal — pump the Wayland server
* event loop so the XWayland process can complete its handshake.
* handle_xwayland_ready() calls XOpenDisplay and stores the result in
* wl_state.x_display, then sets xwayland_ready = true to break the loop.
* If XWayland is not available, return NULL (pure Wayland path). */
if (wl_state.xwayland) {
wwarning("wl_backend: waiting for XWayland ready signal...");
struct wl_event_loop *ev_loop =
wl_display_get_event_loop(wl_state.display);
while (!wl_state.xwayland_ready) {
wl_display_flush_clients(wl_state.display);
wl_event_loop_dispatch(ev_loop, 10);
}
if (wl_state.x_display) {
wwarning("wl_backend: XWayland bridge ready — Display*=%p, full WM UI enabled",
(void *)wl_state.x_display);
/* Integrate X11 events into the Wayland event loop so WINGs
* menus, dialogs, and keyboard events are processed during
* wl_display_run(). ConnectionNumber(dpy) is the Xlib socket fd. */
wl_state.x11_event_source = wl_event_loop_add_fd(
wl_display_get_event_loop(wl_state.display),
ConnectionNumber(wl_state.x_display),
WL_EVENT_READABLE,
wl_handle_x11_events, NULL);
} else {
wwarning("wl_backend: XWayland ready but XOpenDisplay failed — pure Wayland mode");
}
}
return NULL; /* pure Wayland: no X11 Display* used by the WM */
return wl_state.x_display;
}
static void
@ -2130,6 +2188,18 @@ wl_display_close(void)
{
if (!wl_state.started)
return;
/* Remove the X11 event source and close the Xlib bridge connection
* before killing XWayland so we don't get stale readable events. */
if (wl_state.x11_event_source) {
wl_event_source_remove(wl_state.x11_event_source);
wl_state.x11_event_source = NULL;
}
if (wl_state.x_display) {
XCloseDisplay(wl_state.x_display);
wl_state.x_display = NULL;
}
/* Phase 17: shut down XWayland.
*
* wlroots 0.15 bug: wlr_xwayland_server_destroy() calls wl_client_destroy()
@ -2970,8 +3040,17 @@ wl_screen_open(WScreen *scr)
scr->scr_height = wl_state.output ?
(int)wl_state.output->height : 1080;
scr->depth = 24;
scr->root_win = WNATIVE_NONE;
scr->root_win = WNATIVE_NONE;
scr->colormap = 0;
/* XWayland bridge: fill in real X11 screen values so RCreateContext
* and WINGs can use the display connection for drawing and fonts. */
if (dpy) {
int snum = DefaultScreen(dpy);
scr->root_win = DefaultRootWindow(dpy);
scr->depth = DefaultDepth(dpy, snum);
scr->colormap = DefaultColormap(dpy, snum);
}
}
static void wl_screen_close(WScreen *scr) { (void)scr; }
@ -4753,6 +4832,15 @@ wl_screen_create_internals(WScreen *scr)
wSetModifierMasks(Mod1Mask, Mod1Mask, Mod4Mask, 0);
/* XWayland bridge: create a real X11 helper window for EWMH
* (_NET_SUPPORTING_WM_CHECK) and clipboard selection ownership. */
if (dpy) {
Window xwin = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy),
0, 0, 1, 1, 0, 0, 0);
if (xwin)
scr->info_window = (WNativeWindow)xwin;
}
/* Phase 30: the WDock X11 code path is still disabled (it needs X11 GCs /
* pixmaps we don't have in pure Wayland mode). The visual dock panel is
* created below via wm_dock_create() which uses wm_shm_surface directly. */