diff --git a/src/Makefile.am b/src/Makefile.am index f95c8bc..0b69a55 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -189,6 +189,7 @@ wmaker_SOURCES += \ backend/wayland/wl_pointer_focus.c \ backend/wayland/wl_grab.c \ backend/wayland/wl_keyboard.c \ + backend/wayland/wl_xwayland.c \ backend/wayland/wl_dock.c \ backend/wayland/wl_input_fwd.c endif diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 41ddf00..3834008 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -340,7 +340,9 @@ static void handle_layer_shell_new_surface(struct wl_listener *listener, void *d /* Phase 37: virtual pointer handler. */ static void handle_virtual_pointer_new(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); +extern void handle_xwayland_new_surface(struct wl_listener *listener, void *data); +extern void handle_xwayland_ready(struct wl_listener *listener, void *data); +extern void handle_xwayland_server_destroy(struct wl_listener *listener, void *data); extern void wm_dock_create(WScreen *scr); /* Phase 20: nested backend real input device forward declaration. */ extern void handle_backend_new_input(struct wl_listener *listener, void *data); @@ -520,7 +522,7 @@ handle_toplevel_set_app_id(struct wl_listener *listener, void *data) /* * Phase 38: toplevel map — surface is ready to be managed. */ -static void +void handle_toplevel_scene_destroy(struct wl_listener *listener, void *data) { (void)data; @@ -534,7 +536,7 @@ handle_toplevel_scene_destroy(struct wl_listener *listener, void *data) wl_list_remove(&view->scene_destroy_listener.link); } -static void +void view_attach_scene_destroy_listener(struct wl_toplevel_view *view) { if (!view || !view->scene_node) return; @@ -959,438 +961,6 @@ wmaker_popup_manager_bind(struct wl_client *client, void *data, NULL, NULL); } -/* ------------------------------------------------------------------ */ -/* Phase 17: XWayland surface lifecycle */ -/* ------------------------------------------------------------------ */ - -/* --- Override-redirect (unmanaged) XWayland surface handlers --- */ - -static void -handle_or_map(struct wl_listener *listener, void *data) -{ - struct wl_or_view *ov = wl_container_of(listener, ov, map_listener); - struct wlr_xwayland_surface *xw = ov->xw_surface; - (void)data; - - /* Skip scene operations while the startup pump thread is running — - * wlroots is not thread-safe. The scene node will be created when - * the surface is next committed after the pump thread exits. */ - if (wl_state.startup_pump_running) - return; - - if (wl_state.scene && !ov->scene_node && xw->surface) { - struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU] - ? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree; - ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface); - if (ov->scene_node) - wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y); - } -} - -static void -handle_or_unmap(struct wl_listener *listener, void *data) -{ - struct wl_or_view *ov = wl_container_of(listener, ov, unmap_listener); - (void)data; - - if (ov->scene_node) { - wlr_scene_node_destroy(&ov->scene_node->node); - ov->scene_node = NULL; - } -} - -static void -handle_or_destroy(struct wl_listener *listener, void *data) -{ - struct wl_or_view *ov = wl_container_of(listener, ov, destroy_listener); - (void)data; - - wl_list_remove(&ov->map_listener.link); - wl_list_remove(&ov->unmap_listener.link); - wl_list_remove(&ov->destroy_listener.link); - wl_list_remove(&ov->set_geometry_listener.link); - wl_list_remove(&ov->link); - if (ov->scene_node) - wlr_scene_node_destroy(&ov->scene_node->node); - wfree(ov); -} - -static void -handle_or_set_geometry(struct wl_listener *listener, void *data) -{ - struct wl_or_view *ov = wl_container_of(listener, ov, set_geometry_listener); - struct wlr_xwayland_surface *xw = ov->xw_surface; - (void)data; - if (ov->scene_node) - wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y); -} - -/* --- Managed XWayland surface handlers --- */ - -/* - * Path 1: handle_xwayland_set_geometry — X11 window moved/resized. - * Keeps the scene node in sync with the X11-authoritative position. - */ -static void -handle_xwayland_set_geometry(struct wl_listener *listener, void *data) -{ - struct wl_toplevel_view *view = - wl_container_of(listener, view, xw_geometry_listener); - struct wlr_xwayland_surface *xw = view->xw_surface; - (void)data; - if (view->scene_node) { - if (view->frame_id) { - /* Framed: position relative to frame tree */ - wlr_scene_node_set_position(&view->scene_node->node, - view->frame_rel_x, view->frame_rel_y); - } else { - wlr_scene_node_set_position(&view->scene_node->node, xw->x, xw->y); - } - } -} - -/* - * 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_surface_map(struct wl_listener *listener, void *data) -{ - 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 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. */ - 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; - view->scene_node = wlr_scene_subsurface_tree_create(_vp, xw->surface); - if (view->scene_node) { - view_attach_scene_destroy_listener(view); - wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y); - } - } - - /* Manage as WM client. */ - if (w_global.screen_count == 0) - return; - 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); - - 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. - * Pure Wayland: unmanage via wUnmanageWindow. - */ -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); - - /* 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); - - if (view->scene_node) { - wlr_scene_node_destroy(&view->scene_node->node); - view->scene_node = NULL; - } - - if (w_global.screen_count == 0) - return; - 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); - 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); - /* Phase 18: remove scene node (may already be gone from unmap). */ - if (view->scene_node) { - wlr_scene_node_destroy(&view->scene_node->node); - view->scene_node = NULL; - } - 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; -} - -/* - * Path 1: handle_xwayland_server_destroy — fired when the XWayland server - * process exits before or without firing the ready signal (startup failure). - * Set the failure flag so the pump loop can exit early and clean up. - */ -static void -handle_xwayland_server_destroy(struct wl_listener *listener, void *data) -{ - (void)listener; - (void)data; - wwarning("wl_backend: XWayland server exited"); - wl_state.xwayland_ready = true; /* break the wait loop */ -} - -static void -handle_xwayland_ready(struct wl_listener *listener, void *data) -{ - (void)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 — 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; -} - -/* - * handle_xwayland_new_surface — called when a new X11 window connects. - * Allocates a wl_toplevel_view, uses window_id as the Window 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; - - /* Override_redirect windows (WM frames, dock icons, menus, tooltips): - * not managed as ICCCM clients but must be composited. Track them in - * or_list and add to the scene graph on map. */ - if (xw->override_redirect) { - /* Skip the rcontext drawable — it's an offscreen pixmap holder. */ - if (wl_state.rcontext_xwin && - xw->window_id == (xcb_window_t)wl_state.rcontext_xwin) - return; - - struct wl_or_view *ov = wmalloc(sizeof(*ov)); - memset(ov, 0, sizeof(*ov)); - ov->xw_surface = xw; - - ov->map_listener.notify = handle_or_map; - ov->unmap_listener.notify = handle_or_unmap; - ov->destroy_listener.notify = handle_or_destroy; - ov->set_geometry_listener.notify = handle_or_set_geometry; - - wl_signal_add(&xw->events.destroy, &ov->destroy_listener); - wl_signal_add(&xw->events.associate, &ov->map_listener); - wl_signal_add(&xw->events.dissociate, &ov->unmap_listener); - wl_signal_add(&xw->events.set_geometry, &ov->set_geometry_listener); - - wl_list_insert(&wl_state.or_list, &ov->link); - - /* If already associated (surface exists), add to scene now. */ - if (xw->surface) { - if (wl_state.scene) { - struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU] - ? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree; - ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface); - if (ov->scene_node) - wlr_scene_node_set_position(&ov->scene_node->node, - xw->x, xw->y); - } - } - return; - } - - /* Skip the wrlib RContext drawable window. It is created by RCreateContext - * without override_redirect, but is purely an internal offscreen drawable - * with no WM_CLASS and no backing wl_surface. XWM managing it triggers - * RENDER picture operations that fail (BadDrawable/RenderBadPicture) and - * corrupt XWM state → broken pipe. This check must come before the - * screen_count guard because we record rcontext_xwin in screen_alloc_gcs - * (called during wScreenInit at screen_count==0), and XWM may emit the - * new_surface event for it after screen_count becomes 1. */ - if (wl_state.rcontext_xwin && xw->window_id == (xcb_window_t)wl_state.rcontext_xwin) { - wwarning("wl_backend: xwayland rcontext drawable 0x%x skipped", - (unsigned)xw->window_id); - return; - } - - /* Guard: during startup the pump thread dispatches Wayland events while - * WMCreateScreenWithRContext runs on the main thread. WINGs creates - * internal X windows then, triggering this callback from the pump thread. - * wlroots is not thread-safe — calling wlr_xwayland_surface_configure - * from a non-main thread crashes. Real app windows arrive only after - * screen_count > 0. */ - if (w_global.screen_count == 0) { - wwarning("wl_backend: xwayland new surface 0x%x during startup — skipped", - (unsigned)xw->window_id); - return; - } - - /* Skip windows with no WM_CLASS and no title: these are internal - * WINGs/wrlib helper windows that XWM has processed late (after - * screen_count > 0 due to pump-thread timing). Managing them causes - * frame-window creation on a null WNativeWindow → BadDrawable cascade - * that corrupts XWayland state and eventually triggers an XIO error. - * Real application windows always have WM_CLASS set before they map. - * If WM_CLASS is set later, the set_class signal will NOT re-trigger - * management — apps that misbehave this way simply won't be managed, - * which is better than a broken-pipe crash. */ - if (!xw->class && !xw->title) { - wwarning("wl_backend: xwayland surface 0x%x has no class/title yet — deferring", - (unsigned)xw->window_id); - } - - 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_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.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); - wl_signal_add(&xw->events.set_geometry, &view->xw_geometry_listener); - - wl_list_insert(&wl_state.toplevel_list, &view->link); - wl_state.managed_surface_count++; - - view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4; - 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. 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, - xw->class ? xw->class : "(none)", - xw->title ? xw->title : "(none)", - xw->x, xw->y); - - /* Send initial configure so the X11 client gets a position and size. - * Use the client's requested size if reasonable, otherwise default. */ - { - int cw = (xw->width >= 64) ? xw->width : 640; - int ch = (xw->height >= 64) ? xw->height : 480; - wlr_xwayland_surface_configure(xw, - (int16_t)view->x, (int16_t)view->y, cw, ch); - } -} - /* * handle_xdg_toplevel_initial_commit -- wlroots 0.19 requires that the * initial configure is sent in response to the client's first (empty) commit, diff --git a/src/backend/wayland/wl_xwayland.c b/src/backend/wayland/wl_xwayland.c new file mode 100644 index 0000000..6fd133c --- /dev/null +++ b/src/backend/wayland/wl_xwayland.c @@ -0,0 +1,380 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../screen.h" +#include "window.h" +#include "wl_types.h" +#include + +/* Forward declarations */ +void view_attach_scene_destroy_listener(struct wl_toplevel_view *view); +void wm_dock_app_add(struct wl_toplevel_view *v); +void wm_dock_app_remove(WNativeWindow id); +void wl_pointer_warp_to(int x, int y); + +#define VIEW_FROM_LISTENER(ptr, field) \ + ((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field))) + +/* ------------------------------------------------------------------ */ +/* Phase 17: XWayland surface lifecycle */ +/* ------------------------------------------------------------------ */ + +/* --- Override-redirect (unmanaged) XWayland surface handlers --- */ + +static void +handle_or_map(struct wl_listener *listener, void *data) +{ + struct wl_or_view *ov = wl_container_of(listener, ov, map_listener); + struct wlr_xwayland_surface *xw = ov->xw_surface; + (void)data; + + if (wl_state.startup_pump_running) + return; + + if (wl_state.scene && !ov->scene_node && xw->surface) { + struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU] + ? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree; + ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface); + if (ov->scene_node) + wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y); + } +} + +static void +handle_or_unmap(struct wl_listener *listener, void *data) +{ + struct wl_or_view *ov = wl_container_of(listener, ov, unmap_listener); + (void)data; + + if (ov->scene_node) { + wlr_scene_node_destroy(&ov->scene_node->node); + ov->scene_node = NULL; + } +} + +static void +handle_or_destroy(struct wl_listener *listener, void *data) +{ + struct wl_or_view *ov = wl_container_of(listener, ov, destroy_listener); + (void)data; + + wl_list_remove(&ov->map_listener.link); + wl_list_remove(&ov->unmap_listener.link); + wl_list_remove(&ov->destroy_listener.link); + wl_list_remove(&ov->set_geometry_listener.link); + wl_list_remove(&ov->link); + if (ov->scene_node) + wlr_scene_node_destroy(&ov->scene_node->node); + wfree(ov); +} + +static void +handle_or_set_geometry(struct wl_listener *listener, void *data) +{ + struct wl_or_view *ov = wl_container_of(listener, ov, set_geometry_listener); + struct wlr_xwayland_surface *xw = ov->xw_surface; + (void)data; + if (ov->scene_node) + wlr_scene_node_set_position(&ov->scene_node->node, xw->x, xw->y); +} + +/* --- Managed XWayland surface handlers --- */ + +static void +handle_xwayland_set_geometry(struct wl_listener *listener, void *data) +{ + struct wl_toplevel_view *view = + wl_container_of(listener, view, xw_geometry_listener); + struct wlr_xwayland_surface *xw = view->xw_surface; + (void)data; + if (view->scene_node) { + if (view->frame_id) { + wlr_scene_node_set_position(&view->scene_node->node, + view->frame_rel_x, view->frame_rel_y); + } else { + wlr_scene_node_set_position(&view->scene_node->node, xw->x, xw->y); + } + } +} + +static void +handle_xwayland_surface_map(struct wl_listener *listener, void *data) +{ + 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 surface mapped id=0x%lx class=%s title=%s", + view->id, + xw->class ? xw->class : "(none)", + xw->title ? xw->title : "(none)"); + + 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; + view->scene_node = wlr_scene_subsurface_tree_create(_vp, xw->surface); + if (view->scene_node) { + view_attach_scene_destroy_listener(view); + wlr_scene_node_set_position(&view->scene_node->node, view->x, view->y); + } + } + + if (w_global.screen_count == 0) + return; + 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); + wm_dock_app_add(view); + } + } +} + +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); + } +} + +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); + + 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); + + wm_dock_app_remove(view->id); + + if (view->scene_node) { + wlr_scene_node_destroy(&view->scene_node->node); + view->scene_node = NULL; + } + + if (w_global.screen_count == 0) + return; + 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); + } + } +} + +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); + 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); + if (view->scene_node) { + wlr_scene_node_destroy(&view->scene_node->node); + view->scene_node = NULL; + } + 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); +} + +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; +} + +void +handle_xwayland_server_destroy(struct wl_listener *listener, void *data) +{ + (void)listener; + (void)data; + wwarning("wl_backend: XWayland server exited"); + wl_state.xwayland_ready = true; +} + +void +handle_xwayland_ready(struct wl_listener *listener, void *data) +{ + (void)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; + } + + setenv("DISPLAY", wl_state.xwayland->display_name, 1); + wwarning("wl_backend: XWayland ready on %s — opening WM Xlib connection", + wl_state.xwayland->display_name); + + 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; +} + +void +handle_xwayland_new_surface(struct wl_listener *listener, void *data) +{ + struct wlr_xwayland_surface *xw = data; + (void)listener; + + if (xw->override_redirect) { + if (wl_state.rcontext_xwin && + xw->window_id == (xcb_window_t)wl_state.rcontext_xwin) + return; + + struct wl_or_view *ov = wmalloc(sizeof(*ov)); + memset(ov, 0, sizeof(*ov)); + ov->xw_surface = xw; + + ov->map_listener.notify = handle_or_map; + ov->unmap_listener.notify = handle_or_unmap; + ov->destroy_listener.notify = handle_or_destroy; + ov->set_geometry_listener.notify = handle_or_set_geometry; + + wl_signal_add(&xw->events.destroy, &ov->destroy_listener); + wl_signal_add(&xw->events.associate, &ov->map_listener); + wl_signal_add(&xw->events.dissociate, &ov->unmap_listener); + wl_signal_add(&xw->events.set_geometry, &ov->set_geometry_listener); + + wl_list_insert(&wl_state.or_list, &ov->link); + + if (xw->surface) { + if (wl_state.scene) { + struct wlr_scene_tree *_op = wl_state.level_trees[WL_LAYER_MENU] + ? wl_state.level_trees[WL_LAYER_MENU] : &wl_state.scene->tree; + ov->scene_node = wlr_scene_subsurface_tree_create(_op, xw->surface); + if (ov->scene_node) + wlr_scene_node_set_position(&ov->scene_node->node, + xw->x, xw->y); + } + } + return; + } + + if (wl_state.rcontext_xwin && xw->window_id == (xcb_window_t)wl_state.rcontext_xwin) { + wwarning("wl_backend: xwayland rcontext drawable 0x%x skipped", + (unsigned)xw->window_id); + return; + } + + if (w_global.screen_count == 0) { + wwarning("wl_backend: xwayland new surface 0x%x during startup — skipped", + (unsigned)xw->window_id); + return; + } + + if (!xw->class && !xw->title) { + wwarning("wl_backend: xwayland surface 0x%x has no class/title yet — deferring", + (unsigned)xw->window_id); + } + + 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_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.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); + wl_signal_add(&xw->events.set_geometry, &view->xw_geometry_listener); + + wl_list_insert(&wl_state.toplevel_list, &view->link); + wl_state.managed_surface_count++; + + view->x = (wl_state.output ? (int)wl_state.output->width : 1920) / 4; + view->y = (wl_state.output ? (int)wl_state.output->height : 1080) / 4; + + 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, + xw->class ? xw->class : "(none)", + xw->title ? xw->title : "(none)", + xw->x, xw->y); + + { + int cw = (xw->width >= 64) ? xw->width : 640; + int ch = (xw->height >= 64) ? xw->height : 480; + wlr_xwayland_surface_configure(xw, + (int16_t)view->x, (int16_t)view->y, cw, ch); + } +}