From 5bff28efcdf67c41a5f4c9abf563ae926486e34a Mon Sep 17 00:00:00 2001 From: Window Maker Date: Mon, 25 May 2026 11:43:18 +0200 Subject: [PATCH] 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. --- src/backend/wayland/wl_backend.c | 73 ++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index e0dbd7f..02d9344 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -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;