wayland: Phase 38 — per-toplevel view lifecycle and context store
Introduce struct wl_toplevel_view with map/unmap/destroy wl_signal listeners and a synthetic WNativeWindow ID (>= 0x10000000). Implement context_save/find/delete as a per-key linked list of (WNativeWindow, void*) entries — enables downstream policy code to look up views by handle. wl-smoke-test now verifies "toplevel mapped" appears in compositor stderr when weston-simple-shm creates its surface. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3e1fd061e9
commit
74a43f0180
|
|
@ -13,6 +13,8 @@
|
|||
* Phase 36: wl_shm and wl_output advertised; XDG toplevel listener wired.
|
||||
* Phase 37: frame callbacks driven by output frame event; XDG configure
|
||||
* sent on new toplevel so clients complete their first frame.
|
||||
* Phase 38: per-toplevel view struct with map/unmap/destroy lifecycle;
|
||||
* synthetic WNativeWindow IDs; context_save/find/delete implemented.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
|
|
@ -46,6 +48,35 @@
|
|||
#include "../backend.h"
|
||||
#include "wl_backend.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Phase 38: per-toplevel view and context store */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* wl_toplevel_view -- one entry per XDG toplevel managed by the compositor.
|
||||
* Holds the wlroots surface pointer, three lifecycle listeners, and a
|
||||
* synthetic WNativeWindow ID used as the policy-layer window handle.
|
||||
*/
|
||||
struct wl_toplevel_view {
|
||||
struct wlr_xdg_surface *xdg_surface;
|
||||
struct wl_listener map_listener;
|
||||
struct wl_listener unmap_listener;
|
||||
struct wl_listener destroy_listener;
|
||||
WNativeWindow id; /* synthetic handle, >= 0x10000000 */
|
||||
struct wl_list link; /* wl_state.toplevel_list */
|
||||
};
|
||||
|
||||
/*
|
||||
* Context store: three per-key linked lists keyed by WNativeWindow.
|
||||
* Backed by a flat list of (win, data) pairs — fine for the small
|
||||
* number of windows a typical user has open.
|
||||
*/
|
||||
struct wl_ctx_entry {
|
||||
WNativeWindow win;
|
||||
void *data;
|
||||
struct wl_list link;
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Global compositor state */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -66,6 +97,10 @@ struct wl_compositor_state {
|
|||
int managed_surface_count; /* surfaces seen */
|
||||
/* Phase 37: periodic timer that drives client frame callbacks */
|
||||
struct wl_event_source *frame_timer;
|
||||
/* Phase 38: per-toplevel view list and context store */
|
||||
struct wl_list toplevel_list; /* wl_toplevel_view.link */
|
||||
struct wl_list ctx_lists[3]; /* one per WContextKey */
|
||||
unsigned long next_synthetic_id; /* >= 0x10000000 */
|
||||
char socket_name[64]; /* Phase 34: e.g. "wayland-0" */
|
||||
int started; /* non-zero after successful display_open */
|
||||
};
|
||||
|
|
@ -73,54 +108,113 @@ struct wl_compositor_state {
|
|||
static struct wl_compositor_state wl_state;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Phase 36 / 37: XDG surface / toplevel management and frame loop */
|
||||
/* Phase 36 / 37 / 38: XDG surface lifecycle, frame loop, view mgmt */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* Phase 37: Wayland event loop timer — drives client frame callbacks.
|
||||
* For the headless backend we avoid the allocator/swapchain path entirely
|
||||
* and call wlr_output_send_frame directly at ~60 fps. This causes
|
||||
* wlroots to send wl_surface.frame callbacks to all waiting clients.
|
||||
*/
|
||||
static int
|
||||
frame_timer_cb(void *data)
|
||||
{
|
||||
(void)data;
|
||||
|
||||
if (wl_state.output)
|
||||
wlr_output_send_frame(wl_state.output);
|
||||
|
||||
/* re-arm at ~60 fps */
|
||||
if (wl_state.frame_timer)
|
||||
wl_event_source_timer_update(wl_state.frame_timer, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helper: recover the view from one of its embedded listeners. */
|
||||
#define VIEW_FROM_LISTENER(ptr, field) \
|
||||
((struct wl_toplevel_view *)((char *)(ptr) - offsetof(struct wl_toplevel_view, field)))
|
||||
|
||||
/*
|
||||
* Phase 36: new XDG surface listener.
|
||||
* Sends an initial configure to toplevels so clients can commit their
|
||||
* first buffer (size 0,0 = "use whatever size you want").
|
||||
* Phase 38: toplevel map — surface is ready to be managed.
|
||||
*/
|
||||
static void
|
||||
handle_toplevel_map(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, map_listener);
|
||||
(void)data;
|
||||
|
||||
wwarning("wl_backend: toplevel mapped id=0x%lx app_id=%s title=%s",
|
||||
view->id,
|
||||
view->xdg_surface->toplevel->app_id ? view->xdg_surface->toplevel->app_id : "(none)",
|
||||
view->xdg_surface->toplevel->title ? view->xdg_surface->toplevel->title : "(none)");
|
||||
|
||||
/* TODO Phase 39: call wManageWindow(scr, view->id) once context
|
||||
* store can resolve the synthetic ID to this view. */
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 38: toplevel unmap — surface hidden (minimised or gone).
|
||||
*/
|
||||
static void
|
||||
handle_toplevel_unmap(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wl_toplevel_view *view = VIEW_FROM_LISTENER(listener, unmap_listener);
|
||||
(void)data;
|
||||
|
||||
wwarning("wl_backend: toplevel unmapped id=0x%lx", view->id);
|
||||
/* TODO Phase 39: call wUnmanageWindow */
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 38: toplevel destroy — free the view.
|
||||
*/
|
||||
static void
|
||||
handle_toplevel_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->link);
|
||||
wl_state.managed_surface_count--;
|
||||
wfree(view);
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 36 / 38: new XDG surface listener.
|
||||
* Allocates a wl_toplevel_view, assigns a synthetic WNativeWindow ID,
|
||||
* wires the three lifecycle listeners, and schedules the initial configure.
|
||||
*/
|
||||
static void
|
||||
handle_xdg_new_surface(struct wl_listener *listener, void *data)
|
||||
{
|
||||
struct wlr_xdg_surface *xdg_surface = data;
|
||||
struct wlr_xdg_surface *xdg_surface = data;
|
||||
struct wl_toplevel_view *view;
|
||||
(void)listener;
|
||||
|
||||
if (xdg_surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL)
|
||||
return; /* ignore popups for now */
|
||||
|
||||
view = wmalloc(sizeof(*view));
|
||||
memset(view, 0, sizeof(*view));
|
||||
view->xdg_surface = xdg_surface;
|
||||
view->id = wl_state.next_synthetic_id++;
|
||||
|
||||
view->map_listener.notify = handle_toplevel_map;
|
||||
view->unmap_listener.notify = handle_toplevel_unmap;
|
||||
view->destroy_listener.notify = handle_toplevel_destroy;
|
||||
|
||||
wl_signal_add(&xdg_surface->events.map, &view->map_listener);
|
||||
wl_signal_add(&xdg_surface->events.unmap, &view->unmap_listener);
|
||||
wl_signal_add(&xdg_surface->events.destroy, &view->destroy_listener);
|
||||
|
||||
wl_list_insert(&wl_state.toplevel_list, &view->link);
|
||||
wl_state.managed_surface_count++;
|
||||
wwarning("wl_backend: new XDG toplevel #%d (app_id=%s title=%s)",
|
||||
wl_state.managed_surface_count,
|
||||
xdg_surface->toplevel->app_id ? xdg_surface->toplevel->app_id : "(none)",
|
||||
xdg_surface->toplevel->title ? xdg_surface->toplevel->title : "(none)");
|
||||
|
||||
/* Phase 37: send the initial configure so the client can commit. */
|
||||
wwarning("wl_backend: new XDG toplevel id=0x%lx app_id=%s title=%s",
|
||||
view->id,
|
||||
xdg_surface->toplevel->app_id ? xdg_surface->toplevel->app_id : "(none)",
|
||||
xdg_surface->toplevel->title ? xdg_surface->toplevel->title : "(none)");
|
||||
|
||||
/* Phase 37: initial configure so the client can commit its first buffer. */
|
||||
wlr_xdg_surface_schedule_configure(xdg_surface);
|
||||
|
||||
/* TODO: create WWindow and call policy layer (wManageWindow equivalent)
|
||||
* once the surface is committed (use map event on toplevel). */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -209,6 +303,16 @@ wl_display_open(const char *name)
|
|||
if (wl_state.output)
|
||||
wlr_output_create_global(wl_state.output);
|
||||
|
||||
/* Phase 38: initialise per-toplevel tracking and context store. */
|
||||
wl_list_init(&wl_state.toplevel_list);
|
||||
{
|
||||
int ki;
|
||||
for (ki = 0; ki < 3; ki++)
|
||||
wl_list_init(&wl_state.ctx_lists[ki]);
|
||||
}
|
||||
wl_state.next_synthetic_id = 0x10000000UL;
|
||||
wl_state.managed_surface_count = 0;
|
||||
|
||||
/* Phase 36: listen for new XDG toplevel surfaces. */
|
||||
wl_state.xdg_new_surface_listener.notify = handle_xdg_new_surface;
|
||||
wl_signal_add(&wl_state.xdg_shell->events.new_surface,
|
||||
|
|
@ -336,20 +440,53 @@ wl_frame_configure(WNativeWindow win, int x, int y, int width, int height)
|
|||
{ (void)win; (void)x; (void)y; (void)width; (void)height; }
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Context (window → object lookup table) */
|
||||
/* Context (window → object lookup table) — Phase 38 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
wl_context_save(WNativeWindow win, WContextKey key, void *data)
|
||||
{ (void)win; (void)key; (void)data; }
|
||||
{
|
||||
struct wl_list *list = &wl_state.ctx_lists[(int)key];
|
||||
struct wl_ctx_entry *e;
|
||||
|
||||
/* update existing entry if present */
|
||||
wl_list_for_each(e, list, link) {
|
||||
if (e->win == win) { e->data = data; return; }
|
||||
}
|
||||
/* new entry */
|
||||
e = wmalloc(sizeof(*e));
|
||||
e->win = win;
|
||||
e->data = data;
|
||||
wl_list_insert(list, &e->link);
|
||||
}
|
||||
|
||||
static void *
|
||||
wl_context_find(WNativeWindow win, WContextKey key)
|
||||
{ (void)win; (void)key; return NULL; }
|
||||
{
|
||||
struct wl_list *list = &wl_state.ctx_lists[(int)key];
|
||||
struct wl_ctx_entry *e;
|
||||
|
||||
wl_list_for_each(e, list, link) {
|
||||
if (e->win == win) return e->data;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
wl_context_delete(WNativeWindow win, WContextKey key)
|
||||
{ (void)win; (void)key; return 0; }
|
||||
{
|
||||
struct wl_list *list = &wl_state.ctx_lists[(int)key];
|
||||
struct wl_ctx_entry *e, *tmp;
|
||||
|
||||
wl_list_for_each_safe(e, tmp, list, link) {
|
||||
if (e->win == win) {
|
||||
wl_list_remove(&e->link);
|
||||
wfree(e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0; /* not found */
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Focus */
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
# 6. (Phase 36) wl_shm and wl_output advertised; a real SHM client
|
||||
# (weston-simple-shm) can connect without crashing the compositor
|
||||
# 7. (Phase 37) Frame callbacks fire; weston-simple-shm runs its render
|
||||
# loop (stays alive for 1 s, compositor still alive afterwards)
|
||||
# loop (stays alive for 3 s, compositor still alive afterwards)
|
||||
# 8. (Phase 38) Per-toplevel view lifecycle: compositor logs "toplevel
|
||||
# mapped" when weston-simple-shm creates its XDG toplevel surface
|
||||
#
|
||||
# This test grows as wl_backend vtable slots are implemented.
|
||||
#
|
||||
|
|
@ -141,6 +143,11 @@ if command -v weston-simple-shm >/dev/null 2>&1; then
|
|||
kill -0 "$WM_PID" 2>/dev/null \
|
||||
|| die "compositor crashed after weston-simple-shm ran"
|
||||
echo " client check: weston-simple-shm ran 3 s; compositor survived"
|
||||
|
||||
# -- 8. Phase 38: compositor must have logged "toplevel mapped" -------
|
||||
grep -q 'toplevel mapped' /tmp/wl-smoke-stderr \
|
||||
|| die "compositor did not log 'toplevel mapped' for weston-simple-shm"
|
||||
echo " lifecycle check: toplevel mapped event logged"
|
||||
else
|
||||
echo " client check: skipped (weston-simple-shm not installed)"
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue