xwayland: fix rendering by switching from map_request to associate+surface.map

The map_request signal was never firing for managed XWayland surfaces,
preventing scene node creation and window management. Switch to the
standard wlroots 0.19 lifecycle: listen for associate (surface attached),
then hook surface->events.map (content committed) before creating the
scene node and calling wManageWindow.

Also fix wl_window_get_client_attrs to report VIEWABLE based on surface
existence rather than surface->mapped, since X11 geometry (xw->width/height)
is available before the wlr_surface maps.
This commit is contained in:
Window Maker 2026-05-25 11:43:18 +02:00
parent 9bb2d02254
commit 5bff28efcd
1 changed files with 46 additions and 27 deletions

View File

@ -262,6 +262,7 @@ struct wl_toplevel_view {
struct wlr_xwayland_surface *xw_surface;
/* Path 1: geometry listener — keeps scene node in sync when X11 window moves. */
struct wl_listener xw_geometry_listener;
struct wl_listener xw_surface_map_listener; /* fires when wlr_surface maps */
/* Phase 18: scene graph tree for this surface (NULL if scene unavailable). */
struct wlr_scene_tree *scene_node;
/* Listener fires when scene_node is destroyed (e.g. when a parent
@ -1643,25 +1644,23 @@ handle_xwayland_set_geometry(struct wl_listener *listener, void *data)
}
/*
* handle_xwayland_map — X11 window is ready to be shown.
* Add to the scene graph and manage as a WM client via wManageWindow.
* handle_xwayland_surface_map — wlr_surface has mapped (committed first buffer).
* Now we can add to scene graph and manage as a WM client.
*/
static void
handle_xwayland_map(struct wl_listener *listener, void *data)
handle_xwayland_surface_map(struct wl_listener *listener, void *data)
{
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener);
struct wl_toplevel_view *view =
wl_container_of(listener, view, xw_surface_map_listener);
(void)data;
struct wlr_xwayland_surface *xw = view->xw_surface;
wwarning("wl_backend: xwayland mapped id=0x%lx class=%s title=%s",
wwarning("wl_backend: xwayland surface mapped id=0x%lx class=%s title=%s",
view->id,
xw->class ? xw->class : "(none)",
xw->title ? xw->title : "(none)");
/* Add to scene graph for compositing regardless of mode.
* Use view->x,y (set in handle_new_xwayland_surface to output/4)
* rather than xw->x,y which may not yet reflect the configure we
* sent; the set_geometry listener keeps them in sync thereafter. */
/* Add to scene graph for compositing. */
if (wl_state.scene && !view->scene_node && xw->surface) {
struct wlr_scene_tree *_vp = wl_state.level_trees[WL_LAYER_NORMAL]
? wl_state.level_trees[WL_LAYER_NORMAL] : &wl_state.scene->tree;
@ -1669,20 +1668,10 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
if (view->scene_node) {
view_attach_scene_destroy_listener(view);
wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y);
wwarning("wl_backend: xwayland scene_node created id=0x%lx pos=%d,%d surface=%p mapped=%d",
view->id, view->x, view->y, (void*)xw->surface,
xw->surface->mapped);
}
} else if (!xw->surface) {
wwarning("wl_backend: xwayland map id=0x%lx: surface is NULL, window will not render",
view->id);
} else if (view->scene_node) {
wwarning("wl_backend: xwayland map id=0x%lx: scene_node already exists", view->id);
}
/* Manage as WM client. Guard: wScreenWithNumber asserts screen_count > 0,
* so skip if StartUp() hasn't initialised the screen yet. */
/* Manage as WM client. */
if (w_global.screen_count == 0)
return;
WScreen *scr = wScreenWithNumber(0);
@ -1699,13 +1688,31 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
int cy = view->y + wwin->client.height / 2;
wl_pointer_warp_to(cx, cy);
/* Phase 30+: add to dock icon list (after wManageWindow so
* _NET_WM_ICON is readable from the X11 window) */
wm_dock_app_add(view);
}
}
}
/*
* handle_xwayland_associate — wlr_surface attached to XWayland surface.
* Hook surface->events.map to know when it actually has content.
*/
static void
handle_xwayland_associate(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 associate id=0x%lx surface=%p",
view->id, (void*)xw->surface);
if (xw->surface) {
view->xw_surface_map_listener.notify = handle_xwayland_surface_map;
wl_signal_add(&xw->surface->events.map, &view->xw_surface_map_listener);
}
}
/*
* handle_xwayland_unmap — X11 window hidden.
* Path 1: just remove the scene node; WindowMaker handles unmanagement.
@ -1719,6 +1726,12 @@ handle_xwayland_unmap(struct wl_listener *listener, void *data)
wwarning("wl_backend: xwayland unmapped id=0x%lx", view->id);
/* Remove surface map listener since the surface is being dissociated */
if (view->xw_surface_map_listener.link.next &&
view->xw_surface_map_listener.link.next != &view->xw_surface_map_listener.link)
wl_list_remove(&view->xw_surface_map_listener.link);
wl_list_init(&view->xw_surface_map_listener.link);
/* Phase 30+: remove from dock icon list */
wm_dock_app_remove(view->id);
@ -1752,6 +1765,9 @@ handle_xwayland_destroy(struct wl_listener *listener, void *data)
wl_list_remove(&view->unmap_listener.link);
wl_list_remove(&view->destroy_listener.link);
wl_list_remove(&view->title_listener.link);
if (view->xw_surface_map_listener.link.next &&
view->xw_surface_map_listener.link.next != &view->xw_surface_map_listener.link)
wl_list_remove(&view->xw_surface_map_listener.link);
if (view->xw_geometry_listener.link.next)
wl_list_remove(&view->xw_geometry_listener.link);
wl_list_remove(&view->link);
@ -1928,13 +1944,13 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data)
view->xw_surface = xw;
view->id = (WNativeWindow)xw->window_id;
view->map_listener.notify = handle_xwayland_map;
view->map_listener.notify = handle_xwayland_associate;
view->unmap_listener.notify = handle_xwayland_unmap;
view->destroy_listener.notify = handle_xwayland_destroy;
view->title_listener.notify = handle_xwayland_set_class;
view->xw_geometry_listener.notify = handle_xwayland_set_geometry;
wl_signal_add(&xw->events.map_request, &view->map_listener);
wl_signal_add(&xw->events.associate, &view->map_listener);
wl_signal_add(&xw->events.dissociate, &view->unmap_listener);
wl_signal_add(&xw->events.destroy, &view->destroy_listener);
wl_signal_add(&xw->events.set_class, &view->title_listener);
@ -1947,8 +1963,11 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data)
view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4;
/* If the surface is already associated (has a wlr_surface), we missed
* the associate event. Not needed with map_request — the client will
* send MapRequest when ready. */
* the associate event. Hook surface->events.map directly. */
if (xw->surface) {
view->xw_surface_map_listener.notify = handle_xwayland_surface_map;
wl_signal_add(&xw->surface->events.map, &view->xw_surface_map_listener);
}
wwarning("wl_backend: new XWayland surface id=0x%lx class=%s title=%s pos=%d,%d",
view->id,
@ -7268,7 +7287,7 @@ wl_window_get_client_attrs(WNativeWindow win, WClientAttributes *out)
struct wl_toplevel_view *v = wl_find_view_by_id(win);
memset(out, 0, sizeof(*out));
if (v && v->xw_surface) {
if (v->xw_surface->surface && v->xw_surface->surface->mapped) {
if (v->xw_surface->surface) {
out->map_state = WCLIENT_VIEWABLE;
out->width = v->xw_surface->width >= 64 ? v->xw_surface->width : 640;
out->height = v->xw_surface->height >= 64 ? v->xw_surface->height : 480;