decompose wl_backend.c: extract wl_xwayland.c
This commit is contained in:
parent
5a2faa765a
commit
c46adc93c5
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,380 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <wayland-server-core.h>
|
||||
#include <wlr/types/wlr_scene.h>
|
||||
#include <wlr/types/wlr_xdg_shell.h>
|
||||
#include <wlr/util/log.h>
|
||||
#include <wlr/xwayland.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <stddef.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <wlr/types/wlr_output.h>
|
||||
#include "../../screen.h"
|
||||
#include "window.h"
|
||||
#include "wl_types.h"
|
||||
#include <WINGs/WINGsP.h>
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue