// F02 — resize correctness (Wayland). Implements
// ../../../features/f02-resize.md on top of the proven xdg-shell scaffold
// (../scaffold/app.d, findings in ../../scaffold.md).
//
// Wayland never *imposes* a size — it *negotiates* one: every
// xdg_surface.configure carries a serial; the client must ack_configure that
// exact serial and then commit a buffer matching the configured size. This
// demo proves that contract under a programmatic resize storm and records
// what the compositor does when the contract is broken on purpose.
//
// What F02 adds over the scaffold:
// - EVERY configure is logged with its serial, suggested size, and the full
// xdg_toplevel states array (`configure serial=N size=WxH states=[...]`);
// - a larger storm: maximize → unmaximize → fullscreen (via
// xdg_toplevel.set_fullscreen) → unfullscreen → maximize → unmaximize;
// - buffer (re)allocation strategy is fully logged: `buffer_alloc`,
// `buffer_destroy reason=stale_size`, and every `wl_buffer.release`;
// - `--violate`: once, after acking a NEW-size maximized configure (where
// the size is a hard constraint per the xdg_toplevel.maximized state),
// commit a buffer of the OLD size and capture the compositor's reaction
// (protocol error payload, or "tolerated" after a grace period). The
// violation run is a separate invocation; normal runs stay clean.
//
// Under WSI_AUTO_EXIT=1 the storm is self-driven and the demo exits 0.
// Headless-safe: no compositor → SKIP, exit 0.
module (module) appapp;
import c; // ImportC: <wayland-client.h> + xdg-shell glue + wsi_* wrappers
import instrument;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.errnoD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html, errno.h
Source
core/stdc/errno.d
errno : (alias constant) app.EPROTO = int core.stdc.errno.EPROTO = 71EPROTO;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf, (alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) app.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp;
// ----------------------------------------------------------------- tunables
enum int (constant) int app.defaultWidth = 640defaultWidth = 640; // used while the compositor lets us pick (0x0 configure)
enum int (constant) int app.defaultHeight = 480defaultHeight = 480;
// Auto-exit storm choreography (frame-callback numbers, ~60 Hz):
enum int (constant) int app.maximizeAtFrame = 20maximizeAtFrame = 20;
enum int (constant) int app.unmaximizeAtFrame = 40unmaximizeAtFrame = 40;
enum int (constant) int app.fullscreenAtFrame = 60fullscreenAtFrame = 60; // xdg_toplevel.set_fullscreen(null) — compositor picks output
enum int (constant) int app.unfullscreenAtFrame = 80unfullscreenAtFrame = 80;
enum int (constant) int app.maximize2AtFrame = 100maximize2AtFrame = 100; // second cycle: prove the machine is re-entrant
enum int (constant) int app.unmaximize2AtFrame = 115unmaximize2AtFrame = 115;
enum int (constant) int app.autoExitFrames = 140autoExitFrames = 140; // ≈ 2.3 s at 60 Hz
enum long (constant) long app.autoExitUsCap = 3500000LautoExitUsCap = 3_500_000; // wall-clock backstop
enum long (constant) long app.violationGraceUs = 1000000LviolationGraceUs = 1_000_000; // no error within this → "tolerated"
// -------------------------------------------------------------------- state
/// One wl_shm-backed ARGB8888 buffer. `busy` is owned by the compositor
/// between wl_surface.commit and the wl_buffer.release event.
struct (struct) app.BufferOne wl_shm-backed ARGB8888 buffer. busy is owned by the compositor
between wl_surface.commit and the wl_buffer.release event.
Buffer
{
wl_buffer* (field) _error_ app.Buffer.handlehandle;
uint* (field) uint* app.Buffer.pixelspixels; // mmap'ed, shared with the compositor
(alias) object.size_t = ulongsize_t (field) ulong app.Buffer.byteSizebyteSize;
int (field) int app.Buffer.widthwidth, (field) int app.Buffer.heightheight;
bool (field) bool app.Buffer.busybusy;
}
enum (enum) app.OutcomeOutcome
{
(enum value) app.Outcome.none = 0none, // normal run, or violation not yet resolved
(enum value) app.Outcome.protocolError = 1protocolError,
(enum value) app.Outcome.tolerated = 2tolerated,
}
__gshared
{
wl_display* _error_ app.g_displayg_display;
wl_registry* _error_ app.g_registryg_registry;
wl_compositor* _error_ app.g_compositorg_compositor;
wl_shm* _error_ app.g_shmg_shm;
wl_seat* _error_ app.g_seatg_seat;
xdg_wm_base* _error_ app.g_wmBaseg_wmBase;
wl_surface* _error_ app.g_surfaceg_surface;
xdg_surface* _error_ app.g_xdgSurfaceg_xdgSurface;
xdg_toplevel* _error_ app.g_toplevelg_toplevel;
wl_callback* _error_ app.g_frameCbg_frameCb; // at most one outstanding frame callback
(struct) app.BufferOne wl_shm-backed ARGB8888 buffer. busy is owned by the compositor
between wl_surface.commit and the wl_buffer.release event.
Buffer[2] _error_ app.g_buffersg_buffers; // double buffering: paint one while the other is on screen
int (__gshared global) int app.g_widthg_width = defaultWidth; // last *acked* size — buffers must match it
int (__gshared global) int app.g_heightg_height = defaultHeight;
int (__gshared global) int app.g_floatingWidthg_floatingWidth = defaultWidth; // remembered for 0x0 ("you pick") configures
int (__gshared global) int app.g_floatingHeightg_floatingHeight = defaultHeight;
int (__gshared global) int app.g_pendingWidthg_pendingWidth; // from xdg_toplevel.configure (0 = client's choice)
int (__gshared global) int app.g_pendingHeightg_pendingHeight;
bool (__gshared global) bool app.g_pendingMaximizedg_pendingMaximized; // latched from the xdg_toplevel.configure states array
bool (__gshared global) bool app.g_pendingFullscreeng_pendingFullscreen;
bool (__gshared global) bool app.g_configuredg_configured; // first configure acked (may attach buffers from now on)
bool (__gshared global) bool app.g_presentedg_presented; // first frame callback after the first buffer commit
bool (__gshared global) bool app.g_runningg_running = true;
bool (__gshared global) bool app.g_autoExitg_autoExit;
int (__gshared global) int app.g_framesg_frames; // frame callbacks received
int (__gshared global) int app.g_commitsg_commits; // buffer commits sent
int (__gshared global) int app.g_configuresg_configures; // xdg_surface.configure events seen (== acks sent)
int (__gshared global) int app.g_sizeChangesg_sizeChanges; // configures that changed the acked size
int (__gshared global) int app.g_allocsg_allocs; // wl_buffer allocations
int (__gshared global) int app.g_reallocsg_reallocs; // stale-size buffer destroys
// --violate machinery:
bool (__gshared global) bool app.g_violateg_violate; // armed by the CLI flag
bool (__gshared global) bool app.g_violationDoneg_violationDone; // the wrong-sized commit went out
(enum) app.OutcomeOutcome (__gshared global) app.Outcome app.g_outcomeg_outcome;
long (__gshared global) long app.g_violationDeadlineUsg_violationDeadlineUs;
uint (__gshared global) uint app.g_errCodeg_errCode; // wl_display_get_protocol_error payload
uint (__gshared global) uint app.g_errObjectIdg_errObjectId;
const(char)* (__gshared global) const(char)* app.g_errInterfaceg_errInterface = "?";
char[160] (__gshared global) char[160] app.g_statesBufg_statesBuf = "\0"; // last states array, formatted
}
// -------------------------------------------------- states-array formatting
/// xdg_toplevel.state enum names (wayland-protocols 1.47, xdg-shell v6).
const(char)* const(char)* app.stateName(uint s) nothrow @nogcxdg_toplevel.state enum names (wayland-protocols 1.47, xdg-shell v6).
stateName(uint (parameter) uint ss) nothrow @nogc
{
switch ((parameter) uint ss)
{
case XDG_TOPLEVEL_STATE_MAXIMIZED:
return "maximized";
case XDG_TOPLEVEL_STATE_FULLSCREEN:
return "fullscreen";
case XDG_TOPLEVEL_STATE_RESIZING:
return "resizing";
case XDG_TOPLEVEL_STATE_ACTIVATED:
return "activated";
case XDG_TOPLEVEL_STATE_TILED_LEFT:
return "tiled_left";
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
return "tiled_right";
case XDG_TOPLEVEL_STATE_TILED_TOP:
return "tiled_top";
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
return "tiled_bottom";
case XDG_TOPLEVEL_STATE_SUSPENDED:
return "suspended";
default:
return "unknown";
}
}
/// Format the configure states wl_array as `a,b,c` into g_statesBuf (also
/// latches maximized/fullscreen for the xdg_surface.configure handler).
void app.latchStatesFormat the configure states wl_array as a,b,c into g_statesBuf (also
latches maximized/fullscreen for the xdg_surface.configure handler).
latchStates(const(wl_array)* states) nothrow @nogc
{
g_pendingMaximized = false;
g_pendingFullscreen = false;
(unresolved type) size_tsize_t _error_ offoff = 0;
g_statesBuf[0] = '\0';
const _error_ startstart = cast(const(uint)*) states.data;
immutable _error_ nn = states.states.sizesize / uint.sizeof;
foreach ((parameter) ii; 0 .. n)
{
immutable _error_ ss = start[i];
if (s == XDG_TOPLEVEL_STATE_MAXIMIZED)
g_pendingMaximized = true;
if (s == XDG_TOPLEVEL_STATE_FULLSCREEN)
g_pendingFullscreen = true;
if (off + 1 >= g_statesBuf.length)
break;
immutable _error_ wrotewrote = snprintf(g_statesBuf.ptr + off, g_statesBuf.length - off,
"%s%s", i ? ",".ptr : "".ptr, stateName(s));
if (wrote <= 0)
break;
off += wrote;
}
}
// ---------------------------------------------------------- shm buffer pool
/// (Re)allocate `b` so it matches `w`×`h`. Only ever called on non-busy
/// buffers, so destroying a stale-sized wl_buffer here is race-free. The
/// strategy — a finding F02 asks for — is per-resize realloc, lazily: a
/// stale-sized buffer lives until it is *picked* for reuse, never while the
/// compositor holds it.
bool app.ensureBuffer(Re)allocate b so it matches w×h. Only ever called on non-busy
buffers, so destroying a stale-sized wl_buffer here is race-free. The
strategy — a finding F02 asks for — is per-resize realloc, lazily: a
stale-sized buffer lives until it is picked for reuse, never while the
compositor holds it.
ensureBuffer(ref (struct) app.BufferOne wl_shm-backed ARGB8888 buffer. busy is owned by the compositor
between wl_surface.commit and the wl_buffer.release event.
Buffer (parameter) Buffer bb, int (parameter) int ww, int (parameter) int hh) nothrow @nogc
{
if (b.b.handlehandle !is null && (b.b.widthwidth != w || b.b.heightheight != h))
{
instrEvent("buffer_destroy", "size=%dx%d reason=stale_size", b.b.widthwidth, b.b.heightheight);
g_reallocs++;
wsi_buffer_destroy(b.b.handlehandle);
munmap(b.b.pixelspixels, b.b.byteSizebyteSize);
b = Buffer.init;
}
if (b.b.handlehandle !is null)
return true;
immutable _error_ stridestride = w * 4;
immutable _error_ sizesize = cast((unresolved type) size_tsize_t)(stride) * h;
immutable _error_ fdfd = memfd_create("wsi-f02", MFD_CLOEXEC);
if (fd < 0)
return false;
if (ftruncate(fd, cast(long) size) != 0)
{
close(fd);
return false;
}
void* _error_ memmem = mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mem is cast(void*)-1)
{
close(fd);
return false;
}
// The pool can be destroyed right after create_buffer: the buffer keeps
// the backing memory alive, and the compositor has dup'ed the fd.
wl_shm_pool* _error_ poolpool = wsi_shm_create_pool(g_shm, fd, cast(int) size);
b.b.handlehandle = wsi_shm_pool_create_buffer(pool, 0, w, h, stride, WL_SHM_FORMAT_ARGB8888);
wsi_shm_pool_destroy(pool);
close(fd);
wsi_buffer_add_listener(b.b.handlehandle, &g_bufferListener, &b);
b.b.pixelspixels = cast(uint*) mem;
b.b.byteSizebyteSize = size;
b.b.widthwidth = w;
b.b.heightheight = h;
b.b.busybusy = false;
g_allocs++;
instrEvent("buffer_alloc", "size=%dx%d bytes=%zu", w, h, size);
return true;
}
/// Corner-anchored diagonal gradient (F02 §1: geometry must visibly track the
/// window size) with a frame-driven blue channel so each redraw is observable.
void app.paintCorner-anchored diagonal gradient (F02 §1: geometry must visibly track the
window size) with a frame-driven blue channel so each redraw is observable.
paint(ref (struct) app.BufferOne wl_shm-backed ARGB8888 buffer. busy is owned by the compositor
between wl_surface.commit and the wl_buffer.release event.
Buffer (parameter) Buffer bb, int frame) nothrow @nogc
{
immutable _error_ maxXmaxX = b.b.widthwidth > 1 ? b.b.widthwidth - 1 : 1;
immutable _error_ maxYmaxY = b.b.heightheight > 1 ? b.b.heightheight - 1 : 1;
immutable uint _error_ blueblue = (frame * 2) & 0xff;
foreach ((parameter) yy; 0 .. b.b.heightheight)
{
uint* _error_ rowrow = b.b.pixelspixels + cast((unresolved type) size_tsize_t) y * b.b.widthwidth;
immutable uint _error_ gg = cast(uint)(y * 255 / maxY) << 8;
foreach ((parameter) xx; 0 .. b.b.widthwidth)
row[x] = 0xff00_0000 | (cast(uint)(x * 255 / maxX) << 16) | g | blue;
}
}
/// Paint a `w`×`h` buffer and commit it. `violation=true` bypasses the F02
/// size assertion — that is the single deliberate wrong-sized commit.
void void app.renderAt(int w, int h, bool violation) nothrow @nogcPaint a w×h buffer and commit it. ``violation=true bypasses the F02
size assertion — that is the single deliberate wrong-sized commit.
renderAt(int (parameter) int ww, int (parameter) int hh, bool (parameter) bool violationviolation) nothrow @nogc
{
(struct) app.BufferOne wl_shm-backed ARGB8888 buffer. busy is owned by the compositor
between wl_surface.commit and the wl_buffer.release event.
Buffer* (local variable) _error_ bufbuf = null;
foreach (ref (parameter) bb; g_buffers)
if (!b.b.busybusy)
{
buf = &b;
break;
}
if (buf is null)
{
// Both buffers held by the compositor: drop this frame. The
// wl_buffer.release handler re-renders as soon as one comes back.
instrEvent("frame_skipped", "reason=all_buffers_busy");
return;
}
if (!ensureBuffer(*buf, w, h))
{
(__gshared global) bool app.g_runningg_running = false;
return;
}
paint(*buf, g_frames);
// F02 contract: the committed buffer matches the acked configure size.
assert((parameter) bool violationviolation || (buf.(field) _error_ buf.widthwidth == (__gshared global) int app.g_widthg_width && buf.(field) _error_ buf.heightheight == (__gshared global) int app.g_heightg_height),
"committed buffer size does not match the acked configure size");
wsi_surface_attach(g_surface, buf.buf.handlehandle, 0, 0);
wsi_surface_damage_buffer(g_surface, 0, 0, buf.buf.widthwidth, buf.buf.heightheight);
if (g_frameCb is null) // keep exactly one frame callback in flight
{
g_frameCb = wsi_surface_frame(g_surface);
wsi_callback_add_listener(g_frameCb, &g_frameListener, null);
}
wsi_surface_commit(g_surface);
buf.buf.busybusy = true;
(__gshared global) int app.g_commitsg_commits++;
if ((__gshared global) int app.g_commitsg_commits == 1)
instrEvent("first_commit", "size=%dx%d", buf.buf.widthwidth, buf.buf.heightheight);
if ((parameter) bool violationviolation)
instrEvent("violation_commit", "size=%dx%d acked=%dx%d", buf.buf.widthwidth, buf.buf.heightheight,
g_width, g_height);
}
/// Commit a buffer matching the current acked size.
void void app.render() nothrow @nogcCommit a buffer matching the current acked size.
render() nothrow @nogc
{
void app.renderAt(int w, int h, bool violation) nothrow @nogcPaint a w×h buffer and commit it. ``violation=true bypasses the F02
size assertion — that is the single deliberate wrong-sized commit.
renderAt((__gshared global) int app.g_widthg_width, (__gshared global) int app.g_heightg_height, false);
}
// ---------------------------------------------------------------- listeners
// All callbacks are `extern (C) nothrow @nogc` to match the listener
// function-pointer types the ImportC `#pragma attribute` stamped in c.c.
extern (C) void app.onGlobalonGlobal(void* data, wl_registry* reg, uint name,
const(char)* (parameter) const(char)* ifaceiface, uint ver) nothrow @nogc
{
static uint uint capped(uint advertised, uint want) nothrow @nogccapped(uint (parameter) uint advertisedadvertised, uint (parameter) uint wantwant) nothrow @nogc
{
return advertised < want ? advertised : want;
}
if (strcmp(iface, wl_compositor_interface.name) == 0)
g_compositor = cast(wl_compositor*) wsi_registry_bind(reg, name,
&wl_compositor_interface, capped(ver, 4)); // v4: wl_surface.damage_buffer
else if (strcmp(iface, wl_shm_interface.name) == 0)
g_shm = cast(wl_shm*) wsi_registry_bind(reg, name, &wl_shm_interface, 1);
else if (strcmp(iface, xdg_wm_base_interface.name) == 0)
g_wmBase = cast(xdg_wm_base*) wsi_registry_bind(reg, name, &xdg_wm_base_interface, 1);
else if (strcmp(iface, wl_seat_interface.name) == 0)
g_seat = cast(wl_seat*) wsi_registry_bind(reg, name, &wl_seat_interface,
capped(ver, 2)); // v2: name event
else
return;
instrEvent("step", "name=wl_registry_bind iface=%s version=%u", iface, ver);
}
extern (C) void app.onGlobalRemoveonGlobalRemove(void* data, wl_registry* reg, uint name) nothrow @nogc
{
}
extern (C) void app.onWmBasePingonWmBasePing(void* data, xdg_wm_base* (parameter) xdg_wm_base* bb, uint serial) nothrow @nogc
{
wsi_wm_base_pong(b, serial); // liveness check — answer or be killed
instrEvent("ping_pong", "serial=%u", serial);
}
extern (C) void app.onSeatCapabilitiesonSeatCapabilities(void* data, wl_seat* (parameter) wl_seat* ss, uint caps) nothrow @nogc
{
instrEvent("seat_capabilities", "caps=0x%x", caps);
}
extern (C) void app.onSeatNameonSeatName(void* data, wl_seat* (parameter) wl_seat* ss, const(char)* name) nothrow @nogc
{
instrEvent("seat_name", "name=%s", name);
}
/// xdg_toplevel.configure: the compositor *suggests* a size (0×0 = "you
/// pick") plus the full states array. Only latched here; everything takes
/// effect atomically at the following xdg_surface.configure.
extern (C) void app.onToplevelConfigurexdg_toplevel.configure: the compositor suggests a size (0×0 = "you
pick") plus the full states array. Only latched here; everything takes
effect atomically at the following xdg_surface.configure.
onToplevelConfigure(void* data, xdg_toplevel* t, int (parameter) int ww, int (parameter) int hh,
wl_array* states) nothrow @nogc
{
g_pendingWidth = w;
g_pendingHeight = h;
latchStates(states);
instrEvent("xdg_toplevel_configure", "size=%dx%d states=[%s]", w, h, g_statesBuf.ptr);
}
/// xdg_surface.configure: the atomic "apply everything" event. Protocol
/// order proven in the WAYLAND_DEBUG trace: ack_configure(serial) *then* the
/// commit of a buffer matching the new size — except for the single armed
/// `--violate` commit, which deliberately commits the OLD size after acking.
extern (C) void app.onXdgSurfaceConfigurexdg_surface.configure: the atomic "apply everything" event. Protocol
order proven in the WAYLAND_DEBUG trace: ack_configure(serial) then the
commit of a buffer matching the new size — except for the single armed
--violate commit, which deliberately commits the OLD size after acking.
onXdgSurfaceConfigure(void* data, xdg_surface* (parameter) xdg_surface* ss, uint serial) nothrow @nogc
{
// 0x0 means "client picks": a robust client restores its remembered
// floating size, not a hardcoded default.
immutable _error_ ww = g_pendingWidth > 0 ? g_pendingWidth : g_floatingWidth;
immutable _error_ hh = g_pendingHeight > 0 ? g_pendingHeight : g_floatingHeight;
g_configures++;
instrEvent("configure", "serial=%u size=%dx%d states=[%s]", serial, w, h, g_statesBuf.ptr);
wsi_xdg_surface_ack_configure(s, serial);
instrStep("xdg_surface_ack_configure");
immutable _error_ resizedresized = w != g_width || h != g_height;
// --violate: maximized makes the configured size a hard constraint
// (xdg-shell: "the surface ... must have the configured size") — exactly
// the case where a wrong-sized commit is unambiguous. Ack was correct;
// the commit below is the one deliberate lie.
if (g_violate && !g_violationDone && g_configured && g_pendingMaximized && resized)
{
g_violationDone = true;
immutable _error_ oldWoldW = g_width;
immutable _error_ oldHoldH = g_height;
instrEvent("violation", "acked_serial=%u acked_size=%dx%d committing=%dx%d",
serial, w, h, oldW, oldH);
g_width = w; // record what we *should* be at, for the recovery path
g_height = h;
renderAt(oldW, oldH, true); // OLD size, on purpose
g_violationDeadlineUs = instrNowUs() + violationGraceUs;
return;
}
g_width = w;
g_height = h;
if (!g_pendingMaximized && !g_pendingFullscreen)
{
g_floatingWidth = w; // remember the floating size for 0x0 configures
g_floatingHeight = h;
}
if (!g_configured)
{
g_configured = true;
instrFirstConfigure();
render(); // the first buffer commit — the window becomes visible
}
else if (resized)
{
g_sizeChanges++;
instrResize(w, h, 1); // no fractional-scale binding in this demo
render(); // commit a matching buffer right away, don't wait a frame
}
}
extern (C) void app.onToplevelCloseonToplevelClose(void* data, xdg_toplevel* t) nothrow @nogc
{
instrCloseRequested();
g_running = false;
}
// Bound at xdg_wm_base v1, so these v4/v5 events never arrive; the listener
// struct generated from wayland-protocols 1.47 still has the slots to fill.
extern (C) void app.onToplevelConfigureBoundsonToplevelConfigureBounds(void* data, xdg_toplevel* t, int (parameter) int ww, int (parameter) int hh) nothrow @nogc
{
}
extern (C) void app.onToplevelWmCapabilitiesonToplevelWmCapabilities(void* data, xdg_toplevel* t, wl_array* caps) nothrow @nogc
{
}
/// wl_buffer.release: the compositor no longer reads the buffer; reuse it.
/// Logged because *when* this fires is the buffer-lifetime finding (weston
/// copies shm pixels at repaint and releases almost immediately).
extern (C) void app.onBufferReleasewl_buffer.release: the compositor no longer reads the buffer; reuse it.
Logged because when this fires is the buffer-lifetime finding (weston
copies shm pixels at repaint and releases almost immediately).
onBufferRelease(void* data, wl_buffer* (parameter) wl_buffer* bb) nothrow @nogc
{
auto _error_ bufbuf = cast((unresolved type) BufferBuffer*) data;
buf.buf.busybusy = false;
instrEvent("buffer_release", "size=%dx%d", buf.buf.widthwidth, buf.buf.heightheight);
// If a frame was dropped because both buffers were busy, recover here.
if (g_running && g_configured && g_frameCb is null)
render();
}
/// wl_surface.frame `done`: the compositor presented the surface and is
/// ready for the next frame. Drives the redraw loop, the resize storm, and
/// the violation grace-period check.
extern (C) void app.onFrameDonewl_surface.frame done: the compositor presented the surface and is
ready for the next frame. Drives the redraw loop, the resize storm, and
the violation grace-period check.
onFrameDone(void* data, wl_callback* cb, uint timeMs) nothrow @nogc
{
wsi_callback_destroy(cb); // one-shot object
g_frameCb = null;
g_frames++;
instrFrameCallback(timeMs);
if (!g_presented)
{
g_presented = true;
instrFirstPixelPresented();
}
// Violation epilogue: no protocol error within the grace period means
// the compositor tolerated the wrong-sized buffer.
if (g_violationDone && g_outcome == Outcome.Outcome.nonenone
&& instrNowUs() > g_violationDeadlineUs)
{
g_outcome = Outcome.Outcome.toleratedtolerated;
instrEvent("violation_outcome", "result=tolerated grace_us=%lld frames_after=%d",
violationGraceUs, g_frames);
g_running = false;
return;
}
if (g_autoExit && !g_violationDone)
{
switch (g_frames)
{
case maximizeAtFrame:
case maximize2AtFrame:
wsi_toplevel_set_maximized(g_toplevel);
instrStep("xdg_toplevel_set_maximized");
break;
case unmaximizeAtFrame:
case unmaximize2AtFrame:
wsi_toplevel_unset_maximized(g_toplevel);
instrStep("xdg_toplevel_unset_maximized");
break;
case fullscreenAtFrame:
wsi_toplevel_set_fullscreen(g_toplevel, null); // compositor picks the output
instrStep("xdg_toplevel_set_fullscreen");
break;
case unfullscreenAtFrame:
wsi_toplevel_unset_fullscreen(g_toplevel);
instrStep("xdg_toplevel_unset_fullscreen");
break;
default:
break;
}
}
if (g_autoExit && g_outcome == Outcome.Outcome.nonenone
&& (g_frames >= autoExitFrames || instrNowUs() > autoExitUsCap))
{
g_running = false;
return;
}
render();
}
__gshared wl_registry_listener _error_ app.g_registryListenerg_registryListener = {&onGlobal, &onGlobalRemove};
__gshared xdg_wm_base_listener _error_ app.g_wmBaseListenerg_wmBaseListener = {&onWmBasePing};
__gshared wl_seat_listener _error_ app.g_seatListenerg_seatListener = {&onSeatCapabilities, &onSeatName};
__gshared xdg_surface_listener _error_ app.g_xdgSurfaceListenerg_xdgSurfaceListener = {&onXdgSurfaceConfigure};
__gshared xdg_toplevel_listener _error_ app.g_toplevelListenerg_toplevelListener = {
&onToplevelConfigure, &onToplevelClose,
&onToplevelConfigureBounds, &onToplevelWmCapabilities
};
__gshared wl_buffer_listener _error_ app.g_bufferListenerg_bufferListener = {&onBufferRelease};
__gshared wl_callback_listener _error_ app.g_frameListenerg_frameListener = {&onFrameDone};
// ------------------------------------------------------------ error capture
/// wl_display_dispatch returned -1: pull the error off the display. For a
/// protocol error, wl_display_get_protocol_error yields the interface, error
/// code, and object id (the violation run's headline data; the human-readable
/// message only exists in libwayland's stderr log / WAYLAND_DEBUG trace).
void void app.captureDisplayError() nothrow @nogcwl_display_dispatch returned -1: pull the error off the display. For a
protocol error, wl_display_get_protocol_error yields the interface, error
code, and object id (the violation run's headline data; the human-readable
message only exists in libwayland's stderr log / WAYLAND_DEBUG trace).
captureDisplayError() nothrow @nogc
{
immutable (local variable) immutable(_error_) errerr = wl_display_get_error(g_display);
if (err == EPROTO)
{
wl_interface* (local variable) _error_ ifaceiface;
uint (local variable) uint idid;
immutable (local variable) immutable(_error_) codecode = wl_display_get_protocol_error(g_display, &iface, &id);
(__gshared global) uint app.g_errCodeg_errCode = code;
(__gshared global) uint app.g_errObjectIdg_errObjectId = (local variable) uint idid;
(__gshared global) const(char)* app.g_errInterfaceg_errInterface = iface !is null ? iface.name : "?";
if ((__gshared global) bool app.g_violationDoneg_violationDone && (__gshared global) app.Outcome app.g_outcomeg_outcome == (enum) app.OutcomeOutcome.(enum value) app.Outcome.none = 0none)
(__gshared global) app.Outcome app.g_outcomeg_outcome = (enum) app.OutcomeOutcome.(enum value) app.Outcome.protocolError = 1protocolError;
instrEvent("protocol_error", "interface=%s code=%u object_id=%u",
g_errInterface, code, id);
}
else
instrEvent("display_error", "errno=%d", err);
}
// ----------------------------------------------------------------- teardown
void void app.teardown() nothrow @nogcteardown() nothrow @nogc
{
instrEvent("teardown_start");
foreach (ref (parameter) bb; g_buffers)
if (b.b.handlehandle !is null)
{
wsi_buffer_destroy(b.b.handlehandle);
munmap(b.b.pixelspixels, b.b.byteSizebyteSize);
b = Buffer.init;
}
if (g_frameCb !is null)
wsi_callback_destroy(g_frameCb);
if (g_toplevel !is null)
wsi_toplevel_destroy(g_toplevel);
if (g_xdgSurface !is null)
wsi_xdg_surface_destroy(g_xdgSurface);
if (g_surface !is null)
wsi_surface_destroy(g_surface);
if (g_wmBase !is null)
wsi_wm_base_destroy(g_wmBase);
// The remaining globals have no destructor request at the bound versions;
// destroying the client-side proxy is the correct cleanup.
if (g_seat !is null)
wl_proxy_destroy(cast(wl_proxy*) g_seat);
if (g_shm !is null)
wl_proxy_destroy(cast(wl_proxy*) g_shm);
if (g_compositor !is null)
wl_proxy_destroy(cast(wl_proxy*) g_compositor);
if (g_registry !is null)
wl_proxy_destroy(cast(wl_proxy*) g_registry);
wl_display_disconnect(g_display);
instrEvent("teardown_done");
}
// --------------------------------------------------------------------- main
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
instrInit("f02-wayland");
const (local variable) const(char*) autoEnvautoEnv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_AUTO_EXIT");
(__gshared global) bool app.g_autoExitg_autoExit = (local variable) const(char*) autoEnvautoEnv !is null && *(local variable) const(char*) autoEnvautoEnv == '1';
foreach ((parameter) string argarg; (parameter) string[] argsargs[1 .. $])
if ((local variable) string argarg == "--violate")
(__gshared global) bool app.g_violateg_violate = true;
if ((__gshared global) bool app.g_violateg_violate)
instrEvent("violate_armed");
// 1. Connect (SKIP cleanly on headless hosts without a compositor).
g_display = wl_display_connect(null);
if (g_display is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: no Wayland compositor (wl_display_connect returned null)\n");
return 0;
}
instrStep("wl_display_connect");
// 2. Registry: discover and bind the globals a window needs.
g_registry = wsi_display_get_registry(g_display);
instrStep("wl_display_get_registry");
wsi_registry_add_listener(g_registry, &g_registryListener, null);
wl_display_roundtrip(g_display); // blocks; onGlobal binds during this
instrStep("wl_display_roundtrip");
if (g_compositor is null || g_shm is null || g_wmBase is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: compositor lacks a required global (wl_compositor/wl_shm/xdg_wm_base)\n");
void app.teardown() nothrow @nogcteardown();
return 0;
}
wsi_wm_base_add_listener(g_wmBase, &g_wmBaseListener, null);
if (g_seat !is null)
wsi_seat_add_listener(g_seat, &g_seatListener, null);
// 3. Build the window object tree: wl_surface → xdg_surface → xdg_toplevel.
g_surface = wsi_compositor_create_surface(g_compositor);
instrStep("wl_compositor_create_surface");
g_xdgSurface = wsi_wm_base_get_xdg_surface(g_wmBase, g_surface);
instrStep("xdg_wm_base_get_xdg_surface");
wsi_xdg_surface_add_listener(g_xdgSurface, &g_xdgSurfaceListener, null);
g_toplevel = wsi_xdg_surface_get_toplevel(g_xdgSurface);
instrStep("xdg_surface_get_toplevel");
wsi_toplevel_add_listener(g_toplevel, &g_toplevelListener, null);
wsi_toplevel_set_title(g_toplevel, "wsi-f02-resize");
instrStep("xdg_toplevel_set_title");
wsi_toplevel_set_app_id(g_toplevel, "wsi-f02-resize");
instrStep("xdg_toplevel_set_app_id");
instrWindowCreated();
// 4. The mandatory initial commit *without* a buffer (attaching one before
// the first configure is a protocol error). The compositor answers with
// the first xdg_surface.configure; the first pixel happens in its handler.
wsi_surface_commit(g_surface);
instrStep("wl_surface_commit");
// 5. Event loop. wl_display_dispatch flushes requests, blocks on the
// socket, and dispatches a batch of events; the listeners above do all
// the work. A -1 return after the violation is the compositor killing
// the connection — capture the protocol-error payload.
while ((__gshared global) bool app.g_runningg_running)
{
if (wl_display_dispatch(g_display) == -1)
{
void app.captureDisplayError() nothrow @nogcwl_display_dispatch returned -1: pull the error off the display. For a
protocol error, wl_display_get_protocol_error yields the interface, error
code, and object id (the violation run's headline data; the human-readable
message only exists in libwayland's stderr log / WAYLAND_DEBUG trace).
captureDisplayError();
break;
}
if ((__gshared global) bool app.g_autoExitg_autoExit && instrNowUs() > (constant) long app.autoExitUsCap = 3500000LautoExitUsCap + 2_000_000)
(__gshared global) bool app.g_runningg_running = false; // hard backstop if frame callbacks ever stall
}
// 6. Clean teardown: buffers → frame callback → role objects → surface →
// globals → registry → disconnect (children before parents).
void app.teardown() nothrow @nogcteardown();
if ((__gshared global) bool app.g_violateg_violate)
{
final switch ((__gshared global) app.Outcome app.g_outcomeg_outcome)
{
case (enum) app.OutcomeOutcome.(enum value) app.Outcome.protocolError = 1protocolError:
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("violation outcome: protocol error — interface=%s code=%u object_id=%u "
~ "(connection killed by the compositor)\n",
(__gshared global) const(char)* app.g_errInterfaceg_errInterface, (__gshared global) uint app.g_errCodeg_errCode, (__gshared global) uint app.g_errObjectIdg_errObjectId);
break;
case (enum) app.OutcomeOutcome.(enum value) app.Outcome.tolerated = 2tolerated:
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("violation outcome: tolerated — no protocol error within %lld ms; "
~ "compositor accepted a wrong-sized buffer against a maximized configure\n",
(constant) long app.violationGraceUs = 1000000LviolationGraceUs / 1000);
break;
case (enum) app.OutcomeOutcome.(enum value) app.Outcome.none = 0none:
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("violation outcome: NOT CAPTURED (violation never triggered)\n");
return 1;
}
// Both reactions are a successfully captured experiment → exit 0.
return 0;
}
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("ok: %d frames, %d commits, %d configures acked, %d size changes, "
~ "%d buffer allocs (%d stale-size reallocs), final size %dx%d\n",
(__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_commitsg_commits, (__gshared global) int app.g_configuresg_configures, (__gshared global) int app.g_sizeChangesg_sizeChanges, (__gshared global) int app.g_allocsg_allocs, (__gshared global) int app.g_reallocsg_reallocs,
(__gshared global) int app.g_widthg_width, (__gshared global) int app.g_heightg_height);
return 0;
}