diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 18b5333..ef310ce 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -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 #include #include +#include /* Phase 17: XWayland support */ #include /* 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; diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 7e45a64..8cea4f7 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -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=:" +# 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=:" 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