wayland: Phase 18 — nested compositor + wlr_scene rendering
Two major additions: 1. Nested Wayland mode (WMAKER_WAYLAND_NESTED=1): wlr_multi_backend wraps wlr_wl_backend (visual output as a host Wayland window) and wlr_headless_backend (for synthetic input devices). wlr_wl_output_create queues the output window before start; handle_backend_new_output initialises the render pipeline (wlr_output_init_render, preferred mode, enable, commit) and sets the window title to "Window Maker". 2. wlr_scene compositing (both modes): wlr_scene_create() produces a scene graph; per-surface scene nodes are added in handle_toplevel_map (wlr_scene_xdg_surface_create) and handle_xwayland_map (wlr_scene_subsurface_tree_create). Positions are kept in sync via wlr_scene_node_set_position from wl_window_move and wl_client_synth_configure. handle_output_frame calls wlr_scene_output_commit on every output vsync to render and send frame callbacks. Other changes: - wlr_allocator_autocreate added; wlr_output_init_render called in handle_backend_new_output for proper buffer allocation - headless_inner / wl_backend_inner stored separately so wlr_headless_add_input_device always uses the headless sub-backend - frame_timer_cb skips wlr_output_send_frame in nested mode (host drives frame schedule via output->events.frame) - Scene nodes removed on surface destroy; allocator/scene torn down in wl_display_close Smoke test: new "scene check" verifies "scene graph created" and "headless compositor mode" log lines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5b1590607c
commit
f4cc8a803e
|
|
@ -65,6 +65,13 @@
|
|||
* 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.
|
||||
* Phase 18: nested Wayland compositor + wlr_scene rendering.
|
||||
* If WMAKER_WAYLAND_NESTED=1 is set, a wlr_wl_backend is used alongside the
|
||||
* headless backend (via wlr_multi_backend) so the compositor appears as a
|
||||
* Wayland window on the host desktop. wlr_scene provides a scene graph for
|
||||
* compositing client surfaces; wlr_scene_output_commit renders each frame.
|
||||
* A per-view scene node is created for each XDG/XWayland surface and kept
|
||||
* in sync with the WM-assigned position.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
|
|
@ -90,6 +97,10 @@
|
|||
#include <wlr/types/wlr_xdg_shell.h>
|
||||
#include <wlr/types/wlr_xdg_decoration_v1.h>
|
||||
#include <wlr/xwayland.h> /* Phase 17: XWayland support */
|
||||
#include <wlr/backend/wayland.h> /* Phase 18: nested Wayland backend */
|
||||
#include <wlr/backend/multi.h> /* Phase 18: multi-backend */
|
||||
#include <wlr/types/wlr_scene.h> /* Phase 18: scene graph compositing */
|
||||
#include <wlr/render/allocator.h> /* Phase 18: buffer allocator */
|
||||
#include <wlr/util/box.h>
|
||||
/* Generated xdg-shell protocol implementation (Wayland glue code) */
|
||||
#include "protocols/xdg-shell-protocol.c"
|
||||
|
|
@ -128,10 +139,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. */
|
||||
/* Phase 17: XWayland — set for X11 windows, NULL for XDG toplevels. */
|
||||
struct wlr_xwayland_surface *xw_surface;
|
||||
/* Phase 18: scene graph node for this surface (NULL if scene unavailable). */
|
||||
struct wlr_scene_node *scene_node;
|
||||
WNativeWindow id; /* synthetic handle, >= 0x10000000 */
|
||||
int x, y; /* Phase 55: WM-assigned position */
|
||||
long *wm_state; /* Phase 56: saved WM state or NULL */
|
||||
|
|
@ -195,6 +206,15 @@ struct wl_compositor_state {
|
|||
/* Phase 17: XWayland server for X11 client support */
|
||||
struct wlr_xwayland *xwayland;
|
||||
struct wl_listener xwayland_new_surface_listener;
|
||||
/* Phase 18: nested Wayland + wlr_scene */
|
||||
int is_nested; /* 1 if using wlr_wl_backend */
|
||||
struct wlr_backend *headless_inner; /* headless backend (always) */
|
||||
struct wlr_backend *wl_backend_inner; /* wl backend (nested only) */
|
||||
struct wlr_allocator *allocator;
|
||||
struct wlr_scene *scene;
|
||||
struct wlr_scene_output *scene_output;
|
||||
struct wl_listener backend_new_output_listener;
|
||||
struct wl_listener output_frame_listener;
|
||||
char socket_name[64]; /* Phase 34: e.g. "wayland-0" */
|
||||
int started; /* non-zero after successful display_open */
|
||||
};
|
||||
|
|
@ -212,7 +232,11 @@ static int
|
|||
frame_timer_cb(void *data)
|
||||
{
|
||||
(void)data;
|
||||
if (wl_state.output)
|
||||
/* Phase 18: in headless mode, drive frames via the timer.
|
||||
* In nested mode, the host compositor drives frames via output->events.frame
|
||||
* (handle_output_frame); we only need to trigger the scene commit here for
|
||||
* headless so clients still receive frame callbacks at ~60 fps. */
|
||||
if (!wl_state.is_nested && wl_state.output)
|
||||
wlr_output_send_frame(wl_state.output);
|
||||
if (wl_state.frame_timer)
|
||||
wl_event_source_timer_update(wl_state.frame_timer, 16);
|
||||
|
|
@ -234,6 +258,9 @@ static void wl_pointer_warp_to(int x, int y);
|
|||
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);
|
||||
/* Phase 18: output lifecycle and frame rendering forward declarations. */
|
||||
static void handle_backend_new_output(struct wl_listener *listener, void *data);
|
||||
static void handle_output_frame(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);
|
||||
|
|
@ -416,6 +443,15 @@ handle_toplevel_map(struct wl_listener *listener, void *data)
|
|||
* (auto_focus=1 set in wl_screen_create_internals); the
|
||||
* direct wl_focus_set() call is no longer needed here. */
|
||||
|
||||
/* Phase 18: add XDG surface to the scene graph. */
|
||||
if (wl_state.scene && !view->scene_node) {
|
||||
view->scene_node = wlr_scene_xdg_surface_create(
|
||||
&wl_state.scene->node, view->xdg_surface);
|
||||
if (view->scene_node)
|
||||
wlr_scene_node_set_position(view->scene_node,
|
||||
view->x, view->y);
|
||||
}
|
||||
|
||||
/* Phase 13: warp virtual pointer to the centre of the
|
||||
* newly managed window so pointer focus is immediately
|
||||
* delivered to its surface. The warp calls
|
||||
|
|
@ -466,6 +502,11 @@ handle_toplevel_destroy(struct wl_listener *listener, void *data)
|
|||
wl_list_remove(&view->destroy_listener.link);
|
||||
wl_list_remove(&view->title_listener.link);
|
||||
wl_list_remove(&view->link);
|
||||
/* Phase 18: remove scene node. */
|
||||
if (view->scene_node) {
|
||||
wlr_scene_node_destroy(view->scene_node);
|
||||
view->scene_node = NULL;
|
||||
}
|
||||
wl_state.managed_surface_count--;
|
||||
if (view->wm_state) wfree(view->wm_state); /* Phase 56 */
|
||||
if (view->wm_command) { /* Phase 57 */
|
||||
|
|
@ -522,6 +563,14 @@ handle_xwayland_map(struct wl_listener *listener, void *data)
|
|||
wwin->wm_class ? wwin->wm_class : "(none)",
|
||||
wwin->wm_instance ? wwin->wm_instance : "(none)",
|
||||
wwin->client.width, wwin->client.height, (int)xw->pid);
|
||||
/* Phase 18: add XWayland surface to scene graph. */
|
||||
if (wl_state.scene && !view->scene_node && xw->surface) {
|
||||
view->scene_node = wlr_scene_subsurface_tree_create(
|
||||
&wl_state.scene->node, xw->surface);
|
||||
if (view->scene_node)
|
||||
wlr_scene_node_set_position(view->scene_node,
|
||||
view->x, view->y);
|
||||
}
|
||||
int cx = view->x + wwin->client.width / 2;
|
||||
int cy = view->y + wwin->client.height / 2;
|
||||
wl_pointer_warp_to(cx, cy);
|
||||
|
|
@ -564,6 +613,11 @@ handle_xwayland_destroy(struct wl_listener *listener, void *data)
|
|||
wl_list_remove(&view->destroy_listener.link);
|
||||
wl_list_remove(&view->title_listener.link);
|
||||
wl_list_remove(&view->link);
|
||||
/* Phase 18: remove scene node. */
|
||||
if (view->scene_node) {
|
||||
wlr_scene_node_destroy(view->scene_node);
|
||||
view->scene_node = NULL;
|
||||
}
|
||||
wl_state.managed_surface_count--;
|
||||
if (view->wm_state) wfree(view->wm_state);
|
||||
if (view->wm_command) {
|
||||
|
|
@ -760,6 +814,76 @@ handle_keyboard_modifiers(struct wl_listener *listener, void *data)
|
|||
wlr_seat_keyboard_notify_modifiers(wl_state.seat, &kb->modifiers);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Phase 18: output frame rendering and backend new-output handler */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* handle_output_frame — fires on every output vsync/frame event.
|
||||
* Calls wlr_scene_output_commit which renders the scene graph (all mapped
|
||||
* surfaces at their current positions) and sends Wayland frame callbacks
|
||||
* to clients so they know they can submit the next buffer.
|
||||
*/
|
||||
static void
|
||||
handle_output_frame(struct wl_listener *listener, void *data)
|
||||
{
|
||||
(void)listener; (void)data;
|
||||
if (wl_state.scene_output)
|
||||
wlr_scene_output_commit(wl_state.scene_output);
|
||||
}
|
||||
|
||||
/*
|
||||
* handle_backend_new_output — fires when a new output is added to the backend.
|
||||
* For the headless backend this fires synchronously after wlr_headless_add_output;
|
||||
* for the nested Wayland backend it fires when the host compositor creates the
|
||||
* output window. Initialises the render pipeline and wires the frame handler.
|
||||
*/
|
||||
static void
|
||||
handle_backend_new_output(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wlr_output *output = data;
|
||||
(void)listener;
|
||||
|
||||
if (wl_state.output)
|
||||
return; /* we only manage one output */
|
||||
|
||||
wl_state.output = output;
|
||||
|
||||
/* Initialise the render pipeline for this output. */
|
||||
if (wl_state.allocator && wl_state.renderer)
|
||||
wlr_output_init_render(output, wl_state.allocator, wl_state.renderer);
|
||||
|
||||
/* Enable the output: select preferred mode, then commit. */
|
||||
{
|
||||
struct wlr_output_mode *mode = wlr_output_preferred_mode(output);
|
||||
if (mode) wlr_output_set_mode(output, mode);
|
||||
}
|
||||
wlr_output_enable(output, true);
|
||||
if (!wlr_output_commit(output))
|
||||
wwarning("wl_backend: initial output commit failed");
|
||||
|
||||
/* Register with the output layout. */
|
||||
if (wl_state.output_layout)
|
||||
wlr_output_layout_add_auto(wl_state.output_layout, output);
|
||||
|
||||
/* Set window title when running as a nested compositor. */
|
||||
if (wl_state.is_nested && wl_state.wl_backend_inner &&
|
||||
wlr_backend_is_wl(wl_state.wl_backend_inner))
|
||||
wlr_wl_output_set_title(output, "Window Maker");
|
||||
|
||||
/* Create scene output and wire the frame renderer. */
|
||||
if (wl_state.scene) {
|
||||
wl_state.scene_output = wlr_scene_output_create(wl_state.scene, output);
|
||||
wl_state.output_frame_listener.notify = handle_output_frame;
|
||||
wl_signal_add(&output->events.frame, &wl_state.output_frame_listener);
|
||||
wwarning("wl_backend: scene output created for %s (%dx%d)",
|
||||
output->name, output->width, output->height);
|
||||
}
|
||||
|
||||
wwarning("wl_backend: new output %s (%dx%d)",
|
||||
output->name, output->width, output->height);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Display lifecycle (Phase 32 / Phase 34) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -779,17 +903,53 @@ wl_display_open(const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
wl_state.backend = wlr_headless_backend_create(wl_state.display);
|
||||
if (!wl_state.backend) {
|
||||
wwarning("wl_backend: failed to create wlroots headless backend");
|
||||
/* Phase 18: choose backend — nested Wayland if WMAKER_WAYLAND_NESTED=1
|
||||
* is set (run inside an existing Wayland compositor), otherwise headless.
|
||||
* Both modes always include a headless backend (headless_inner) so that
|
||||
* wlr_headless_add_input_device works regardless of nesting. */
|
||||
{
|
||||
const char *nested_env = getenv("WMAKER_WAYLAND_NESTED");
|
||||
wl_state.is_nested = (nested_env && nested_env[0]) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Always create a headless backend for input devices. */
|
||||
wl_state.headless_inner = wlr_headless_backend_create(wl_state.display);
|
||||
if (!wl_state.headless_inner) {
|
||||
wwarning("wl_backend: failed to create headless backend");
|
||||
wl_display_destroy(wl_state.display);
|
||||
wl_state.display = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
wl_state.renderer = wlr_renderer_autocreate(wl_state.backend);
|
||||
if (wl_state.is_nested) {
|
||||
/* Nested: multi-backend wrapping nested Wayland + headless. */
|
||||
wl_state.wl_backend_inner = wlr_wl_backend_create(wl_state.display, NULL);
|
||||
if (!wl_state.wl_backend_inner) {
|
||||
wwarning("wl_backend: failed to create nested Wayland backend; falling back to headless");
|
||||
wl_state.is_nested = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (wl_state.is_nested && wl_state.wl_backend_inner) {
|
||||
struct wlr_backend *multi = wlr_multi_backend_create(wl_state.display);
|
||||
wlr_multi_backend_add(multi, wl_state.wl_backend_inner);
|
||||
wlr_multi_backend_add(multi, wl_state.headless_inner);
|
||||
wl_state.backend = multi;
|
||||
wwarning("wl_backend: nested Wayland compositor mode");
|
||||
} else {
|
||||
wl_state.backend = wl_state.headless_inner;
|
||||
wwarning("wl_backend: headless compositor mode");
|
||||
}
|
||||
|
||||
/* Renderer: prefer the Wayland backend (EGL) in nested mode; fall back
|
||||
* to headless (pixman software) for the pure-headless case. */
|
||||
{
|
||||
struct wlr_backend *render_src = wl_state.wl_backend_inner
|
||||
? wl_state.wl_backend_inner : wl_state.headless_inner;
|
||||
wl_state.renderer = wlr_renderer_autocreate(render_src);
|
||||
}
|
||||
if (!wl_state.renderer) {
|
||||
wwarning("wl_backend: failed to create wlroots renderer");
|
||||
wwarning("wl_backend: failed to create renderer");
|
||||
wlr_backend_destroy(wl_state.backend);
|
||||
wl_display_destroy(wl_state.display);
|
||||
wl_state.backend = NULL;
|
||||
|
|
@ -797,8 +957,29 @@ wl_display_open(const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Allocator (Phase 18) — needed for wlr_output_init_render. */
|
||||
{
|
||||
struct wlr_backend *alloc_src = wl_state.wl_backend_inner
|
||||
? wl_state.wl_backend_inner : wl_state.headless_inner;
|
||||
wl_state.allocator = wlr_allocator_autocreate(alloc_src, wl_state.renderer);
|
||||
}
|
||||
if (!wl_state.allocator)
|
||||
wwarning("wl_backend: allocator unavailable; wlr_output_init_render will be skipped");
|
||||
|
||||
/* Phase 18: wire new_output BEFORE starting the backend so we catch
|
||||
* outputs that appear synchronously during wlr_backend_start. */
|
||||
wl_state.backend_new_output_listener.notify = handle_backend_new_output;
|
||||
wl_signal_add(&wl_state.backend->events.new_output,
|
||||
&wl_state.backend_new_output_listener);
|
||||
|
||||
/* For nested mode, request an output window before start so it is
|
||||
* created during wlr_backend_start (wlr_wl_output_create returns NULL
|
||||
* before the backend is started, which is the expected "queued" path). */
|
||||
if (wl_state.is_nested && wl_state.wl_backend_inner)
|
||||
wlr_wl_output_create(wl_state.wl_backend_inner);
|
||||
|
||||
if (!wlr_backend_start(wl_state.backend)) {
|
||||
wwarning("wl_backend: failed to start wlroots headless backend");
|
||||
wwarning("wl_backend: failed to start backend");
|
||||
wlr_renderer_destroy(wl_state.renderer);
|
||||
wlr_backend_destroy(wl_state.backend);
|
||||
wl_display_destroy(wl_state.display);
|
||||
|
|
@ -808,6 +989,11 @@ wl_display_open(const char *name)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* For headless mode, add the output explicitly after start; this fires
|
||||
* handle_backend_new_output synchronously via backend->events.new_output. */
|
||||
if (!wl_state.is_nested)
|
||||
wlr_headless_add_output(wl_state.headless_inner, 1920, 1080);
|
||||
|
||||
/* Phase 34: create the UNIX socket so Wayland clients can connect. */
|
||||
sock = wl_display_add_socket_auto(wl_state.display);
|
||||
if (!sock) {
|
||||
|
|
@ -826,11 +1012,18 @@ wl_display_open(const char *name)
|
|||
|
||||
/* Phase 34: output layout for multi-head geometry tracking. */
|
||||
wl_state.output_layout = wlr_output_layout_create();
|
||||
/* (output is added to layout inside handle_backend_new_output) */
|
||||
|
||||
/* Add a single 1920×1080 headless output (screen 0). */
|
||||
wl_state.output = wlr_headless_add_output(wl_state.backend, 1920, 1080);
|
||||
if (wl_state.output && wl_state.output_layout)
|
||||
wlr_output_layout_add_auto(wl_state.output_layout, wl_state.output);
|
||||
/* Phase 18: create the scene graph for compositing. */
|
||||
wl_state.scene = wlr_scene_create();
|
||||
if (wl_state.scene)
|
||||
wwarning("wl_backend: scene graph created");
|
||||
else
|
||||
wwarning("wl_backend: wlr_scene_create failed (no compositing)");
|
||||
|
||||
/* Expose the output as a wl_output global so clients get geometry. */
|
||||
if (wl_state.output)
|
||||
wlr_output_create_global(wl_state.output);
|
||||
|
||||
/* Phase 35: advertise core Wayland protocol globals. */
|
||||
wl_state.compositor =
|
||||
|
|
@ -852,16 +1045,15 @@ wl_display_open(const char *name)
|
|||
wwarning("wl_backend: xdg-decoration-manager created");
|
||||
}
|
||||
|
||||
/* Phase 36: advertise wl_shm (needed by SHM buffer clients) and
|
||||
* expose the headless output as a wl_output global. */
|
||||
/* Phase 36: advertise wl_shm (needed by SHM buffer clients). */
|
||||
wlr_renderer_init_wl_shm(wl_state.renderer, wl_state.display);
|
||||
if (wl_state.output)
|
||||
wlr_output_create_global(wl_state.output);
|
||||
/* wl_output global is created in handle_backend_new_output. */
|
||||
|
||||
/* Phase 43: create a headless keyboard and attach it to the seat so
|
||||
* wlr_seat_keyboard_notify_enter() can deliver keyboard focus. */
|
||||
* wlr_seat_keyboard_notify_enter() can deliver keyboard focus.
|
||||
* Always use headless_inner regardless of nested/headless mode. */
|
||||
wl_state.keyboard_dev =
|
||||
wlr_headless_add_input_device(wl_state.backend, WLR_INPUT_DEVICE_KEYBOARD);
|
||||
wlr_headless_add_input_device(wl_state.headless_inner, WLR_INPUT_DEVICE_KEYBOARD);
|
||||
if (wl_state.keyboard_dev) {
|
||||
/* Phase 77: set a default XKB keymap on the headless keyboard so that
|
||||
* wl_keysym_to_keycode() can populate wKeyBindings during StartUp().
|
||||
|
|
@ -897,9 +1089,10 @@ wl_display_open(const char *name)
|
|||
}
|
||||
|
||||
/* Phase 53: create a headless pointer so the seat advertises pointer
|
||||
* capability to clients (required for clients that bind wl_pointer). */
|
||||
* capability to clients (required for clients that bind wl_pointer).
|
||||
* Always use headless_inner regardless of nested/headless mode. */
|
||||
wl_state.pointer_dev =
|
||||
wlr_headless_add_input_device(wl_state.backend, WLR_INPUT_DEVICE_POINTER);
|
||||
wlr_headless_add_input_device(wl_state.headless_inner, WLR_INPUT_DEVICE_POINTER);
|
||||
|
||||
/* Phase 14: wire pointer motion/button listeners to the headless pointer
|
||||
* device so real pointer hardware (if added later) dispatches events. */
|
||||
|
|
@ -1000,6 +1193,19 @@ wl_display_close(void)
|
|||
wlr_output_layout_destroy(wl_state.output_layout);
|
||||
wl_state.output_layout = NULL;
|
||||
}
|
||||
/* Phase 18: destroy scene and allocator before backend. */
|
||||
if (wl_state.scene_output) {
|
||||
wlr_scene_output_destroy(wl_state.scene_output);
|
||||
wl_state.scene_output = NULL;
|
||||
}
|
||||
if (wl_state.scene) {
|
||||
wlr_scene_node_destroy(&wl_state.scene->node);
|
||||
wl_state.scene = NULL;
|
||||
}
|
||||
if (wl_state.allocator) {
|
||||
wlr_allocator_destroy(wl_state.allocator);
|
||||
wl_state.allocator = NULL;
|
||||
}
|
||||
if (wl_state.renderer)
|
||||
wlr_renderer_destroy(wl_state.renderer);
|
||||
if (wl_state.backend)
|
||||
|
|
@ -1007,11 +1213,13 @@ wl_display_close(void)
|
|||
if (wl_state.display)
|
||||
wl_display_destroy(wl_state.display);
|
||||
unsetenv("WAYLAND_DISPLAY");
|
||||
wl_state.renderer = NULL;
|
||||
wl_state.backend = NULL;
|
||||
wl_state.display = NULL;
|
||||
wl_state.output = NULL;
|
||||
wl_state.started = 0;
|
||||
wl_state.renderer = NULL;
|
||||
wl_state.backend = NULL;
|
||||
wl_state.headless_inner = NULL;
|
||||
wl_state.wl_backend_inner = NULL;
|
||||
wl_state.display = NULL;
|
||||
wl_state.output = NULL;
|
||||
wl_state.started = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -1687,6 +1895,9 @@ static void wl_window_move(WNativeWindow win, int x, int y)
|
|||
struct wl_toplevel_view *v = wl_find_view_by_id(win);
|
||||
if (!v) return;
|
||||
v->x = x; v->y = y;
|
||||
/* Phase 18: keep scene node in sync with WM position. */
|
||||
if (v->scene_node)
|
||||
wlr_scene_node_set_position(v->scene_node, x, 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);
|
||||
|
|
@ -1822,6 +2033,9 @@ wl_client_synth_configure(WNativeWindow client_win,
|
|||
if (!v) return;
|
||||
v->x = x;
|
||||
v->y = y;
|
||||
/* Phase 18: keep scene node in sync. */
|
||||
if (v->scene_node)
|
||||
wlr_scene_node_set_position(v->scene_node, x, y);
|
||||
if (v->xw_surface) {
|
||||
wlr_xwayland_surface_configure(v->xw_surface,
|
||||
(int16_t)x, (int16_t)y,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@
|
|||
# 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.
|
||||
# 40. (Phase 18) wlr_scene created; nested Wayland compositor mode when
|
||||
# WMAKER_WAYLAND_NESTED=1 (uses wlr_wl_backend + wlr_multi_backend),
|
||||
# headless mode otherwise. Scene nodes added for each XDG/XWayland
|
||||
# surface so client content is composited into the output frame.
|
||||
# Verified by "scene graph created" + "headless compositor mode" logs.
|
||||
# 39. (Phase 17) XWayland started: wlr_xwayland_create() is called in
|
||||
# wl_display_open(); compositor logs "XWayland started DISPLAY=:<n>"
|
||||
# so X11 clients (GNUstep apps) can connect. DISPLAY env var is set.
|
||||
|
|
@ -273,6 +278,17 @@ else
|
|||
die "compositor did not log XWayland startup status"
|
||||
fi
|
||||
|
||||
# -- 4h. Phase 18: scene graph must be created ---------------------------
|
||||
#
|
||||
# wl_display_open() logs "scene graph created" when wlr_scene_create()
|
||||
# succeeds (both nested and headless modes).
|
||||
grep -q 'scene graph created' /tmp/wl-smoke-stderr \
|
||||
|| die "compositor did not log scene graph creation (wlr_scene_create failed)"
|
||||
# Verify headless compositor mode (WMAKER_WAYLAND_NESTED not set in test)
|
||||
grep -q 'headless compositor mode' /tmp/wl-smoke-stderr \
|
||||
|| die "compositor did not log headless compositor mode"
|
||||
echo " scene check: scene graph created (headless compositor mode)"
|
||||
|
||||
# -- 5. Core Wayland globals must be advertised ---------------------------
|
||||
#
|
||||
# Phase 35: wl_compositor, wl_seat, and xdg_wm_base must appear in the
|
||||
|
|
|
|||
Loading…
Reference in New Issue