// F12 cursor demo — both Wayland cursor mechanisms in one binary, runtime-
// selected by the advertised globals, on top of the xdg-shell scaffold
// (../scaffold/app.d; findings: ../../scaffold.md, ../../f12-cursors.md).
//
// (a) shape path — wp_cursor_shape_device_v1.set_shape (cursor-shape-v1):
// the compositor owns the pixels; the client sends one enum per change.
// (b) theme path — classic wl_pointer.set_cursor with a wl_surface the
// client renders itself from the Xcursor theme via libwayland-cursor
// (wl_cursor_theme_load at size × buffer-scale, animated frames driven
// by the frame callback when the theme provides them).
//
// A 3×3 hover-zone grid switches the cursor: the 8 border zones map to the 8
// resize-edge cursors; every (re)entry into the center zone advances a cycle
// default → text → pointer(hand) → bullseye → wait. The bullseye is the
// custom-pixmap requirement: a 24×24 ARGB wl_shm image with hotspot (12,12),
// always set via wl_pointer.set_cursor (cursor-shape-v1 has no custom-image
// request — itself a finding). Every change logs `cursor_set name=… path=…`.
//
// Env knobs: WSI_FORCE_PATH=shape|theme overrides the selection;
// WSI_AUTO_EXIT=1 + WSI_RUN_MS=<ms> bound the run (./run.sh drives the
// pointer over the zones with `swaymsg seat seat0 cursor set`). If no real
// pointer focus arrives by half the run cap, the demo falls back to a blind
// request tour (serial 0) so the WAYLAND_DEBUG trace still carries the
// protocol evidence. Headless-safe: no compositor → SKIP, exit 0; a seat-less
// compositor (headless weston) still yields the registry probe + theme dump.
module (module) appapp;
import c; // ImportC: wayland-client + wayland-cursor + xdg-shell/cursor-shape 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 int (constant) int app.cursorBaseSize = 24cursorBaseSize = 24; // theme size before HiDPI scaling
enum int (constant) int app.bullseyeSize = 24bullseyeSize = 24, (constant) int app.bullseyeHot = 12bullseyeHot = 12;
// ------------------------------------------------------------- cursor table
enum (enum) app.PathPath
{
(enum value) app.Path.shape = 0shape, // wp_cursor_shape_device_v1.set_shape
(enum value) app.Path.theme = 1theme, // wl_pointer.set_cursor + libwayland-cursor
}
/// One logical cursor: the cursor-shape-v1 enum value, the modern (CSS/
/// cursor-spec) theme name and the legacy Xcursor fallback name.
struct (struct) app.CursorDefOne logical cursor: the cursor-shape-v1 enum value, the modern (CSS/
cursor-spec) theme name and the legacy Xcursor fallback name.
CursorDef
{
const(char)* (field) const(char)* app.CursorDef.labellabel;
uint (field) uint app.CursorDef.shapeshape;
const(char)* (field) const(char)* app.CursorDef.namename;
const(char)* (field) const(char)* app.CursorDef.legacylegacy;
}
__gshared (struct) app.CursorDefOne logical cursor: the cursor-shape-v1 enum value, the modern (CSS/
cursor-spec) theme name and the legacy Xcursor fallback name.
CursorDef[12] (__gshared global) app.CursorDef[12] app.g_defsg_defs = [
{"default", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT, "default", "left_ptr"},
{"text", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT, "text", "xterm"},
{"pointer", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER, "pointer", "hand1"},
{"nw-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE, "nw-resize", "top_left_corner"},
{"n-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE, "n-resize", "top_side"},
{"ne-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE, "ne-resize", "top_right_corner"},
{"w-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE, "w-resize", "left_side"},
{"e-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE, "e-resize", "right_side"},
{"sw-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE, "sw-resize", "bottom_left_corner"},
{"s-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE, "s-resize", "bottom_side"},
{"se-resize", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE, "se-resize", "bottom_right_corner"},
{"wait", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT, "wait", "watch"},
];
enum int (constant) int app.idxBullseye = 12idxBullseye = 12; // pseudo-index: the custom pixmap cursor
// zone (row-major 0..8) → def index; center (4) cycles instead.
__gshared immutable int[9] (immutable global) immutable(int[9]) app.g_zoneDefg_zoneDef = [3, 4, 5, 6, -1, 7, 8, 9, 10];
__gshared immutable int[5] (immutable global) immutable(int[5]) app.g_centerCycleg_centerCycle = [0, 1, 2, (constant) int app.idxBullseye = 12idxBullseye, 11];
// -------------------------------------------------------------------- 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;
}
__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;
wp_cursor_shape_manager_v1* _error_ app.g_shapeMgrg_shapeMgr;
uint (__gshared global) uint app.g_shapeMgrVersiong_shapeMgrVersion;
wp_cursor_shape_device_v1* _error_ app.g_shapeDevg_shapeDev;
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;
wl_surface* _error_ app.g_cursorSurfaceg_cursorSurface; // theme + custom cursors render here
wl_cursor_theme* _error_ app.g_themeg_theme;
wl_buffer* _error_ app.g_bullseyeg_bullseye; // 24×24 ARGB custom cursor
void* (__gshared global) void* app.g_bullseyeMemg_bullseyeMem;
(alias) object.size_t = ulongsize_t (__gshared global) ulong app.g_bullseyeBytesg_bullseyeBytes;
(struct) app.BufferBuffer[2] _error_ app.g_buffersg_buffers;
(enum) app.PathPath (__gshared global) app.Path app.g_pathg_path;
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;
int (__gshared global) int app.g_scaleg_scale = 1; // wl_surface.preferred_buffer_scale (HiDPI cursor size)
uint (__gshared global) uint app.g_enterSerialg_enterSerial;
int (__gshared global) int app.g_zoneg_zone = -1, (__gshared global) int app.g_cyclePosg_cyclePos = -1, (__gshared global) int app.g_currentDefg_currentDef = -1;
wl_cursor* _error_ app.g_animCursorg_animCursor; // non-null while an animated theme cursor shows
int (__gshared global) int app.g_animFrameg_animFrame;
long (__gshared global) long app.g_animStartUsg_animStartUs;
int (__gshared global) int app.g_animLoggedg_animLogged;
bool (__gshared global) bool app.g_haveEnterg_haveEnter, (__gshared global) bool app.g_seatHasPointerg_seatHasPointer;
bool (__gshared global) bool app.g_configuredg_configured, (__gshared global) bool app.g_runningg_running = true, (__gshared global) bool app.g_autoExitg_autoExit;
long (__gshared global) long app.g_runUsCapg_runUsCap = 2_500_000;
long (__gshared global) long app.g_tourNextUsg_tourNextUs = -1;
int (__gshared global) int app.g_tourIdxg_tourIdx;
int (__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_commitsg_commits;
}
const(char)* const(char)* app.pathName() nothrow @nogcpathName() nothrow @nogc
{
return (__gshared global) app.Path app.g_pathg_path == (enum) app.PathPath.(enum value) app.Path.shape = 0shape ? "shape".(constant) immutable(char)* "shape".ptr = "shape"ptr : "theme".(constant) immutable(char)* "theme".ptr = "theme"ptr;
}
// ------------------------------------------------------- theme + set_cursor
/// (Re)load the Xcursor theme at base size × current buffer scale — the
/// HiDPI size-selection rule the client itself must implement on this path.
void void app.loadTheme() nothrow @nogc(Re)load the Xcursor theme at base size × current buffer scale — the
HiDPI size-selection rule the client itself must implement on this path.
loadTheme() nothrow @nogc
{
if (g_theme !is null)
wl_cursor_theme_destroy(g_theme);
immutable (local variable) immutable(int) sizesize = (constant) int app.cursorBaseSize = 24cursorBaseSize * (__gshared global) int app.g_scaleg_scale;
const (local variable) const(char*) themeNamethemeName = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("XCURSOR_THEME");
g_theme = wl_cursor_theme_load(themeName, size, g_shm);
instrEvent("cursor_theme_load", "requested_size=%d base=%d scale=%d theme=%s loaded=%d",
size, cursorBaseSize, g_scale,
themeName !is null ? themeName : "(default)"."(default)".ptrptr, g_theme !is null ? 1 : 0);
}
/// Look a cursor up by modern name, then by legacy Xcursor name.
wl_cursor* app.themeCursorLook a cursor up by modern name, then by legacy Xcursor name.
themeCursor(in (struct) app.CursorDefOne logical cursor: the cursor-shape-v1 enum value, the modern (CSS/
cursor-spec) theme name and the legacy Xcursor fallback name.
CursorDef (parameter) CursorDef dd, out const(char)* (parameter) const(char)* resolvedresolved) nothrow @nogc
{
auto _error_ curcur = wl_cursor_theme_get_cursor(g_theme, d.d.namename);
resolved = d.d.namename;
if (cur is null)
{
cur = wl_cursor_theme_get_cursor(g_theme, d.d.legacylegacy);
resolved = d.d.legacylegacy;
}
return cur;
}
/// Dump every cursor the demo uses: which name resolved (modern vs legacy
/// Xcursor vocabulary) and the image size the theme picked for our size×scale.
void void app.probeTheme() nothrow @nogcDump every cursor the demo uses: which name resolved (modern vs legacy
Xcursor vocabulary) and the image size the theme picked for our size×scale.
probeTheme() nothrow @nogc
{
foreach (ref (parameter) app.CursorDef dd; (__gshared global) app.CursorDef[12] app.g_defsg_defs)
{
const(char)* (local variable) const(char)* resolvedresolved;
auto (local variable) _error_ curcur = themeCursor(d, resolved);
if (cur is null)
instrEvent("cursor_probe", "name=%s resolved=MISSING", d.d.labellabel);
else
{
auto (local variable) _error_ imgimg = cur.images[0];
instrEvent("cursor_probe", "name=%s resolved=%s image=%ux%u hotspot=%u,%u frames=%u",
d.d.labellabel, resolved, img.img.widthwidth, img.img.heightheight,
img.hotspot_x, img.hotspot_y, cur.image_count);
}
}
}
/// Attach one theme-cursor frame to the cursor surface (and optionally
/// (re)point the pointer at it). Hotspot is surface-local, i.e. divided by
/// the buffer scale the surface carries.
void app.attachFrameAttach one theme-cursor frame to the cursor surface (and optionally
(re)point the pointer at it). Hotspot is surface-local, i.e. divided by
the buffer scale the surface carries.
attachFrame(wl_cursor* (parameter) wl_cursor* curcur, int (parameter) int frameframe, bool setCursor, uint (parameter) uint serialserial) nothrow @nogc
{
auto _error_ imgimg = cur.images[frame];
auto _error_ bufbuf = wl_cursor_image_get_buffer(img);
wsi_surface_set_buffer_scale(g_cursorSurface, g_scale);
wsi_surface_attach(g_cursorSurface, buf, 0, 0);
wsi_surface_damage_buffer(g_cursorSurface, 0, 0, cast(int) img.img.widthwidth, cast(int) img.img.heightheight);
wsi_surface_commit(g_cursorSurface);
if (setCursor)
wsi_pointer_set_cursor(g_pointer, serial, g_cursorSurface,
cast(int) img.hotspot_x / g_scale, cast(int) img.hotspot_y / g_scale);
}
bool bool app.ensureBullseye() nothrow @nogcensureBullseye() nothrow @nogc
{
if (g_bullseye !is null)
return true;
enum (constant) int app.ensureBullseye.stride = 96stride = (constant) int app.bullseyeSize = 24bullseyeSize * 4;
enum (constant) ulong app.ensureBullseye.size = 2304LUsize = cast((alias) object.size_t = ulongsize_t) (constant) int app.ensureBullseye.stride = 96stride * (constant) int app.bullseyeSize = 24bullseyeSize;
immutable (local variable) immutable(_error_) fdfd = memfd_create("wsi-f12-bullseye", MFD_CLOEXEC);
if (fd < 0 || ftruncate(fd, size) != 0)
return false;
uint* (local variable) uint* pxpx = cast(uint*) mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if ((local variable) uint* pxpx is cast(uint*)-1)
{
close(fd);
return false;
}
foreach ((local variable) int yy; 0 .. (constant) int app.bullseyeSize = 24bullseyeSize) // concentric red/white rings, transparent corners
foreach ((local variable) int xx; 0 .. (constant) int app.bullseyeSize = 24bullseyeSize)
{
immutable (local variable) immutable(int) dxdx = (local variable) int xx - (constant) int app.bullseyeHot = 12bullseyeHot, (local variable) immutable(int) dydy = (local variable) int yy - (constant) int app.bullseyeHot = 12bullseyeHot;
immutable (local variable) immutable(int) r2r2 = (local variable) immutable(int) dxdx * (local variable) immutable(int) dxdx + (local variable) immutable(int) dydy * (local variable) immutable(int) dydy;
uint (local variable) uint cc = 0;
if ((local variable) immutable(int) r2r2 <= 144) // radius 12
(local variable) uint cc = (((local variable) immutable(int) r2r2 <= 9) || ((local variable) immutable(int) r2r2 > 36 && (local variable) immutable(int) r2r2 <= 81)) ? 0xffff2020 : 0xffffffff;
(local variable) uint* pxpx[(local variable) int yy * (constant) int app.bullseyeSize = 24bullseyeSize + (local variable) int xx] = (local variable) uint cc;
}
wl_shm_pool* (local variable) _error_ poolpool = wsi_shm_create_pool(g_shm, fd, cast(int) size);
g_bullseye = wsi_shm_pool_create_buffer(pool, 0, bullseyeSize, bullseyeSize,
stride, WL_SHM_FORMAT_ARGB8888);
wsi_shm_pool_destroy(pool);
close(fd);
(__gshared global) void* app.g_bullseyeMemg_bullseyeMem = (local variable) uint* pxpx;
(__gshared global) ulong app.g_bullseyeBytesg_bullseyeBytes = (constant) ulong app.ensureBullseye.size = 2304LUsize;
return true;
}
/// The single entry point every zone change funnels into.
void void app.applyCursor(int idx, uint serial) nothrow @nogcThe single entry point every zone change funnels into.
applyCursor(int (parameter) int idxidx, uint (parameter) uint serialserial) nothrow @nogc
{
if (g_pointer is null)
return;
(__gshared global) int app.g_currentDefg_currentDef = (parameter) int idxidx;
g_animCursor = null;
if ((parameter) int idxidx == (constant) int app.idxBullseye = 12idxBullseye)
{
if (g_pointer is null || !bool app.ensureBullseye() nothrow @nogcensureBullseye())
return;
// Custom pixmaps have no shape enum: even on the shape path this goes
// through classic set_cursor. Mixing both on one wl_pointer is legal —
// the latest request wins.
wsi_surface_set_buffer_scale(g_cursorSurface, 1);
wsi_surface_attach(g_cursorSurface, g_bullseye, 0, 0);
wsi_surface_damage_buffer(g_cursorSurface, 0, 0, bullseyeSize, bullseyeSize);
wsi_surface_commit(g_cursorSurface);
wsi_pointer_set_cursor(g_pointer, serial, g_cursorSurface, bullseyeHot, bullseyeHot);
instrEvent("cursor_set", "name=bullseye path=custom size=%dx%d hotspot=%d,%d serial=%u",
bullseyeSize, bullseyeSize, bullseyeHot, bullseyeHot, serial);
return;
}
auto (local variable) app.CursorDef* dd = &(__gshared global) app.CursorDef[12] app.g_defsg_defs[(parameter) int idxidx];
if ((__gshared global) app.Path app.g_pathg_path == (enum) app.PathPath.(enum value) app.Path.shape = 0shape)
{
if (g_shapeDev is null)
return; // no pointer yet → no shape device yet
wsi_cursor_shape_set_shape(g_shapeDev, serial, d.d.shapeshape);
instrEvent("cursor_set", "name=%s path=shape shape_enum=%u serial=%u",
d.d.labellabel, d.d.shapeshape, serial);
return;
}
const(char)* (local variable) const(char)* resolvedresolved;
auto (local variable) _error_ curcur = themeCursor(*d, resolved);
if (cur is null)
{
instrEvent("cursor_set", "name=%s path=theme MISSING", d.d.labellabel);
return;
}
attachFrame(cur, 0, true, serial);
auto (local variable) _error_ imgimg = cur.images[0];
instrEvent("cursor_set", "name=%s path=theme resolved=%s image=%ux%u hotspot=%u,%u frames=%u serial=%u",
d.d.labellabel, resolved, img.img.widthwidth, img.img.heightheight, img.hotspot_x, img.hotspot_y,
cur.image_count, serial);
if (cur.image_count > 1) // animated: frames advance on the frame clock
{
g_animCursor = cur;
(__gshared global) int app.g_animFrameg_animFrame = 0;
(__gshared global) long app.g_animStartUsg_animStartUs = instrNowUs();
(__gshared global) int app.g_animLoggedg_animLogged = 0;
}
}
void void app.enterZone(int zone, uint serial) nothrow @nogcenterZone(int (parameter) int zonezone, uint (parameter) uint serialserial) nothrow @nogc
{
(__gshared global) int app.g_zoneg_zone = (parameter) int zonezone;
int (local variable) int idxidx = (immutable global) immutable(int[9]) app.g_zoneDefg_zoneDef[(parameter) int zonezone];
if ((local variable) int idxidx < 0) // center: advance the cycle on every (re)entry
{
(__gshared global) int app.g_cyclePosg_cyclePos = ((__gshared global) int app.g_cyclePosg_cyclePos + 1) % cast(int) (immutable global) immutable(int[5]) app.g_centerCycleg_centerCycle.(constant) ulong immutable(int[5]).length = 5LUlength;
(local variable) int idxidx = (immutable global) immutable(int[5]) app.g_centerCycleg_centerCycle[(__gshared global) int app.g_cyclePosg_cyclePos];
}
instrEvent("zone", "zone=%d cursor=%s", zone,
idx == idxBullseye ? "bullseye"."bullseye".ptrptr : g_defs[idx].g_defs[idx].labellabel);
void app.applyCursor(int idx, uint serial) nothrow @nogcThe single entry point every zone change funnels into.
applyCursor((local variable) int idxidx, (parameter) uint serialserial);
}
// ---------------------------------------------------------------- listeners
extern (C) void app.onPointerEnteronPointerEnter(void* data, wl_pointer* p, uint (parameter) uint serialserial,
wl_surface* surf, int sx, int sy) nothrow @nogc
{
g_enterSerial = serial;
g_haveEnter = true;
immutable _error_ xx = sx / 256, _error_ yy = sy / 256;
instrEvent("pointer_enter", "serial=%u pos=%d,%d", serial, x, y);
// The contract: the cursor is UNDEFINED on enter; the client must set it
// now (with this serial) or it keeps whatever the previous client left.
immutable _error_ zonezone = (y * 3 / (g_height < 1 ? 1 : g_height)) * 3
+ (x * 3 / (g_width < 1 ? 1 : g_width));
enterZone(zone < 0 ? 0 : (zone > 8 ? 8 : zone), serial);
}
extern (C) void app.onPointerLeaveonPointerLeave(void* data, wl_pointer* p, uint (parameter) uint serialserial, wl_surface* surf) nothrow @nogc
{
instrEvent("pointer_leave", "serial=%u", serial);
g_zone = -1;
}
extern (C) void app.onPointerMotiononPointerMotion(void* data, wl_pointer* p, uint time, int sx, int sy) nothrow @nogc
{
immutable _error_ xx = sx / 256, _error_ yy = sy / 256;
immutable _error_ zonezone = (y * 3 / (g_height < 1 ? 1 : g_height)) * 3
+ (x * 3 / (g_width < 1 ? 1 : g_width));
immutable _error_ clampedclamped = zone < 0 ? 0 : (zone > 8 ? 8 : zone);
if (clamped != g_zone)
enterZone(clamped, g_enterSerial);
}
extern (C) void app.onPointerButtononPointerButton(void* data, wl_pointer* p, uint (parameter) uint serialserial, uint time,
uint button, uint state) nothrow @nogc {}
extern (C) void app.onPointerAxisonPointerAxis(void* data, wl_pointer* p, uint time, uint axis, int value) nothrow @nogc {}
extern (C) void app.onPointerFrameonPointerFrame(void* data, wl_pointer* p) nothrow @nogc {}
extern (C) void app.onPointerAxisSourceonPointerAxisSource(void* data, wl_pointer* p, uint src) nothrow @nogc {}
extern (C) void app.onPointerAxisStoponPointerAxisStop(void* data, wl_pointer* p, uint time, uint axis) nothrow @nogc {}
extern (C) void app.onPointerAxisDiscreteonPointerAxisDiscrete(void* data, wl_pointer* p, uint axis, int discrete) nothrow @nogc {}
extern (C) void app.onPointerAxisValue120onPointerAxisValue120(void* data, wl_pointer* p, uint axis, int v120) nothrow @nogc {}
extern (C) void app.onPointerAxisRelDironPointerAxisRelDir(void* data, wl_pointer* p, uint axis, uint dir) nothrow @nogc {}
__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* s, uint caps) nothrow @nogc
{
immutable _error_ hasPointerhasPointer = (caps & WL_SEAT_CAPABILITY_POINTER) != 0;
instrEvent("seat_capabilities", "caps=%u pointer=%d keyboard=%d", caps,
hasPointer ? 1 : 0, (caps & WL_SEAT_CAPABILITY_KEYBOARD) ? 1 : 0);
if (hasPointer && g_pointer is null)
{
g_seatHasPointer = true;
g_pointer = wsi_seat_get_pointer(g_seat);
wsi_pointer_add_listener(g_pointer, &g_pointerListener, null);
if (g_shapeMgr !is null)
g_shapeDev = wsi_cursor_shape_get_pointer(g_shapeMgr, g_pointer);
}
else if (!hasPointer && g_pointer !is null)
{
// The capability went away (e.g. the wlrctl virtual pointer was
// unplugged). The spec: "When the capability is regained, a client
// should create the new objects" — the old resource stays inert, so
// destroy it now and re-create on the next capabilities event.
if (g_shapeDev !is null)
wsi_cursor_shape_device_destroy(g_shapeDev);
g_shapeDev = null;
wsi_pointer_destroy(g_pointer);
g_pointer = null;
instrEvent("pointer_dropped", "note=capability removed; proxies destroyed");
}
}
extern (C) void app.onSeatNameonSeatName(void* data, wl_seat* s, const(char)* (parameter) const(char)* namename) nothrow @nogc {}
extern (C) void app.onPreferredBufferScaleonPreferredBufferScale(void* data, wl_surface* s, int factor) nothrow @nogc
{
if (factor == g_scale)
return;
g_scale = factor;
instrEvent("preferred_buffer_scale", "factor=%d", factor);
if (g_path == Path.Path.themetheme)
{
loadTheme(); // HiDPI: reload at base × scale, then re-apply live
if (g_currentDef >= 0 && g_currentDef != idxBullseye && g_haveEnter)
applyCursor(g_currentDef, g_enterSerial);
}
}
extern (C) void app.onSurfaceEnteronSurfaceEnter(void* data, wl_surface* s, wl_output* o) nothrow @nogc {}
extern (C) void app.onSurfaceLeaveonSurfaceLeave(void* data, wl_surface* s, wl_output* o) nothrow @nogc {}
extern (C) void app.onPreferredBufferTransformonPreferredBufferTransform(void* data, wl_surface* s, uint t) nothrow @nogc {}
extern (C) void app.onGlobalonGlobal(void* data, wl_registry* reg, uint (parameter) uint namename,
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.wl_compositor_interface.namename) == 0)
g_compositor = cast(wl_compositor*) wsi_registry_bind(reg, name,
&wl_compositor_interface, capped(ver, 6));
else if (strcmp(iface, wl_shm_interface.wl_shm_interface.namename) == 0)
g_shm = cast(wl_shm*) wsi_registry_bind(reg, name, &wl_shm_interface, 1);
else if (strcmp(iface, xdg_wm_base_interface.xdg_wm_base_interface.namename) == 0)
g_wmBase = cast(xdg_wm_base*) wsi_registry_bind(reg, name, &xdg_wm_base_interface, 1);
else if (strcmp(iface, wl_seat_interface.wl_seat_interface.namename) == 0 && g_seat is null)
{
g_seat = cast(wl_seat*) wsi_registry_bind(reg, name, &wl_seat_interface, capped(ver, 5));
wsi_seat_add_listener(g_seat, &g_seatListener, null);
}
else if (strcmp(iface, wp_cursor_shape_manager_v1_interface.wp_cursor_shape_manager_v1_interface.namename) == 0)
{
g_shapeMgrVersion = ver;
g_shapeMgr = cast(wp_cursor_shape_manager_v1*) wsi_registry_bind(reg, name,
&wp_cursor_shape_manager_v1_interface, capped(ver, 1));
}
}
extern (C) void app.onGlobalRemoveonGlobalRemove(void* data, wl_registry* reg, uint (parameter) uint namename) 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-f12", 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;
}
void void app.render() nothrow @nogcrender() 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)
return;
if (!ensureBuffer(*buf, g_width, g_height))
return;
foreach ((parameter) yy; 0 .. buf.(field) _error_ buf.heightheight) // shade the 3×3 zones so the grid has eyes-appeal
foreach ((parameter) xx; 0 .. buf.buf.widthwidth)
{
immutable _error_ zonezone = (y * 3 / buf.buf.heightheight) * 3 + (x * 3 / buf.buf.widthwidth);
immutable _error_ gg = cast(uint)(0x30 + zone * 0x14);
buf.buf.pixelspixels[cast((unresolved type) size_tsize_t) y * buf.buf.widthwidth + x] = 0xff000000 | (g << 16) | (g << 8) | g;
}
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 (parameter) uint serialserial) 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* s, uint (parameter) uint serialserial) 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();
// Animated theme cursor: the client owns the animation timer. We ride the
// window's frame clock; wl_cursor_frame_and_duration picks the frame.
if (g_animCursor !is null && g_path == Path.Path.themetheme)
{
uint _error_ durdur;
immutable _error_ msms = cast(uint)((instrNowUs() - g_animStartUs) / 1000);
immutable _error_ frameframe = wl_cursor_frame_and_duration(g_animCursor, ms, &dur);
if (frame != g_animFrame)
{
g_animFrame = frame;
attachFrame(g_animCursor, frame, false, 0);
if (g_animLogged < 4)
{
g_animLogged++;
instrEvent("cursor_frame", "index=%d duration_ms=%u t_ms=%u", frame, dur, ms);
}
}
}
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 wl_surface_listener _error_ app.g_surfaceListenerg_surfaceListener = {
&onSurfaceEnter, &onSurfaceLeave, &onPreferredBufferScale, &onPreferredBufferTransform
};
__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()main()
{
instrInit("f12_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;
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;
}
// The per-compositor capability answer, logged before anything else runs.
instrEvent("cursor_shape_v1", "offered=%d version=%u seat=%d pointer=%d",
g_shapeMgr !is null ? 1 : 0, g_shapeMgrVersion,
g_seat !is null ? 1 : 0, g_seatHasPointer ? 1 : 0);
const (local variable) const(char*) forceforce = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_FORCE_PATH");
(__gshared global) app.Path app.g_pathg_path = g_shapeMgr !is null ? (enum) app.PathPath.(enum value) app.Path.shape = 0shape : (enum) app.PathPath.(enum value) app.Path.theme = 1theme;
if ((local variable) const(char*) forceforce !is null && int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp((local variable) const(char*) forceforce, "theme") == 0)
(__gshared global) app.Path app.g_pathg_path = (enum) app.PathPath.(enum value) app.Path.theme = 1theme;
if ((local variable) const(char*) forceforce !is null && int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp((local variable) const(char*) forceforce, "shape") == 0 && g_shapeMgr is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: shape path forced but wp_cursor_shape_manager_v1 missing\n");
wl_display_disconnect(g_display);
return 0;
}
instrEvent("path_selected", "path=%s", pathName());
// Theme machinery loads on both paths: the probe is part of the findings,
// and the bullseye/theme fallback needs the cursor surface anyway.
void app.loadTheme() nothrow @nogc(Re)load the Xcursor theme at base size × current buffer scale — the
HiDPI size-selection rule the client itself must implement on this path.
loadTheme();
if (g_theme !is null)
void app.probeTheme() nothrow @nogcDump every cursor the demo uses: which name resolved (modern vs legacy
Xcursor vocabulary) and the image size the theme picked for our size×scale.
probeTheme();
if (g_seat is null)
{
// headless weston: no seat at all. The registry probe + theme dump
// above are still the Tier-A evidence; cursors need a pointer.
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("ok: no seat on this compositor; registry+theme probe only\n");
wl_display_disconnect(g_display);
return 0;
}
// A seat with capabilities=0 (headless sway, no input devices) may gain
// the pointer capability later — e.g. when `wlrctl` plugs a
// zwlr_virtual_pointer in (./run.sh does exactly that). get_pointer
// before the capability exists is a hard wlroots protocol error
// ("wl_seat.get_pointer called when no pointer capability has existed"),
// so the demo strictly waits for the capabilities event.
wsi_wm_base_add_listener(g_wmBase, &g_wmBaseListener, null);
g_surface = wsi_compositor_create_surface(g_compositor);
wsi_surface_add_listener(g_surface, &g_surfaceListener, null);
g_cursorSurface = 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-f12-cursors");
wsi_toplevel_set_app_id(g_toplevel, "wsi-f12-cursors");
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;
// Blind request tour: if no real pointer focus arrived by half the
// cap, issue every cursor request anyway (serial 0) so WAYLAND_DEBUG
// still records the full protocol exchange. Pixels-need-eyes is
// Tier C either way; this keeps the request side Tier A.
if ((__gshared global) bool app.g_autoExitg_autoExit && g_pointer !is null && !(__gshared global) bool app.g_haveEnterg_haveEnter
&& (__gshared global) int app.g_tourIdxg_tourIdx <= (constant) int app.idxBullseye = 12idxBullseye && instrNowUs() > (__gshared global) long app.g_runUsCapg_runUsCap / 2)
{
if ((__gshared global) long app.g_tourNextUsg_tourNextUs < 0)
instrEvent("tour", "mode=blind serial=0 note=requests-only");
if (instrNowUs() >= (__gshared global) long app.g_tourNextUsg_tourNextUs)
{
void app.applyCursor(int idx, uint serial) nothrow @nogcThe single entry point every zone change funnels into.
applyCursor((__gshared global) int app.g_tourIdxg_tourIdx++, 0);
(__gshared global) long app.g_tourNextUsg_tourNextUs = instrNowUs() + 150_000;
}
}
if ((__gshared global) bool app.g_autoExitg_autoExit && instrNowUs() > (__gshared global) long app.g_runUsCapg_runUsCap)
(__gshared global) bool app.g_runningg_running = false;
}
// 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_bullseye !is null)
{
wsi_buffer_destroy(g_bullseye);
munmap(g_bullseyeMem, g_bullseyeBytes);
}
if (g_theme !is null)
wl_cursor_theme_destroy(g_theme);
if (g_frameCb !is null)
wsi_callback_destroy(g_frameCb);
if (g_shapeDev !is null)
wsi_cursor_shape_device_destroy(g_shapeDev);
if (g_shapeMgr !is null)
wsi_cursor_shape_manager_destroy(g_shapeMgr);
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_cursorSurface);
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 path=%s zones_visited_last=%d\n",
(__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_commitsg_commits, const(char)* app.pathName() nothrow @nogcpathName(), (__gshared global) int app.g_zoneg_zone);
return 0;
}