// F11 scroll-fidelity demo — every wl_pointer axis event with its FULL native
// payload, on top of the xdg-shell scaffold (../scaffold/app.d; findings:
// ../../scaffold.md, ../../f11-scroll.md).
//
// The wl_seat is bound at min(advertised, 9) so the whole axis vocabulary is
// on the table: `axis` (continuous length, wl_fixed), `axis_value120`
// (high-resolution wheel 120ths, seat v8+, replacing the deprecated
// `axis_discrete`), `axis_source` (wheel / finger / continuous / wheel_tilt),
// `axis_stop` (end of a scroll sequence — e.g. fingers lifted; the kinetic-
// scroll trigger), and `axis_relative_direction` (seat v9+, natural-scrolling
// signal). Every raw event is logged as it arrives, then `wl_pointer.frame`
// closes the group and a `frame` line summarizes it — the frame is the
// atomicity contract: all events since the last frame belong to one logical
// hardware event and must be interpreted together.
//
// On top of the raw stream the demo:
// - accumulates axis_value120 into wheel DETENTS (carry the remainder —
// truncating loses high-resolution sub-detent steps),
// - scrolls a rendered ruler by the continuous `axis` length (over/under-
// scroll would be visible as tick drift),
// - emits per-gesture summaries (gesture = frames until axis_stop or a
// 400 ms idle gap): total value, total v120, detents, source.
//
// ./run.sh injects wheel scrolls via `wlrctl pointer scroll <dy> <dx>` under
// headless sway — what source/v120 a zwlr_virtual_pointer_v1 client produces
// is itself the finding about injection fidelity. The capability dance is the
// F12 lesson: headless sway's seat starts at capabilities=0, get_pointer
// before the capability ever existed is a fatal wlroots error, and each
// wlrctl invocation unplugs its device, so wl_pointer is destroyed/re-created
// on every capabilities edge. Headless-safe: no compositor → SKIP, exit 0; a
// seat-less compositor (headless weston) yields the registry probe only.
module (module) appapp;
import c; // ImportC: wayland-client + xdg-shell glue
import instrument;
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;
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.atoi = int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi, (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;
enum int (constant) int app.defaultWidth = 640defaultWidth = 640;
enum int (constant) int app.defaultHeight = 480defaultHeight = 480;
enum long (constant) long app.gestureGapUs = 400000LgestureGapUs = 400_000; // idle gap that closes a gesture
// -------------------------------------------------------------------- state
struct (struct) app.BufferBuffer
{
wl_buffer* (field) _error_ app.Buffer.handlehandle;
uint* (field) uint* app.Buffer.pixelspixels;
(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;
}
/// Everything that arrived for one axis since the last wl_pointer.frame.
struct (struct) app.AxisPendingEverything that arrived for one axis since the last wl_pointer.frame.
AxisPending
{
bool (field) bool app.AxisPending.hasValuehasValue, (field) bool app.AxisPending.hasV120hasV120, (field) bool app.AxisPending.stopstop, (field) bool app.AxisPending.hasDirectionhasDirection;
double (field) double app.AxisPending.valuevalue = 0;
int (field) int app.AxisPending.v120v120;
uint (field) uint app.AxisPending.directiondirection;
}
__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;
xdg_wm_base* _error_ app.g_wmBaseg_wmBase;
wl_seat* _error_ app.g_seatg_seat;
wl_pointer* _error_ app.g_pointerg_pointer;
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;
(struct) app.BufferBuffer[2] _error_ app.g_buffersg_buffers;
int (__gshared global) int app.g_widthg_width = defaultWidth, (__gshared global) int app.g_heightg_height = defaultHeight;
int (__gshared global) int app.g_pendingWidthg_pendingWidth, (__gshared global) int app.g_pendingHeightg_pendingHeight;
uint (__gshared global) uint app.g_seatVerAdvertisedg_seatVerAdvertised, (__gshared global) uint app.g_seatVerBoundg_seatVerBound;
// pending wl_pointer.frame group
(struct) app.AxisPendingEverything that arrived for one axis since the last wl_pointer.frame.
AxisPending[2] (__gshared global) app.AxisPending[2] app.g_pendg_pend; // [0]=vertical, [1]=horizontal
bool (__gshared global) bool app.g_hasSourceg_hasSource;
uint (__gshared global) uint app.g_sourceg_source;
int (__gshared global) int app.g_frameSeqg_frameSeq;
// gesture accumulation
bool (__gshared global) bool app.g_inGestureg_inGesture;
int (__gshared global) int app.g_gestureFramesg_gestureFrames;
double[2] (__gshared global) double[2] app.g_gestureValueg_gestureValue = [0, 0];
long[2] (__gshared global) long[2] app.g_gestureV120g_gestureV120 = [0, 0];
int (__gshared global) int app.g_gestureNumg_gestureNum;
bool (__gshared global) bool app.g_gestureHasSourceg_gestureHasSource;
uint (__gshared global) uint app.g_gestureSourceg_gestureSource;
long (__gshared global) long app.g_lastAxisUsg_lastAxisUs;
// detent accumulation + ruler
long (__gshared global) long app.g_v120Carryg_v120Carry; // vertical 120ths not yet a whole detent
int (__gshared global) int app.g_detentsg_detents; // signed net wheel detents (vertical)
double (__gshared global) double app.g_scrollPxg_scrollPx = 0; // continuous vertical scroll length → ruler offset
bool (__gshared global) bool app.g_configuredg_configured, (__gshared global) bool app.g_runningg_running = true, (__gshared global) bool app.g_autoExitg_autoExit, (__gshared global) bool app.g_keepPointerg_keepPointer;
long (__gshared global) long app.g_runUsCapg_runUsCap = 2_500_000;
int (__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_commitsg_commits, (__gshared global) int app.g_axisEventsg_axisEvents;
}
const(char)* const(char)* app.axisName(uint axis) nothrow @nogcaxisName(uint (parameter) uint axisaxis) nothrow @nogc
{
return (parameter) uint axisaxis == WL_POINTER_AXIS_VERTICAL_SCROLL ? "vertical".ptr : "horizontal".ptr;
}
const(char)* const(char)* app.sourceName(uint src) nothrow @nogcsourceName(uint (parameter) uint srcsrc) nothrow @nogc
{
switch ((parameter) uint srcsrc)
{
case WL_POINTER_AXIS_SOURCE_WHEEL: return "wheel".(constant) immutable(char)* "wheel".ptr = "wheel"ptr;
case WL_POINTER_AXIS_SOURCE_FINGER: return "finger".(constant) immutable(char)* "finger".ptr = "finger"ptr;
case WL_POINTER_AXIS_SOURCE_CONTINUOUS: return "continuous".(constant) immutable(char)* "continuous".ptr = "continuous"ptr;
case WL_POINTER_AXIS_SOURCE_WHEEL_TILT: return "wheel_tilt".(constant) immutable(char)* "wheel_tilt".ptr = "wheel_tilt"ptr;
default: return "?".(constant) immutable(char)* "?".ptr = "?"ptr;
}
}
// ----------------------------------------------------------------- gestures
void void app.endGesture(const(char)* reason) nothrow @nogcendGesture(const(char)* (parameter) const(char)* reasonreason) nothrow @nogc
{
if (!(__gshared global) bool app.g_inGestureg_inGesture)
return;
(__gshared global) bool app.g_inGestureg_inGesture = false;
instrEvent("gesture_summary",
"num=%d frames=%d value_v=%.3f value_h=%.3f v120_v=%lld v120_h=%lld detents_total=%d source=%s reason=%s",
g_gestureNum, g_gestureFrames, g_gestureValue[0], g_gestureValue[1],
g_gestureV120[0], g_gestureV120[1], g_detents,
g_gestureHasSource ? sourceName(g_gestureSource) : "none"."none".ptrptr, reason);
(__gshared global) int app.g_gestureFramesg_gestureFrames = 0;
(__gshared global) double[2] app.g_gestureValueg_gestureValue = [0, 0];
(__gshared global) long[2] app.g_gestureV120g_gestureV120 = [0, 0];
(__gshared global) bool app.g_gestureHasSourceg_gestureHasSource = false;
}
// ---------------------------------------------------------------- listeners
extern (C) void app.onPointerEnteronPointerEnter(void* data, wl_pointer* p, uint serial,
wl_surface* surf, int sx, int sy) nothrow @nogc
{
instrEvent("pointer_enter", "serial=%u pos=%.2f,%.2f", serial, sx / 256.0, sy / 256.0);
}
extern (C) void app.onPointerLeaveonPointerLeave(void* data, wl_pointer* p, uint serial, wl_surface* surf) nothrow @nogc
{
instrEvent("pointer_leave", "serial=%u", serial);
endGesture("leave");
}
extern (C) void app.onPointerMotiononPointerMotion(void* data, wl_pointer* p, uint time, int sx, int sy) nothrow @nogc {}
extern (C) void app.onPointerButtononPointerButton(void* data, wl_pointer* p, uint serial, uint time,
uint button, uint state) nothrow @nogc {}
extern (C) void app.onPointerAxisonPointerAxis(void* data, wl_pointer* p, uint time, uint (parameter) uint axisaxis, int (parameter) int valuevalue) nothrow @nogc
{
g_axisEvents++;
immutable _error_ vv = value / 256.0;
instrEvent("axis", "axis=%s value=%.3f time=%u", axisName(axis), v, time);
if (axis > 1)
return;
g_pend[axis].g_pend[axis].hasValuehasValue = true;
g_pend[axis].g_pend[axis].valuevalue += v;
}
extern (C) void app.onPointerAxisSourceonPointerAxisSource(void* data, wl_pointer* p, uint (parameter) uint srcsrc) nothrow @nogc
{
instrEvent("axis_source", "source=%s raw=%u", sourceName(src), src);
g_hasSource = true;
g_source = src;
}
extern (C) void app.onPointerAxisStoponPointerAxisStop(void* data, wl_pointer* p, uint time, uint (parameter) uint axisaxis) nothrow @nogc
{
instrEvent("axis_stop", "axis=%s time=%u", axisName(axis), time);
if (axis <= 1)
g_pend[axis].g_pend[axis].stopstop = true;
}
extern (C) void app.onPointerAxisDiscreteonPointerAxisDiscrete(void* data, wl_pointer* p, uint (parameter) uint axisaxis, int discrete) nothrow @nogc
{
// Deprecated with seat v8: a v8+ bind must receive axis_value120 instead.
// Logged so a compositor that still sends it on a high bind is caught.
instrEvent("axis_discrete", "axis=%s discrete=%d note=DEPRECATED-pre-v8", axisName(axis), discrete);
}
extern (C) void app.onPointerAxisValue120onPointerAxisValue120(void* data, wl_pointer* p, uint (parameter) uint axisaxis, int (parameter) int v120v120) nothrow @nogc
{
instrEvent("axis_value120", "axis=%s v120=%d detent_fraction=%.3f",
axisName(axis), v120, v120 / 120.0);
if (axis > 1)
return;
g_pend[axis].g_pend[axis].hasV120hasV120 = true;
g_pend[axis].g_pend[axis].v120v120 += v120;
}
extern (C) void app.onPointerAxisRelDironPointerAxisRelDir(void* data, wl_pointer* p, uint (parameter) uint axisaxis, uint dir) nothrow @nogc
{
instrEvent("axis_relative_direction", "axis=%s direction=%s", axisName(axis),
dir == WL_POINTER_AXIS_RELATIVE_DIRECTION_INVERTED ? "inverted"."inverted".ptrptr : "identical"."identical".ptrptr);
if (axis <= 1)
{
g_pend[axis].g_pend[axis].hasDirectionhasDirection = true;
g_pend[axis].g_pend[axis].directiondirection = dir;
}
}
/// The frame boundary: everything since the previous frame is ONE logical
/// event group. Only groups that carried axis state are logged/accumulated
/// (enter/leave/motion also arrive frame-terminated).
extern (C) void app.onPointerFrameThe frame boundary: everything since the previous frame is ONE logical
event group. Only groups that carried axis state are logged/accumulated
(enter/leave/motion also arrive frame-terminated).
onPointerFrame(void* data, wl_pointer* p) nothrow @nogc
{
immutable _error_ hadAxishadAxis = g_pend[0].g_pend[0].hasValuehasValue || g_pend[1].g_pend[1].hasValuehasValue
|| g_pend[0].g_pend[0].hasV120hasV120 || g_pend[1].g_pend[1].hasV120hasV120
|| g_pend[0].g_pend[0].stopstop || g_pend[1].g_pend[1].stopstop || g_hasSource;
if (!hadAxis)
return;
g_frameSeq++;
instrEvent("frame",
"seq=%d source=%s v=[value=%.3f%s v120=%d%s stop=%d dir=%s] h=[value=%.3f%s v120=%d%s stop=%d dir=%s]",
g_frameSeq, g_hasSource ? sourceName(g_source) : "none"."none".ptrptr,
g_pend[0].g_pend[0].valuevalue, g_pend[0].g_pend[0].hasValuehasValue ? ""."".ptrptr : "(absent)"."(absent)".ptrptr,
g_pend[0].g_pend[0].v120v120, g_pend[0].g_pend[0].hasV120hasV120 ? ""."".ptrptr : "(absent)"."(absent)".ptrptr,
g_pend[0].g_pend[0].stopstop ? 1 : 0,
g_pend[0].g_pend[0].hasDirectionhasDirection
? (g_pend[0].g_pend[0].directiondirection ? "inverted"."inverted".ptrptr : "identical"."identical".ptrptr) : "-"."-".ptrptr,
g_pend[1].g_pend[1].valuevalue, g_pend[1].g_pend[1].hasValuehasValue ? ""."".ptrptr : "(absent)"."(absent)".ptrptr,
g_pend[1].g_pend[1].v120v120, g_pend[1].g_pend[1].hasV120hasV120 ? ""."".ptrptr : "(absent)"."(absent)".ptrptr,
g_pend[1].g_pend[1].stopstop ? 1 : 0,
g_pend[1].g_pend[1].hasDirectionhasDirection
? (g_pend[1].g_pend[1].directiondirection ? "inverted"."inverted".ptrptr : "identical"."identical".ptrptr) : "-"."-".ptrptr);
// gesture bookkeeping
if (!g_inGesture && (g_pend[0].g_pend[0].hasValuehasValue || g_pend[1].g_pend[1].hasValuehasValue))
{
g_inGesture = true;
g_gestureNum++;
instrEvent("gesture_begin", "num=%d source=%s", g_gestureNum,
g_hasSource ? sourceName(g_source) : "none"."none".ptrptr);
}
if (g_inGesture)
{
g_gestureFrames++;
foreach ((parameter) aa; 0 .. 2)
{
g_gestureValue[a] += g_pend[a].g_pend[a].valuevalue;
g_gestureV120[a] += g_pend[a].g_pend[a].v120v120;
}
// First VALUE-carrying frame names the gesture source — wlrctl's
// trailing stop frame arrives with source=finger even when the value
// frame said wheel, and must not overwrite it.
if (g_hasSource && !g_gestureHasSource
&& (g_pend[0].g_pend[0].hasValuehasValue || g_pend[1].g_pend[1].hasValuehasValue))
{
g_gestureHasSource = true;
g_gestureSource = g_source;
}
g_lastAxisUs = instrNowUs();
}
// detent accumulation (vertical): carry the remainder — truncating each
// event would lose sub-detent precision from high-resolution wheels.
if (g_pend[0].g_pend[0].hasV120hasV120)
{
g_v120Carry += g_pend[0].g_pend[0].v120v120;
while (g_v120Carry >= 120 || g_v120Carry <= -120)
{
immutable _error_ stepstep = g_v120Carry > 0 ? 1 : -1;
g_detents += step;
g_v120Carry -= step * 120;
instrEvent("detent", "step=%d detents=%d carry=%lld", step, g_detents, g_v120Carry);
}
}
g_scrollPx += g_pend[0].g_pend[0].valuevalue; // continuous length drives the ruler
immutable _error_ wasStopwasStop = g_pend[0].g_pend[0].stopstop || g_pend[1].g_pend[1].stopstop;
g_pend[0] = AxisPending.init;
g_pend[1] = AxisPending.init;
g_hasSource = false;
if (wasStop)
endGesture("axis_stop");
}
__gshared wl_pointer_listener _error_ app.g_pointerListenerg_pointerListener = {
&onPointerEnter, &onPointerLeave, &onPointerMotion, &onPointerButton,
&onPointerAxis, &onPointerFrame, &onPointerAxisSource, &onPointerAxisStop,
&onPointerAxisDiscrete, &onPointerAxisValue120, &onPointerAxisRelDir
};
extern (C) void app.onSeatCapabilitiesonSeatCapabilities(void* data, wl_seat* (parameter) wl_seat* ss, uint caps) nothrow @nogc
{
immutable _error_ hasPointerhasPointer = (caps & WL_SEAT_CAPABILITY_POINTER) != 0;
instrEvent("seat_capabilities", "caps=%u pointer=%d", caps, hasPointer ? 1 : 0);
if (hasPointer && g_pointer is null)
{
g_pointer = wsi_seat_get_pointer(g_seat);
wsi_pointer_add_listener(g_pointer, &g_pointerListener, null);
}
else if (!hasPointer && g_pointer !is null)
{
// Capability flap (the wlrctl device unplugged). The conformant move
// is destroy + re-create (the F12 lesson) — but the re-created
// wl_pointer needs a get_pointer round-trip, and a wlrctl scroll
// burst is fully delivered before that completes: every axis event
// would be dropped. WSI_KEEP_POINTER=1 keeps the proxy across the
// flap instead so the next burst lands on an existing resource —
// which behaviour actually receives events is itself a finding.
if (g_keepPointer)
{
instrEvent("pointer_kept", "note=capability removed; wl_pointer retained (WSI_KEEP_POINTER)");
return;
}
wsi_pointer_destroy(g_pointer);
g_pointer = null;
instrEvent("pointer_dropped", "note=capability removed; wl_pointer destroyed");
}
}
extern (C) void app.onSeatNameonSeatName(void* data, wl_seat* (parameter) wl_seat* ss, const(char)* name) nothrow @nogc {}
extern (C) void app.onGlobalonGlobal(void* data, wl_registry* reg, uint name,
const(char)* iface, 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, 6));
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 is null)
{
// v8 brings axis_value120 (and retires axis_discrete); v9 brings
// axis_relative_direction. Bind as high as the compositor offers.
g_seatVerAdvertised = ver;
g_seatVerBound = capped(ver, 9);
g_seat = cast(wl_seat*) wsi_registry_bind(reg, name, &wl_seat_interface, g_seatVerBound);
wsi_seat_add_listener(g_seat, &g_seatListener, null);
}
}
extern (C) void app.onGlobalRemoveonGlobalRemove(void* data, wl_registry* reg, uint name) nothrow @nogc {}
// ------------------------------------------------ window plumbing (scaffold)
bool app.ensureBufferensureBuffer(ref (struct) app.BufferBuffer (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))
{
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-f11", MFD_CLOEXEC);
if (fd < 0 || ftruncate(fd, cast(long) size) != 0)
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;
}
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;
return true;
}
/// The scrollable ruler: a tick every 40 px (major every 200) offset by the
/// accumulated continuous scroll, plus a left-edge detent gauge. Over/under-
/// scroll bugs would be visible as ruler drift against the detent gauge.
void void app.render() nothrow @nogcThe scrollable ruler: a tick every 40 px (major every 200) offset by the
accumulated continuous scroll, plus a left-edge detent gauge. Over/under-
scroll bugs would be visible as ruler drift against the detent gauge.
render() nothrow @nogc
{
(struct) app.BufferBuffer* (local variable) _error_ bufbuf = null;
foreach (ref (parameter) bb; g_buffers)
if (!b.b.busybusy)
{
buf = &b;
break;
}
if (buf is null || !ensureBuffer(*buf, g_width, g_height))
return;
immutable (local variable) immutable(int) offoff = cast(int) (__gshared global) double app.g_scrollPxg_scrollPx;
foreach ((parameter) yy; 0 .. buf.(field) _error_ buf.heightheight)
{
immutable _error_ ryry = y + off; // ruler-space row
immutable _error_ mm = ((ry % 200) + 200) % 200;
immutable _error_ isMajorisMajor = m < 3;
immutable _error_ isMinorisMinor = (((ry % 40) + 40) % 40) < 2;
foreach ((parameter) xx; 0 .. buf.buf.widthwidth)
{
uint _error_ cc = 0xff181c28;
if (isMajor)
c = 0xffe0e0e0;
else if (isMinor && x > 40)
c = 0xff707888;
if (x < 24) // detent gauge: one 8-px notch per accumulated detent
{
immutable _error_ notchnotch = g_detents * 8;
c = (notch >= 0 && y >= buf.buf.heightheight / 2 - notch && y < buf.buf.heightheight / 2)
|| (notch < 0 && y < buf.buf.heightheight / 2 - notch && y >= buf.buf.heightheight / 2)
? 0xff60c060 : 0xff101010;
}
buf.buf.pixelspixels[cast((unresolved type) size_tsize_t) y * buf.buf.widthwidth + x] = c;
}
}
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)
{
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++;
}
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);
}
extern (C) void app.onToplevelConfigureonToplevelConfigure(void* data, xdg_toplevel* t, int (parameter) int ww, int (parameter) int hh,
wl_array* states) nothrow @nogc
{
g_pendingWidth = w;
g_pendingHeight = h;
}
extern (C) void app.onXdgSurfaceConfigureonXdgSurfaceConfigure(void* data, xdg_surface* (parameter) xdg_surface* ss, uint serial) nothrow @nogc
{
immutable _error_ ww = g_pendingWidth > 0 ? g_pendingWidth : defaultWidth;
immutable _error_ hh = g_pendingHeight > 0 ? g_pendingHeight : defaultHeight;
instrEvent("configure", "serial=%u size=%dx%d", serial, w, h);
wsi_xdg_surface_ack_configure(s, serial);
immutable _error_ resizedresized = w != g_width || h != g_height;
g_width = w;
g_height = h;
if (!g_configured)
{
g_configured = true;
instrFirstConfigure();
render();
}
else if (resized)
render();
}
extern (C) void app.onToplevelCloseonToplevelClose(void* data, xdg_toplevel* t) nothrow @nogc
{
instrCloseRequested();
g_running = false;
}
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 {}
extern (C) void app.onBufferReleaseonBufferRelease(void* data, wl_buffer* (parameter) wl_buffer* bb) nothrow @nogc
{
(cast((unresolved type) BufferBuffer*) data).(cast(Buffer*)data).busybusy = false;
if (g_running && g_configured && g_frameCb is null)
render();
}
extern (C) void app.onFrameDoneonFrameDone(void* data, wl_callback* cb, uint timeMs) nothrow @nogc
{
wsi_callback_destroy(cb);
g_frameCb = null;
g_frames++;
if (g_frames == 1)
instrFirstPixelPresented();
render();
}
__gshared wl_registry_listener _error_ app.g_registryListenerg_registryListener = {&onGlobal, &onGlobalRemove};
__gshared wl_seat_listener _error_ app.g_seatListenerg_seatListener = {&onSeatCapabilities, &onSeatName};
__gshared xdg_wm_base_listener _error_ app.g_wmBaseListenerg_wmBaseListener = {&onWmBasePing};
__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};
// --------------------------------------------------------------------- main
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
// `f11_scroll inject <hold_ms> <steps> <dx> <dy> <interval_ms>` plugs a
// zwlr_virtual_pointer_v1 device in (see ./inject.d). ./run.sh uses it
// with steps=0 as a pure capability HOLD: while the held device keeps the
// seat's pointer capability up, the demo's wl_pointer survives and the
// transient `wlrctl pointer scroll` bursts land on a live resource —
// without the hold, every burst is fully delivered before the demo's
// get_pointer round-trip completes and ALL axis events are dropped.
// NB: druntime packs main's args into one contiguous buffer WITHOUT NUL
// terminators between them — atoi(args[i].ptr) reads the neighbours too.
static int int app.main.parseInt(in char[] s) pure nothrow @nogc @safeparseInt(in char[] (parameter) const(char[]) ss) nothrow @nogc
{
int (local variable) int vv;
bool (local variable) bool negneg;
foreach ((parameter) ulong ii, (parameter) const(char) chch; (parameter) const(char[]) ss)
{
if ((local variable) ulong ii == 0 && (local variable) const(char) chch == '-')
(local variable) bool negneg = true;
else if ((local variable) const(char) chch >= '0' && (local variable) const(char) chch <= '9')
(local variable) int vv = (local variable) int vv * 10 + ((local variable) const(char) chch - '0');
}
return (local variable) bool negneg ? -(local variable) int vv : (local variable) int vv;
}
if ((parameter) string[] argsargs.(field) ulong string[].lengthlength >= 7 && (parameter) string[] argsargs[1] == "inject")
{
import inject : injectMain;
return injectMain(parseInt(args[2]), parseInt(args[3]),
parseInt(args[4]), parseInt(args[5]), parseInt(args[6]),
args.args.lengthlength > 7 && args[7] == "wheel");
}
instrInit("f11_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';
const (local variable) const(char*) runMsrunMs = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_RUN_MS");
if ((local variable) const(char*) runMsrunMs !is null)
(__gshared global) long app.g_runUsCapg_runUsCap = int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi((local variable) const(char*) runMsrunMs) * 1000L;
const (local variable) const(char*) keepEnvkeepEnv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_KEEP_POINTER");
(__gshared global) bool app.g_keepPointerg_keepPointer = (local variable) const(char*) keepEnvkeepEnv !is null && *(local variable) const(char*) keepEnvkeepEnv == '1';
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;
}
g_registry = wsi_display_get_registry(g_display);
wsi_registry_add_listener(g_registry, &g_registryListener, null);
wl_display_roundtrip(g_display); // globals
wl_display_roundtrip(g_display); // seat capabilities
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\n");
wl_display_disconnect(g_display);
return 0;
}
instrEvent("seat", "present=%d advertised=%u bound=%u v120=%s direction=%s",
g_seat !is null ? 1 : 0, g_seatVerAdvertised, g_seatVerBound,
g_seatVerBound >= 8 ? "yes"."yes".ptrptr : "no-pre-v8"."no-pre-v8".ptrptr,
g_seatVerBound >= 9 ? "yes"."yes".ptrptr : "no-pre-v9"."no-pre-v9".ptrptr);
if (g_seat is null)
{
// headless weston: no seat at all → no pointer, no axis events; the
// registry probe above is the Tier-A evidence.
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("ok: no seat on this compositor; registry probe only\n");
wl_display_disconnect(g_display);
return 0;
}
wsi_wm_base_add_listener(g_wmBase, &g_wmBaseListener, null);
g_surface = wsi_compositor_create_surface(g_compositor);
g_xdgSurface = wsi_wm_base_get_xdg_surface(g_wmBase, g_surface);
wsi_xdg_surface_add_listener(g_xdgSurface, &g_xdgSurfaceListener, null);
g_toplevel = wsi_xdg_surface_get_toplevel(g_xdgSurface);
wsi_toplevel_add_listener(g_toplevel, &g_toplevelListener, null);
wsi_toplevel_set_title(g_toplevel, "wsi-f11-scroll");
wsi_toplevel_set_app_id(g_toplevel, "wsi-f11-scroll");
instrWindowCreated();
wsi_surface_commit(g_surface); // mandatory no-buffer first commit
immutable (local variable) immutable(_error_) dispFddispFd = wl_display_get_fd(g_display);
while ((__gshared global) bool app.g_runningg_running)
{
while (wl_display_prepare_read(g_display) != 0)
wl_display_dispatch_pending(g_display);
wl_display_flush(g_display);
pollfd (local variable) _error_ pfdpfd = {dispFd, POLLIN, 0};
if (poll(&pfd, 1, 50) > 0)
wl_display_read_events(g_display);
else
wl_display_cancel_read(g_display);
if (wl_display_dispatch_pending(g_display) == -1)
break;
if ((__gshared global) bool app.g_inGestureg_inGesture && instrNowUs() - (__gshared global) long app.g_lastAxisUsg_lastAxisUs > gestureGapUs)
void app.endGesture(const(char)* reason) nothrow @nogcendGesture("idle_gap"); // momentum is NOT delivered: the app owns kinetics
if ((__gshared global) bool app.g_autoExitg_autoExit && instrNowUs() > (__gshared global) long app.g_runUsCapg_runUsCap)
(__gshared global) bool app.g_runningg_running = false;
}
void app.endGesture(const(char)* reason) nothrow @nogcendGesture("shutdown");
instrEvent("summary",
"axis_events=%d frames_with_axis=%d gestures=%d detents=%d v120_carry=%lld scroll_px=%.2f",
g_axisEvents, g_frameSeq, g_gestureNum, g_detents, g_v120Carry, g_scrollPx);
// Teardown: children before parents.
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);
}
if (g_frameCb !is null)
wsi_callback_destroy(g_frameCb);
if (g_pointer !is null)
wsi_pointer_destroy(g_pointer);
if (g_seat !is null)
wsi_seat_destroy(g_seat);
wsi_toplevel_destroy(g_toplevel);
wsi_xdg_surface_destroy(g_xdgSurface);
wsi_surface_destroy(g_surface);
wsi_wm_base_destroy(g_wmBase);
wl_proxy_destroy(cast(wl_proxy*) g_shm);
wl_proxy_destroy(cast(wl_proxy*) g_compositor);
wl_proxy_destroy(cast(wl_proxy*) g_registry);
wl_display_disconnect(g_display);
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("ok: frames=%d commits=%d axis_events=%d gestures=%d detents=%d\n",
(__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_commitsg_commits, (__gshared global) int app.g_axisEventsg_axisEvents, (__gshared global) int app.g_gestureNumg_gestureNum, (__gshared global) int app.g_detentsg_detents);
return 0;
}