wayland: Phase 17 — XWayland support for X11 client windows

Add wlr_xwayland to the headless compositor so X11 applications
(e.g. GNUstep apps) can run under the Wayland backend.

Key changes:
- Include <wlr/xwayland.h>; add xwayland/xwayland_new_surface_listener
  to wl_compositor_state
- Add xw_surface field to wl_toplevel_view so XDG and XWayland windows
  share the same view struct and context-store keyed by WNativeWindow
- XWayland window_id used directly as WNativeWindow (no conflict with
  XDG synthetic IDs ≥ 0x10000000)
- handle_xwayland_{map,unmap,destroy,set_class}: same wManageWindow/
  wUnmanageWindow lifecycle as XDG toplevels
- override_redirect windows (menus, tooltips) are skipped
- view_surface_at: bounds-check path for XWayland surfaces alongside
  wlr_xdg_surface_surface_at for XDG toplevels
- vtable functions updated: focus_set, prop_get_normal_hints,
  prop_get_wm_class, window_fetch_name, window_get_icon_name,
  window_get_client_attrs, client_synth_configure, window_resize,
  window_move, client_close, window_get_rect, prop_get_transient_for,
  client_get_pid all handle xw_surface != NULL
- wlr_xwayland_create(lazy=false) called in wl_display_open after
  compositor and seat; DISPLAY env var set from display_name
- wlr_xwayland_destroy in wl_display_close
- Smoke test: new check 4g verifies "XWayland started DISPLAY=:<n>" log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-03-28 21:24:49 +01:00
parent c2c5027610
commit 5b1590607c
2 changed files with 353 additions and 30 deletions

View File

@ -60,6 +60,11 @@
* Phase 66: XDG popup initial configure — schedule configure for
* WLR_XDG_SURFACE_ROLE_POPUP so context menus and autocomplete dropdowns
* can commit their first buffer without stalling on the compositor.
* Phase 17: XWayland support — wlr_xwayland started alongside the headless
* backend; X11 windows (e.g. GNUstep apps) appear via new_surface events,
* receive synthetic WNativeWindow IDs equal to their xcb_window_t, and
* are managed through the same wl_toplevel_view / wManageWindow lifecycle
* as XDG toplevels. DISPLAY env var is set so X11 clients can connect.
*/
#ifdef HAVE_CONFIG_H
@ -84,6 +89,7 @@
#include <wlr/types/wlr_keyboard.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/xwayland.h> /* Phase 17: XWayland support */
#include <wlr/util/box.h>
/* Generated xdg-shell protocol implementation (Wayland glue code) */
#include "protocols/xdg-shell-protocol.c"
@ -122,6 +128,10 @@ struct wl_toplevel_view {
struct wl_listener request_fullscreen_listener;/* Phase 16 */
struct wl_listener request_minimize_listener; /* Phase 16 */
struct wl_listener set_app_id_listener; /* Phase 16 */
/* Phase 17: XWayland — set for X11 windows, NULL for XDG toplevels.
* When non-NULL, the lifecycle listeners above are wired to XWayland
* signals instead of XDG signals; id == xw_surface->window_id. */
struct wlr_xwayland_surface *xw_surface;
WNativeWindow id; /* synthetic handle, >= 0x10000000 */
int x, y; /* Phase 55: WM-assigned position */
long *wm_state; /* Phase 56: saved WM state or NULL */
@ -182,6 +192,9 @@ struct wl_compositor_state {
struct wl_listener pointer_motion_abs_listener;
struct wl_listener pointer_button_listener;
struct wl_listener pointer_frame_listener;
/* Phase 17: XWayland server for X11 client support */
struct wlr_xwayland *xwayland;
struct wl_listener xwayland_new_surface_listener;
char socket_name[64]; /* Phase 34: e.g. "wayland-0" */
int started; /* non-zero after successful display_open */
};
@ -219,6 +232,8 @@ static void wl_update_pointer_focus(uint32_t time_msec);
static void wl_pointer_warp_to(int x, int y);
/* Phase 14: pointer event handler forward declarations. */
static void handle_pointer_motion(struct wl_listener *listener, void *data);
/* Phase 17: XWayland new surface forward declaration. */
static void handle_xwayland_new_surface(struct wl_listener *listener, void *data);
static void handle_pointer_motion_absolute(struct wl_listener *listener, void *data);
static void handle_pointer_button(struct wl_listener *listener, void *data);
static void handle_pointer_frame(struct wl_listener *listener, void *data);
@ -477,6 +492,161 @@ handle_new_toplevel_decoration(struct wl_listener *listener, void *data)
wwarning("wl_backend: decoration mode=client_side for new toplevel");
}
/* ------------------------------------------------------------------ */
/* Phase 17: XWayland surface lifecycle */
/* ------------------------------------------------------------------ */
/*
* handle_xwayland_map — X11 window is ready to be managed.
* Uses the same wManageWindow path as XDG toplevels.
*/
static void
handle_xwayland_map(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
wwarning("wl_backend: xwayland mapped id=0x%lx class=%s title=%s",
view->id,
xw->class ? xw->class : "(none)",
xw->title ? xw->title : "(none)");
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WWindow *wwin = wManageWindow(scr, view->id);
if (wwin) {
wwarning("wl_backend: wManageWindow(xwayland) succeeded id=0x%lx "
"class=%s name=%s geo=%dx%d pid=%d",
view->id,
wwin->wm_class ? wwin->wm_class : "(none)",
wwin->wm_instance ? wwin->wm_instance : "(none)",
wwin->client.width, wwin->client.height, (int)xw->pid);
int cx = view->x + wwin->client.width / 2;
int cy = view->y + wwin->client.height / 2;
wl_pointer_warp_to(cx, cy);
}
}
}
/*
* handle_xwayland_unmap — X11 window hidden; unmanage it.
*/
static void
handle_xwayland_unmap(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener);
(void)data;
wwarning("wl_backend: xwayland unmapped id=0x%lx", view->id);
WScreen *scr = wScreenWithNumber(0);
if (scr) {
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (desc && desc->parent_type == WCLASS_WINDOW) {
WWindow *wwin = (WWindow *)desc->parent;
wUnmanageWindow(wwin, False, True);
}
}
}
/*
* handle_xwayland_destroy — X11 window destroyed; free the view.
*/
static void
handle_xwayland_destroy(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, destroy_listener);
(void)data;
wl_list_remove(&view->map_listener.link);
wl_list_remove(&view->unmap_listener.link);
wl_list_remove(&view->destroy_listener.link);
wl_list_remove(&view->title_listener.link);
wl_list_remove(&view->link);
wl_state.managed_surface_count--;
if (view->wm_state) wfree(view->wm_state);
if (view->wm_command) {
int ci;
for (ci = 0; ci < view->wm_command_n; ci++)
wfree(view->wm_command[ci]);
wfree(view->wm_command);
}
wfree(view);
}
/*
* handle_xwayland_set_class — X11 window WM_CLASS changed.
*/
static void
handle_xwayland_set_class(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, title_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
WObjDescriptor *desc = wm_backend->context_find(view->id, WM_CTX_CLIENT_WIN);
if (!desc || desc->parent_type != WCLASS_WINDOW)
return;
WWindow *wwin = (WWindow *)desc->parent;
if (wwin->wm_class) wfree(wwin->wm_class);
if (wwin->wm_instance) wfree(wwin->wm_instance);
wwin->wm_class = xw->class ? strdup(xw->class) : NULL;
wwin->wm_instance = xw->instance ? strdup(xw->instance) : NULL;
}
/*
* handle_xwayland_new_surface — called when a new X11 window connects.
* Allocates a wl_toplevel_view, uses window_id as the WNativeWindow ID,
* and wires the lifecycle listeners to XWayland signals.
*/
static void
handle_xwayland_new_surface(struct wl_listener *listener, void *data)
{
struct wlr_xwayland_surface *xw = data;
(void)listener;
/* Skip override-redirect windows (menus, tooltips — not WM-managed). */
if (xw->override_redirect) {
wwarning("wl_backend: xwayland override_redirect window 0x%x (skipped)",
(unsigned)xw->window_id);
return;
}
struct wl_toplevel_view *view = wmalloc(sizeof(*view));
memset(view, 0, sizeof(*view));
view->xw_surface = xw;
view->id = (WNativeWindow)xw->window_id;
view->map_listener.notify = handle_xwayland_map;
view->unmap_listener.notify = handle_xwayland_unmap;
view->destroy_listener.notify = handle_xwayland_destroy;
view->title_listener.notify = handle_xwayland_set_class;
wl_signal_add(&xw->events.map, &view->map_listener);
wl_signal_add(&xw->events.unmap, &view->unmap_listener);
wl_signal_add(&xw->events.destroy, &view->destroy_listener);
wl_signal_add(&xw->events.set_class, &view->title_listener);
wl_list_insert(&wl_state.toplevel_list, &view->link);
wl_state.managed_surface_count++;
/* Position at the centre of the headless output initially. */
view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4;
view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4;
wwarning("wl_backend: new XWayland surface id=0x%lx class=%s title=%s",
view->id,
xw->class ? xw->class : "(none)",
xw->title ? xw->title : "(none)");
/* Send an initial configure so the X11 client gets a position/size. */
wlr_xwayland_surface_configure(xw,
(int16_t)view->x, (int16_t)view->y,
xw->width > 0 ? xw->width : 640,
xw->height > 0 ? xw->height : 480);
}
/*
* Phase 36 / 38: new XDG surface listener.
* Allocates a wl_toplevel_view, assigns a synthetic WNativeWindow ID,
@ -768,6 +938,27 @@ 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. */
wl_state.xwayland = wlr_xwayland_create(wl_state.display,
wl_state.compositor, false);
if (wl_state.xwayland) {
wl_state.xwayland_new_surface_listener.notify = handle_xwayland_new_surface;
wl_signal_add(&wl_state.xwayland->events.new_surface,
&wl_state.xwayland_new_surface_listener);
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",
wl_state.xwayland->display_name);
} else {
wwarning("wl_backend: XWayland created (display_name pending)");
}
} else {
wwarning("wl_backend: XWayland unavailable (X11 clients will not work)");
}
/* Phase 37: periodic timer to drive client frame callbacks at ~60 fps.
* Using a timer avoids the swapchain/allocator complexity of the full
* render pipeline while still keeping Wayland clients unblocked. */
@ -795,6 +986,12 @@ wl_display_close(void)
{
if (!wl_state.started)
return;
/* Phase 17: shut down XWayland before destroying the display. */
if (wl_state.xwayland) {
wlr_xwayland_destroy(wl_state.xwayland);
wl_state.xwayland = NULL;
unsetenv("DISPLAY");
}
if (wl_state.frame_timer) {
wl_event_source_remove(wl_state.frame_timer);
wl_state.frame_timer = NULL;
@ -962,7 +1159,14 @@ wl_focus_set(WNativeWindow win, int revert_to, unsigned long timestamp)
return;
}
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || !v->xdg_surface->surface)
if (!v) return;
/* Phase 17: XWayland windows use wlr_xwayland_surface_activate. */
if (v->xw_surface) {
wlr_xwayland_surface_activate(v->xw_surface, true);
wwarning("wl_backend: focus_set(xwayland) id=0x%lx", win);
return;
}
if (!v->xdg_surface || !v->xdg_surface->surface)
return;
wlr_seat_keyboard_notify_enter(wl_state.seat,
v->xdg_surface->surface,
@ -1063,7 +1267,7 @@ static void wl_pointer_ungrab(unsigned long timestamp) { (void)timestamp; }
/*
* Find the topmost mapped view whose surface contains screen point (px, py).
* Returns the wlr_surface and sets *sx/*sy to surface-local coordinates.
* Returns the wlr_surface and sets sx,sy to surface-local coordinates.
* Iterates toplevel_list in reverse-insertion order (last = topmost).
*/
static struct wlr_surface *
@ -1072,10 +1276,27 @@ view_surface_at(int px, int py, double *sx_out, double *sy_out,
{
struct wl_toplevel_view *v;
wl_list_for_each_reverse(v, &wl_state.toplevel_list, link) {
if (!v->xdg_surface || !v->xdg_surface->mapped)
continue;
double sx = px - (double)v->x;
double sy = py - (double)v->y;
/* Phase 17: XWayland window — simple bounds check on the surface. */
if (v->xw_surface) {
if (!v->xw_surface->mapped || !v->xw_surface->surface)
continue;
if (sx >= 0 && sy >= 0 &&
sx < (double)v->xw_surface->width &&
sy < (double)v->xw_surface->height) {
if (sx_out) *sx_out = sx;
if (sy_out) *sy_out = sy;
if (view_out) *view_out = v;
return v->xw_surface->surface;
}
continue;
}
/* XDG toplevel path. */
if (!v->xdg_surface || !v->xdg_surface->mapped)
continue;
double sub_sx, sub_sy;
struct wlr_surface *sub = wlr_xdg_surface_surface_at(
v->xdg_surface, sx, sy, &sub_sx, &sub_sy);
@ -1276,11 +1497,37 @@ static void wl_event_randr_update(void *event) { (void)event; }
static int
wl_prop_get_normal_hints(WNativeWindow win, WNormalHints *out, int *pre_icccm)
{
/* Phase 47: populate size constraints from XDG toplevel. */
/* Phase 47: populate size constraints from XDG toplevel.
* Phase 17: also handles XWayland size_hints. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
memset(out, 0, sizeof(*out));
*pre_icccm = 0;
if (!v || !v->xdg_surface || !v->xdg_surface->toplevel)
if (!v) return 0;
/* Phase 17: XWayland size_hints path. */
if (v->xw_surface) {
struct wlr_xwayland_surface_size_hints *sh = v->xw_surface->size_hints;
if (sh) {
if (sh->min_width > 0 || sh->min_height > 0) {
out->flags |= WNH_P_MIN_SIZE;
out->min_width = sh->min_width > 0 ? sh->min_width : 1;
out->min_height = sh->min_height > 0 ? sh->min_height : 1;
}
if (sh->max_width > 0 || sh->max_height > 0) {
out->flags |= WNH_P_MAX_SIZE;
out->max_width = sh->max_width;
out->max_height = sh->max_height;
}
}
if (v->xw_surface->width > 0 && v->xw_surface->height > 0) {
out->flags |= WNH_P_SIZE;
out->width = v->xw_surface->width;
out->height = v->xw_surface->height;
}
return (out->flags != 0) ? 1 : 0;
}
if (!v->xdg_surface || !v->xdg_surface->toplevel)
return 0;
struct wlr_xdg_toplevel *tl = v->xdg_surface->toplevel;
if (tl->current.min_width > 0 || tl->current.min_height > 0) {
@ -1313,8 +1560,13 @@ static int
wl_prop_get_wm_class(WNativeWindow win,
char **wm_class, char **wm_instance)
{
/* Phase 41: use XDG app_id as both WM_CLASS and WM_INSTANCE. */
/* Phase 41: use XDG app_id / Phase 17: use XWayland WM_CLASS. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->xw_surface) {
*wm_class = strdup(v->xw_surface->class ? v->xw_surface->class : "x11-client");
*wm_instance = strdup(v->xw_surface->instance ? v->xw_surface->instance : "x11-client");
return 0;
}
const char *app_id = (v && v->xdg_surface && v->xdg_surface->toplevel &&
v->xdg_surface->toplevel->app_id)
? v->xdg_surface->toplevel->app_id : "wayland-client";
@ -1326,11 +1578,13 @@ wl_prop_get_wm_class(WNativeWindow win,
static Bool
wl_window_fetch_name(WNativeWindow win, char **name_out)
{
/* Phase 41: use XDG toplevel title as the window name. */
/* Phase 41: XDG title / Phase 17: XWayland title. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *title = (v && v->xdg_surface && v->xdg_surface->toplevel &&
v->xdg_surface->toplevel->title)
? v->xdg_surface->toplevel->title : NULL;
const char *title = NULL;
if (v && v->xw_surface)
title = v->xw_surface->title;
else if (v && v->xdg_surface && v->xdg_surface->toplevel)
title = v->xdg_surface->toplevel->title;
if (title) {
*name_out = strdup(title);
return True;
@ -1342,13 +1596,13 @@ wl_window_fetch_name(WNativeWindow win, char **name_out)
static Bool
wl_window_get_icon_name(WNativeWindow win, char **name_out)
{
/* Phase 60: Wayland has no separate icon-name concept; return the
* XDG toplevel title (same as window_fetch_name) so the WM always
* has something to display in icon labels. */
/* Phase 60: return title for icon labels; Phase 17: XWayland path. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
const char *title = (v && v->xdg_surface && v->xdg_surface->toplevel &&
v->xdg_surface->toplevel->title)
? v->xdg_surface->toplevel->title : NULL;
const char *title = NULL;
if (v && v->xw_surface)
title = v->xw_surface->title;
else if (v && v->xdg_surface && v->xdg_surface->toplevel)
title = v->xdg_surface->toplevel->title;
if (title) {
*name_out = strdup(title);
return True;
@ -1374,7 +1628,14 @@ wl_prop_get_transient_for(WNativeWindow win)
* implementation stores the parent as xdg_surface->toplevel->parent
* (a wlr_xdg_surface *). We walk the view list to find its ID. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || !v->xdg_surface->toplevel)
if (!v) return WNATIVE_NONE;
/* Phase 17: XWayland transient_for via parent window_id. */
if (v->xw_surface) {
struct wlr_xwayland_surface *parent = v->xw_surface->parent;
if (!parent) return WNATIVE_NONE;
return (WNativeWindow)parent->window_id;
}
if (!v->xdg_surface || !v->xdg_surface->toplevel)
return WNATIVE_NONE;
struct wlr_xdg_surface *parent = v->xdg_surface->toplevel->parent;
if (!parent)
@ -1421,17 +1682,28 @@ static void wl_window_map_subwindows(WNativeWindow parent) { (void)parent; }
static void wl_window_unmap(WNativeWindow win) { (void)win; }
static void wl_window_move(WNativeWindow win, int x, int y)
{
/* Phase 55: record the WM-assigned position in the view struct. */
/* Phase 55: record the WM-assigned position in the view struct.
* Phase 17: also inform XWayland of the new position. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v) { v->x = x; v->y = y; }
if (!v) return;
v->x = x; v->y = y;
if (v->xw_surface)
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)x, (int16_t)y, v->xw_surface->width, v->xw_surface->height);
}
static void wl_window_resize(WNativeWindow win, int w, int h)
{
/* Phase 44: send a configure with the new size. */
/* Phase 44: send a configure with the new size.
* Phase 17: XWayland path uses wlr_xwayland_surface_configure. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || w <= 0 || h <= 0)
if (!v || w <= 0 || h <= 0) return;
if (v->xw_surface) {
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)v->x, (int16_t)v->y, (uint16_t)w, (uint16_t)h);
return;
}
if (!v->xdg_surface) return;
wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)w, (uint32_t)h);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
}
@ -1451,6 +1723,14 @@ wl_window_get_rect(WNativeWindow win,
return 0; /* invalid/none */
}
struct wl_toplevel_view *v = wl_find_view_by_id(win);
/* Phase 17: XWayland geometry. */
if (v && v->xw_surface) {
if (win_x) { *win_x = v->x; }
if (win_y) { *win_y = v->y; }
if (win_w) { *win_w = v->xw_surface->width > 0 ? v->xw_surface->width : 1; }
if (win_h) { *win_h = v->xw_surface->height > 0 ? v->xw_surface->height : 1; }
return 1;
}
if (v && v->xdg_surface && v->xdg_surface->surface) {
int w = v->xdg_surface->surface->current.width;
int h = v->xdg_surface->surface->current.height;
@ -1491,10 +1771,19 @@ static int wl_overlay_text_width(WScreen *scr, const char *str, int n) { (void)
static int
wl_window_get_client_attrs(WNativeWindow win, WClientAttributes *out)
{
/* Phase 42: return real map_state and geometry from XDG surface. */
/* Phase 42: return real map_state and geometry from XDG surface.
* Phase 17: XWayland path uses xw_surface->mapped and width/height. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
memset(out, 0, sizeof(*out));
if (v && v->xdg_surface && v->xdg_surface->surface) {
if (v && v->xw_surface) {
if (v->xw_surface->mapped) {
out->map_state = WCLIENT_VIEWABLE;
out->width = v->xw_surface->width;
out->height = v->xw_surface->height;
} else {
out->map_state = WCLIENT_UNMAPPED;
}
} else if (v && v->xdg_surface && v->xdg_surface->surface) {
out->map_state = WCLIENT_VIEWABLE;
out->width = v->xdg_surface->surface->current.width;
out->height = v->xdg_surface->surface->current.height;
@ -1526,13 +1815,21 @@ wl_client_synth_configure(WNativeWindow client_win,
{
/* Phase 44: send an XDG configure with the new size to the client.
* Phase 13: also record the WM-assigned position in the view so
* pointer hit-testing (view_surface_at) uses current coordinates. */
* pointer hit-testing (view_surface_at) uses current coordinates.
* Phase 17: XWayland uses wlr_xwayland_surface_configure instead. */
(void)border_width; (void)above;
struct wl_toplevel_view *v = wl_find_view_by_id(client_win);
if (!v || !v->xdg_surface)
return;
if (!v) return;
v->x = x;
v->y = y;
if (v->xw_surface) {
wlr_xwayland_surface_configure(v->xw_surface,
(int16_t)x, (int16_t)y,
width > 0 ? (uint16_t)width : v->xw_surface->width,
height > 0 ? (uint16_t)height : v->xw_surface->height);
return;
}
if (!v->xdg_surface) return;
if (width > 0 && height > 0)
wlr_xdg_toplevel_set_size(v->xdg_surface, (uint32_t)width, (uint32_t)height);
wlr_xdg_surface_schedule_configure(v->xdg_surface);
@ -1544,9 +1841,13 @@ static void wl_client_remove_from_save_set(WNativeWindow win) { (void)win; }
static void
wl_client_close(WNativeWindow win)
{
/* Phase 46: ask the XDG toplevel to close itself. */
/* Phase 46: ask the surface to close.
* Phase 17: XWayland uses wlr_xwayland_surface_close. */
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->xdg_surface)
if (!v) return;
if (v->xw_surface)
wlr_xwayland_surface_close(v->xw_surface);
else if (v->xdg_surface)
wlr_xdg_toplevel_send_close(v->xdg_surface);
}
@ -1560,7 +1861,11 @@ wl_client_get_gnustep_attrs(WNativeWindow win, GNUstepWMAttributes **attr_out)
static int wl_client_get_pid(WNativeWindow win)
{
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (!v || !v->xdg_surface || !v->xdg_surface->resource)
if (!v) return 0;
/* Phase 17: XWayland surfaces have a direct pid field. */
if (v->xw_surface)
return (int)v->xw_surface->pid;
if (!v->xdg_surface || !v->xdg_surface->resource)
return 0;
struct wl_client *client = wl_resource_get_client(v->xdg_surface->resource);
if (!client) return 0;

View File

@ -110,6 +110,10 @@
# receives wl_pointer.enter events. wl_client_synth_configure now records
# the WM-assigned position (v->x/v->y) so view_surface_at() returns the
# correct surface. Verified by "pointer focus entered surface" log.
# 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.
# 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
@ -255,6 +259,20 @@ 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 ----------------------
#
# wl_display_open() logs "XWayland started DISPLAY=:<n>" when
# wlr_xwayland_create() succeeds; verify the log line is present.
# (Falls back gracefully if XWayland is unavailable.)
if grep -q 'XWayland started DISPLAY=' /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"
elif grep -q 'XWayland unavailable' /tmp/wl-smoke-stderr; then
echo " xwayland check: XWayland unavailable (skipped)"
else
die "compositor did not log XWayland startup status"
fi
# -- 5. Core Wayland globals must be advertised ---------------------------
#
# Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the