// F15 popup demo — xdg_popup + xdg_positioner + xdg_popup.grab on top of the
// xdg-shell scaffold (../scaffold/app.d; findings: ../../f15-popup.md).
//
// Four choreographed scenarios, selected by WSI_SCENARIO (input injected by
// ./run.sh through this binary's own `inject` mode — one zwlr_virtual_pointer
// device held across a WHOLE scenario, see ./inject.d):
//
// menu right-click opens a 3-item context menu: xdg_positioner with a
// 1x1 anchor rect at the click, gravity bottom_right,
// constraint_adjustment slide|flip, then xdg_popup.grab with the
// press serial. Hover highlight from wl_pointer.motion; a
// reposition probe (xdg_popup v3 reposition/repositioned) fires
// 350 ms after the popup maps; clicking item 2 ("submenu >")
// opens a NESTED popup parented on the first (the protocol
// requires the chain) with its own grab; a click OUTSIDE the
// window then dismisses — the compositor decides, the client only
// receives xdg_popup.popup_done (topmost first: measured).
// edge same right-click menu, but the window sits at the output's
// bottom-right corner so the requested placement is constrained —
// the xdg_popup.configure x/y is the COMPOSITOR's solution
// (slide? flip?); dismissal is Esc, delivered on the grab's
// wl_keyboard (wtype plugs a virtual keyboard in; libxkbcommon
// decodes its generated keymap).
// stale xdg_popup.grab with serial=1 when NO input event ever happened —
// what does the compositor do with an invalid grab serial?
// (protocol error? immediate popup_done? silent grant?) Measured.
// noinput headless-weston baseline (no seat → no serial → no grab):
// plain popup + nested popup + reposition, logging the
// positioner-solved placements only.
//
// Headless-safe: no compositor → SKIP, exit 0.
module (module) appapp;
import c; // ImportC: wayland-client + xdg-shell glue + xkbcommon + poll
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.mainWidth = 640mainWidth = 640;
enum int (constant) int app.mainHeight = 480mainHeight = 480;
enum uint (constant) uint app.BTN_LEFT = 272uBTN_LEFT = 0x110, (constant) uint app.BTN_RIGHT = 273uBTN_RIGHT = 0x111;
// Menu geometry (also hardcoded in ./inject.d and ./run.sh — keep in sync).
enum int (constant) int app.p1W = 160p1W = 160, (constant) int app.p1H = 96p1H = 96; // popup1: 3 items
enum int (constant) int app.p2W = 140p2W = 140, (constant) int app.p2H = 64p2H = 64; // popup2 (submenu): 2 items
enum int (constant) int app.itemX = 4itemX = 4, (constant) int app.itemW = 152itemW = 152, (constant) int app.itemY0 = 6itemY0 = 6, (constant) int app.itemStep = 28itemStep = 28, (constant) int app.itemH = 26itemH = 26;
enum (enum) app.ScenarioScenario
{
(enum value) app.Scenario.noinput = 0noinput,
(enum value) app.Scenario.menu = 1menu,
(enum value) app.Scenario.edge = 2edge,
(enum value) app.Scenario.stale = 3stale,
}
__gshared immutable(char*)[4] (immutable global) immutable(char*[4]) app.g_scenarioNamesg_scenarioNames = ["noinput", "menu", "edge", "stale"];
// -------------------------------------------------------------------- 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;
}
/// One wl_surface with either the toplevel or a popup role.
struct (struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf
{
wl_surface* (field) _error_ app.Surf.surfacesurface;
xdg_surface* (field) _error_ app.Surf.xdgSurfacexdgSurface;
xdg_toplevel* (field) _error_ app.Surf.topleveltoplevel;
xdg_popup* (field) _error_ app.Surf.popuppopup;
(struct) app.BufferBuffer[2] (field) _error_ app.Surf.buffersbuffers;
int (field) int app.Surf.widthwidth, (field) int app.Surf.heightheight;
int (field) int app.Surf.itemsitems; // popup menu item count (0 for the toplevel)
int (field) int app.Surf.hoverItemhoverItem = -1;
bool (field) bool app.Surf.configuredconfigured;
}
enum (alias) object.size_t = ulongsize_t (constant) ulong app.IDX_MAIN = 0LUIDX_MAIN = 0, (constant) ulong app.IDX_POPUP1 = 1LUIDX_POPUP1 = 1, (constant) ulong app.IDX_POPUP2 = 2LUIDX_POPUP2 = 2;
__gshared immutable(char*)[3] (immutable global) immutable(char*[3]) app.g_surfNamesg_surfNames = ["main", "popup1", "popup2"];
__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;
uint (__gshared global) uint app.g_wmBaseVersiong_wmBaseVersion;
wl_seat* _error_ app.g_seatg_seat;
wl_pointer* _error_ app.g_pointerg_pointer;
wl_keyboard* _error_ app.g_keyboardg_keyboard;
wl_callback* _error_ app.g_frameCbg_frameCb;
xkb_context* _error_ app.g_xkbCtxg_xkbCtx;
xkb_keymap* _error_ app.g_xkbKeymapg_xkbKeymap;
xkb_state* _error_ app.g_xkbStateg_xkbState;
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf[3] _error_ app.g_surfsg_surfs;
(enum) app.ScenarioScenario (__gshared global) app.Scenario app.g_scenariog_scenario = Scenario.Scenario.noinputnoinput;
int (__gshared global) int app.g_focusIdxg_focusIdx = -1; // pointer-focused surf index, -1 = none of ours
double (__gshared global) double app.g_ptrXg_ptrX, (__gshared global) double app.g_ptrYg_ptrY; // surface-local, on g_focusIdx
int (__gshared global) int app.g_pendingWg_pendingW, (__gshared global) int app.g_pendingHg_pendingH; // toplevel configure
bool (__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_firstPaintUsg_firstPaintUs = -1; // epoch for the noinput/stale step machine
long (__gshared global) long app.g_repositionAtUsg_repositionAtUs = -1; // menu/edge: scheduled after popup1 maps
bool (__gshared global) bool app.g_repositionSentg_repositionSent, (__gshared global) bool app.g_grabUsedg_grabUsed;
int (__gshared global) int app.g_stepg_step; // noinput/stale step machine
int (__gshared global) int app.g_doneEventsg_doneEvents; // popup_done counter (dismissal ORDER evidence)
int (__gshared global) int app.g_framesg_frames;
bool (__gshared global) bool app.g_staleConfiguredg_staleConfigured, (__gshared global) bool app.g_staleDoneg_staleDone;
}
// ------------------------------------------------------------------ popups
/// Create popup `idx` with the given positioner recipe, optionally grabbing
/// with `grabSerial` (>= 0). The initial commit carries no buffer (the
/// configure/ack contract applies to popups exactly as to toplevels).
void void app.openPopup(ulong idx, ulong parentIdx, int ax, int ay, int aw, int ah, uint anchor, uint gravity, uint adjustment, int w, int h, long grabSerial, const(char)* note) nothrow @nogcCreate popup idx with the given positioner recipe, optionally grabbing
with grabSerial (>= 0). The initial commit carries no buffer (the
configure/ack contract applies to popups exactly as to toplevels).
openPopup((alias) object.size_t = ulongsize_t (parameter) ulong idxidx, (alias) object.size_t = ulongsize_t (parameter) ulong parentIdxparentIdx, int (parameter) int axax, int (parameter) int ayay, int (parameter) int awaw, int (parameter) int ahah,
uint (parameter) uint anchoranchor, uint (parameter) uint gravitygravity, uint (parameter) uint adjustmentadjustment, int (parameter) int ww, int (parameter) int hh, long (parameter) long grabSerialgrabSerial,
const(char)* (parameter) const(char)* notenote) nothrow @nogc
{
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf* (local variable) _error_ ss = &g_surfs[idx];
s.s.widthwidth = w;
s.s.heightheight = h;
s.s.itemsitems = idx == IDX_POPUP2 ? 2 : 3;
s.s.hoverItemhoverItem = -1;
s.s.configuredconfigured = false;
xdg_positioner* (local variable) _error_ pospos = wsi_wm_base_create_positioner(g_wmBase);
wsi_positioner_set_size(pos, w, h);
wsi_positioner_set_anchor_rect(pos, ax, ay, aw, ah);
wsi_positioner_set_anchor(pos, anchor);
wsi_positioner_set_gravity(pos, gravity);
wsi_positioner_set_constraint_adjustment(pos, adjustment);
if ((__gshared global) uint app.g_wmBaseVersiong_wmBaseVersion >= 3)
wsi_positioner_set_reactive(pos); // auto-reposition on env change (v3)
s.s.surfacesurface = wsi_compositor_create_surface(g_compositor);
s.s.xdgSurfacexdgSurface = wsi_wm_base_get_xdg_surface(g_wmBase, s.s.surfacesurface);
wsi_xdg_surface_add_listener(s.s.xdgSurfacexdgSurface, &g_xdgSurfaceListener, cast(void*) idx);
s.s.popuppopup = wsi_xdg_surface_get_popup(s.s.xdgSurfacexdgSurface, g_surfs[parentIdx].g_surfs[parentIdx].xdgSurfacexdgSurface, pos);
wsi_popup_add_listener(s.s.popuppopup, &g_popupListener, cast(void*) idx);
wsi_positioner_destroy(pos);
instrEvent("popup_open",
"idx=%s parent=%s anchor_rect=%d,%d %dx%d anchor=%u gravity=%u adjustment=%u size=%dx%d note=%s",
g_surfNames[idx], g_surfNames[parentIdx], ax, ay, aw, ah,
anchor, gravity, adjustment, w, h, note);
if ((parameter) long grabSerialgrabSerial >= 0)
{
wsi_popup_grab(s.s.popuppopup, g_seat, cast(uint) grabSerial);
(__gshared global) bool app.g_grabUsedg_grabUsed = true;
instrEvent("grab", "idx=%s serial=%lld state=requested", g_surfNames[idx], grabSerial);
}
wsi_surface_commit(s.s.surfacesurface); // mandatory no-buffer first commit
}
/// Client-side dismissal (item click / Esc): topmost first, per the chain
/// rule ("they must be destroyed in the reverse order they were created in").
void void app.dismissPopups(const(char)* cause) nothrow @nogcClient-side dismissal (item click / Esc): topmost first, per the chain
rule ("they must be destroyed in the reverse order they were created in").
dismissPopups(const(char)* (parameter) const(char)* causecause) nothrow @nogc
{
foreach_reverse ((parameter) idxidx; (constant) ulong app.IDX_POPUP1 = 1LUIDX_POPUP1 .. g_surfs.(field) _error_ g_surfs.lengthlength)
if (g_surfs[idx].g_surfs[idx].popuppopup !is null)
{
instrEvent("popup_dismiss", "idx=%s cause=%s", g_surfNames[idx], cause);
destroyPopup(idx);
}
}
void void app.destroyPopup(ulong idx) nothrow @nogcdestroyPopup((alias) object.size_t = ulongsize_t (parameter) ulong idxidx) nothrow @nogc
{
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf* (local variable) _error_ ss = &g_surfs[idx];
if (s.(field) _error_ s.popuppopup is null)
return;
wsi_popup_destroy(s.s.popuppopup);
wsi_xdg_surface_destroy(s.s.xdgSurfacexdgSurface);
wsi_surface_destroy(s.s.surfacesurface);
foreach (ref (parameter) bb; s.(field) _error_ s.buffersbuffers)
if (b.b.handlehandle !is null)
{
wsi_buffer_destroy(b.b.handlehandle);
munmap(b.b.pixelspixels, b.b.byteSizebyteSize);
b = Buffer.init;
}
s.s.popuppopup = null;
s.s.xdgSurfacexdgSurface = null;
s.s.surfacesurface = null;
if ((__gshared global) int app.g_focusIdxg_focusIdx == cast(int) (parameter) ulong idxidx)
(__gshared global) int app.g_focusIdxg_focusIdx = -1;
}
/// The right-click menu (popup1). Anchor: 1x1 rect at the click position,
/// anchor point its bottom-right corner, gravity bottom_right ("down-right
/// of the cursor"), constraint_adjustment slide|flip on both axes.
void void app.openMenu(int clickX, int clickY, long serial) nothrow @nogcThe right-click menu (popup1). Anchor: 1x1 rect at the click position,
anchor point its bottom-right corner, gravity bottom_right ("down-right
of the cursor"), constraint_adjustment slide|flip on both axes.
openMenu(int (parameter) int clickXclickX, int (parameter) int clickYclickY, long (parameter) long serialserial) nothrow @nogc
{
enum uint (constant) uint app.openMenu.adj = __error__adj = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y;
void app.openPopup(ulong idx, ulong parentIdx, int ax, int ay, int aw, int ah, uint anchor, uint gravity, uint adjustment, int w, int h, long grabSerial, const(char)* note) nothrow @nogcCreate popup idx with the given positioner recipe, optionally grabbing
with grabSerial (>= 0). The initial commit carries no buffer (the
configure/ack contract applies to popups exactly as to toplevels).
openPopup((constant) ulong app.IDX_POPUP1 = 1LUIDX_POPUP1, (constant) ulong app.IDX_MAIN = 0LUIDX_MAIN, (parameter) int clickXclickX, (parameter) int clickYclickY, 1, 1,
XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
adj, (constant) int app.p1W = 160p1W, (constant) int app.p1H = 96p1H, (parameter) long serialserial, "context-menu");
}
/// The submenu (popup2), parented on POPUP1 — the chain the protocol
/// requires for a second grabbing popup. Anchored to item 2's rect, opening
/// to the right; flip_x so a constrained edge would mirror it to the left.
void void app.openSubmenu(long serial) nothrow @nogcThe submenu (popup2), parented on POPUP1 — the chain the protocol
requires for a second grabbing popup. Anchored to item 2's rect, opening
to the right; flip_x so a constrained edge would mirror it to the left.
openSubmenu(long (parameter) long serialserial) nothrow @nogc
{
enum uint (constant) uint app.openSubmenu.adj = __error__adj = XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y;
void app.openPopup(ulong idx, ulong parentIdx, int ax, int ay, int aw, int ah, uint anchor, uint gravity, uint adjustment, int w, int h, long grabSerial, const(char)* note) nothrow @nogcCreate popup idx with the given positioner recipe, optionally grabbing
with grabSerial (>= 0). The initial commit carries no buffer (the
configure/ack contract applies to popups exactly as to toplevels).
openPopup((constant) ulong app.IDX_POPUP2 = 2LUIDX_POPUP2, (constant) ulong app.IDX_POPUP1 = 1LUIDX_POPUP1, (constant) int app.itemX = 4itemX, (constant) int app.itemY0 = 6itemY0 + 2 * (constant) int app.itemStep = 28itemStep, (constant) int app.itemW = 152itemW, (constant) int app.itemH = 26itemH,
XDG_POSITIONER_ANCHOR_TOP_RIGHT, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT,
adj, (constant) int app.p2W = 140p2W, (constant) int app.p2H = 64p2H, (parameter) long serialserial, "submenu");
}
extern (C) void app.onPopupConfigureonPopupConfigure(void* data, xdg_popup* p, int (parameter) int xx, int (parameter) int yy, int (parameter) int ww, int (parameter) int hh) nothrow @nogc
{
immutable _error_ idxidx = cast((unresolved type) size_tsize_t) data;
// x/y are "relative to the upper left corner of the window geometry of
// the parent surface" — this is the compositor's placement DECISION,
// not an echo of the request.
instrEvent("popup_placed", "idx=%s x=%d y=%d size=%dx%d",
g_surfNames[idx], x, y, w, h);
if (w > 0 && h > 0)
{
g_surfs[idx].g_surfs[idx].widthwidth = w;
g_surfs[idx].g_surfs[idx].heightheight = h;
}
if (idx == IDX_POPUP1 && g_scenario == Scenario.Scenario.stalestale)
g_staleConfigured = true;
// Schedule the explicit-reposition probe once the menu has mapped.
if (idx == IDX_POPUP1 && !g_repositionSent && g_repositionAtUs < 0
&& g_scenario != Scenario.Scenario.edgeedge && g_wmBaseVersion >= 3)
g_repositionAtUs = instrNowUs() + 350_000;
}
extern (C) void app.onPopupDoneonPopupDone(void* data, xdg_popup* p) nothrow @nogc
{
immutable _error_ idxidx = cast((unresolved type) size_tsize_t) data;
g_doneEvents++;
// The compositor dismissed us; the client merely cleans up. The ORDER of
// these events across the chain is the topmost-first evidence.
instrEvent("popup_dismiss", "idx=%s cause=popup_done order=%d",
g_surfNames[idx], g_doneEvents);
if (idx == IDX_POPUP1 && g_scenario == Scenario.Scenario.stalestale)
g_staleDone = true;
destroyPopup(idx);
}
extern (C) void app.onPopupRepositionedonPopupRepositioned(void* data, xdg_popup* p, uint token) nothrow @nogc
{
instrEvent("repositioned", "idx=%s token=%u", g_surfNames[cast((unresolved type) size_tsize_t) data], token);
}
__gshared xdg_popup_listener _error_ app.g_popupListenerg_popupListener = {
&onPopupConfigure, &onPopupDone, &onPopupRepositioned
};
/// The v3 explicit-reposition probe: a fresh positioner (same recipe,
/// +24/+16 offset), xdg_popup.reposition(token=7) → expect repositioned(7)
/// followed by a new popup configure.
void void app.sendReposition() nothrow @nogcThe v3 explicit-reposition probe: a fresh positioner (same recipe,
+24/+16 offset), xdg_popup.reposition(token=7) → expect repositioned(7)
followed by a new popup configure.
sendReposition() nothrow @nogc
{
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf* (local variable) _error_ ss = &g_surfs[IDX_POPUP1];
if (s.(field) _error_ s.popuppopup is null)
return;
xdg_positioner* (local variable) _error_ pospos = wsi_wm_base_create_positioner(g_wmBase);
wsi_positioner_set_size(pos, p1W, p1H);
wsi_positioner_set_anchor_rect(pos, 180, 180, 1, 1);
wsi_positioner_set_anchor(pos, XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT);
wsi_positioner_set_gravity(pos, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
wsi_positioner_set_constraint_adjustment(pos,
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X
| XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y);
wsi_positioner_set_offset(pos, 24, 16);
wsi_positioner_set_reactive(pos);
wsi_popup_reposition(s.s.popuppopup, pos, 7);
wsi_positioner_destroy(pos);
(__gshared global) bool app.g_repositionSentg_repositionSent = true;
instrEvent("reposition_request", "idx=popup1 token=7 offset=24,16");
}
// ----------------------------------------------------------------- pointer
int app.surfIndexOfsurfIndexOf(wl_surface* (parameter) wl_surface* ss) nothrow @nogc
{
foreach ((parameter) ii, ref (parameter) sfsf; g_surfs)
if (sf.sf.surfacesurface is s)
return cast(int) i;
return -1;
}
int int app.itemAt(ulong idx, double x, double y) nothrow @nogcitemAt((alias) object.size_t = ulongsize_t (parameter) ulong idxidx, double (parameter) double xx, double (parameter) double yy) nothrow @nogc
{
if ((parameter) double xx < (constant) int app.itemX = 4itemX || (parameter) double xx >= (constant) int app.itemX = 4itemX + (constant) int app.itemW = 152itemW)
return -1;
foreach ((parameter) ii; 0 .. g_surfs[idx].(field) _error_ (__error)[idx].itemsitems)
{
immutable _error_ toptop = itemY0 + i * itemStep;
if (y >= top && y < top + itemH)
return i;
}
return -1;
}
extern (C) void app.onPointerEnteronPointerEnter(void* data, wl_pointer* p, uint (parameter) uint serialserial,
wl_surface* surf, int sx, int sy) nothrow @nogc
{
g_focusIdx = surfIndexOf(surf);
g_ptrX = sx / 256.0;
g_ptrY = sy / 256.0;
instrEvent("pointer_enter", "surface=%s serial=%u pos=%.1f,%.1f",
g_focusIdx >= 0 ? g_surfNames[g_focusIdx] : "other", serial, g_ptrX, g_ptrY);
if (g_focusIdx > 0)
{
immutable _error_ itemitem = itemAt(g_focusIdx, g_ptrX, g_ptrY);
if (item != g_surfs[g_focusIdx].g_surfs[g_focusIdx].hoverItemhoverItem)
{
g_surfs[g_focusIdx].g_surfs[g_focusIdx].hoverItemhoverItem = item;
instrEvent("hover", "idx=%s item=%d", g_surfNames[g_focusIdx], item);
render(g_focusIdx);
}
}
}
extern (C) void app.onPointerLeaveonPointerLeave(void* data, wl_pointer* p, uint (parameter) uint serialserial, wl_surface* surf) nothrow @nogc
{
immutable _error_ idxidx = surfIndexOf(surf);
instrEvent("pointer_leave", "surface=%s serial=%u",
idx >= 0 ? g_surfNames[idx] : "other", serial);
if (idx >= 0 && g_surfs[idx].g_surfs[idx].popuppopup !is null && g_surfs[idx].g_surfs[idx].hoverItemhoverItem != -1)
{
g_surfs[idx].g_surfs[idx].hoverItemhoverItem = -1;
render(idx);
}
g_focusIdx = -1;
}
extern (C) void app.onPointerMotiononPointerMotion(void* data, wl_pointer* p, uint time, int sx, int sy) nothrow @nogc
{
g_ptrX = sx / 256.0;
g_ptrY = sy / 256.0;
if (g_focusIdx <= 0)
return;
(unresolved type) SurfSurf* _error_ ss = &g_surfs[g_focusIdx];
immutable _error_ itemitem = itemAt(g_focusIdx, g_ptrX, g_ptrY);
if (item != s.s.hoverItemhoverItem)
{
s.s.hoverItemhoverItem = item;
instrEvent("hover", "idx=%s item=%d", g_surfNames[g_focusIdx], item);
render(g_focusIdx);
}
}
extern (C) void app.onPointerButtononPointerButton(void* data, wl_pointer* p, uint (parameter) uint serialserial, uint time,
uint button, uint (parameter) uint statestate) nothrow @nogc
{
instrEvent("button", "surface=%s serial=%u button=0x%x state=%u",
g_focusIdx >= 0 ? g_surfNames[g_focusIdx] : "none", serial, button, state);
if (state != WL_POINTER_BUTTON_STATE_PRESSED)
return;
if (g_focusIdx == IDX_MAIN && button == BTN_RIGHT && g_surfs[IDX_POPUP1].g_surfs[IDX_POPUP1].popuppopup is null)
{
openMenu(cast(int) g_ptrX, cast(int) g_ptrY, serial);
return;
}
if (g_focusIdx == IDX_POPUP1 && button == BTN_LEFT)
{
immutable _error_ itemitem = g_surfs[IDX_POPUP1].g_surfs[IDX_POPUP1].hoverItemhoverItem;
if (item == 2 && g_surfs[IDX_POPUP2].g_surfs[IDX_POPUP2].popuppopup is null)
openSubmenu(serial); // nested grab: parent must be the grabbed popup
else if (item >= 0)
{
instrEvent("item_activated", "idx=popup1 item=%d", item);
dismissPopups("item-click");
}
return;
}
if (g_focusIdx == IDX_POPUP2 && button == BTN_LEFT)
{
instrEvent("item_activated", "idx=popup2 item=%d", g_surfs[IDX_POPUP2].g_surfs[IDX_POPUP2].hoverItemhoverItem);
dismissPopups("item-click");
return;
}
// A press on the parent window while a grab is held: does the grab route
// it here at all, and does the compositor treat "inside the same client
// but outside the popups" as outside? Logged either way (finding).
if (g_focusIdx == IDX_MAIN && g_surfs[IDX_POPUP1].g_surfs[IDX_POPUP1].popuppopup !is null)
instrEvent("button_on_parent_during_grab", "serial=%u button=0x%x", serial, button);
}
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
};
// ---------------------------------------------------------------- keyboard
extern (C) void app.onKeymaponKeymap(void* data, wl_keyboard* kb, uint format, int (parameter) int fdfd, uint (parameter) uint sizesize) nothrow @nogc
{
instrEvent("keymap", "format=%u size=%u", format, size);
if (format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
{
void* _error_ memmem = mmap(null, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mem !is cast(void*)-1)
{
if (g_xkbKeymap !is null)
xkb_keymap_unref(g_xkbKeymap);
if (g_xkbState !is null)
xkb_state_unref(g_xkbState);
g_xkbKeymap = xkb_keymap_new_from_string(g_xkbCtx, cast(char*) mem,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
g_xkbState = g_xkbKeymap !is null ? xkb_state_new(g_xkbKeymap) : null;
munmap(mem, size);
}
}
close(fd);
}
extern (C) void app.onKeyboardEnteronKeyboardEnter(void* data, wl_keyboard* kb, uint (parameter) uint serialserial,
wl_surface* surf, wl_array* keys) nothrow @nogc
{
// Under an xdg_popup.grab the keyboard focus must land on the POPUP —
// this line is the proof of where the grab routed it.
immutable _error_ idxidx = surfIndexOf(surf);
instrEvent("keyboard_enter", "surface=%s serial=%u keys_down=%zu",
idx >= 0 ? g_surfNames[idx] : "other", serial, keys.keys.sizesize / uint.sizeof);
// Keys already held at enter arrive in the `keys` array, NOT as key
// events — a freshly plugged wtype keyboard's Esc press can land here.
foreach ((parameter) keykey; (cast(uint*) keys.data)[0 .. keys.size / uint.sizeof])
handleKey(key, WL_KEYBOARD_KEY_STATE_PRESSED);
}
void void app.handleKey(uint key, uint state) nothrow @nogchandleKey(uint (parameter) uint keykey, uint (parameter) uint statestate) nothrow @nogc
{
uint (local variable) uint symsym = 0;
if (g_xkbState !is null)
(local variable) uint symsym = xkb_state_key_get_one_sym(g_xkbState, key + 8);
instrEvent("key", "key=%u keysym=0x%x state=%u", key, sym, state);
if ((parameter) uint statestate == WL_KEYBOARD_KEY_STATE_PRESSED && (local variable) uint symsym == XKB_KEY_Escape
&& g_surfs[IDX_POPUP1].(field) _error_ (__error)[IDX_POPUP1].popuppopup !is null)
void app.dismissPopups(const(char)* cause) nothrow @nogcClient-side dismissal (item click / Esc): topmost first, per the chain
rule ("they must be destroyed in the reverse order they were created in").
dismissPopups("esc-grab-keyboard");
}
extern (C) void app.onKeyboardLeaveonKeyboardLeave(void* data, wl_keyboard* kb, uint (parameter) uint serialserial, wl_surface* surf) nothrow @nogc
{
immutable _error_ idxidx = surfIndexOf(surf);
instrEvent("keyboard_leave", "surface=%s serial=%u",
idx >= 0 ? g_surfNames[idx] : "other", serial);
}
extern (C) void app.onKeyonKey(void* data, wl_keyboard* kb, uint (parameter) uint serialserial, uint time,
uint (parameter) uint keykey, uint (parameter) uint statestate) nothrow @nogc
{
handleKey(key, state);
}
extern (C) void app.onModifiersonModifiers(void* data, wl_keyboard* kb, uint (parameter) uint serialserial,
uint depressed, uint latched, uint locked, uint group) nothrow @nogc
{
if (g_xkbState !is null)
xkb_state_update_mask(g_xkbState, depressed, latched, locked, 0, 0, group);
}
extern (C) void app.onRepeatInfoonRepeatInfo(void* data, wl_keyboard* kb, int rate, int delay) nothrow @nogc {}
__gshared wl_keyboard_listener _error_ app.g_keyboardListenerg_keyboardListener = {
&onKeymap, &onKeyboardEnter, &onKeyboardLeave, &onKey, &onModifiers, &onRepeatInfo
};
// ------------------------------------------------------------------- seat
extern (C) void app.onSeatCapabilitiesonSeatCapabilities(void* data, wl_seat* (parameter) wl_seat* ss, uint caps) nothrow @nogc
{
instrEvent("seat_capabilities", "caps=%u", caps);
immutable _error_ hasPointerhasPointer = (caps & WL_SEAT_CAPABILITY_POINTER) != 0;
immutable _error_ hasKeyboardhasKeyboard = (caps & WL_SEAT_CAPABILITY_KEYBOARD) != 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)
{
wsi_pointer_destroy(g_pointer);
g_pointer = null;
g_focusIdx = -1;
// Does the device unplug (capability drop) dismiss an active grab
// popup? Whatever follows this line in the log is the answer.
instrEvent("pointer_dropped", "popup1_open=%d popup2_open=%d",
g_surfs[IDX_POPUP1].g_surfs[IDX_POPUP1].popuppopup !is null ? 1 : 0,
g_surfs[IDX_POPUP2].g_surfs[IDX_POPUP2].popuppopup !is null ? 1 : 0);
}
if (hasKeyboard && g_keyboard is null)
{
g_keyboard = wsi_seat_get_keyboard(g_seat);
wsi_keyboard_add_listener(g_keyboard, &g_keyboardListener, null);
}
else if (!hasKeyboard && g_keyboard !is null)
{
wsi_keyboard_destroy(g_keyboard);
g_keyboard = null;
}
}
extern (C) void app.onSeatNameonSeatName(void* data, wl_seat* (parameter) wl_seat* ss, const(char)* name) nothrow @nogc {}
__gshared wl_seat_listener _error_ app.g_seatListenerg_seatListener = {&onSeatCapabilities, &onSeatName};
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)
{
// v3+ unlocks xdg_popup.reposition/repositioned and set_reactive.
g_wmBaseVersion = capped(ver, 5);
g_wmBase = cast(xdg_wm_base*) wsi_registry_bind(reg, name,
&xdg_wm_base_interface, g_wmBaseVersion);
}
else if (strcmp(iface, wl_seat_interface.name) == 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);
}
}
extern (C) void app.onGlobalRemoveonGlobalRemove(void* data, wl_registry* reg, uint name) nothrow @nogc {}
__gshared wl_registry_listener _error_ app.g_registryListenerg_registryListener = {&onGlobal, &onGlobalRemove};
// ------------------------------------------------ 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-f15", 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;
}
/// Paint surf `idx`: flat background for the main window; solid menu panel
/// with item rows + hover highlight for popups. Event-driven redraw only
/// (no frame-callback loop — popups repaint on hover changes).
void void app.render(ulong idx) nothrow @nogcPaint surf idx: flat background for the main window; solid menu panel
with item rows + hover highlight for popups. Event-driven redraw only
(no frame-callback loop — popups repaint on hover changes).
render((alias) object.size_t = ulongsize_t (parameter) ulong idxidx) nothrow @nogc
{
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf* (local variable) _error_ ss = &g_surfs[idx];
if (s.(field) _error_ s.surfacesurface is null || !s.(field) _error_ s.configuredconfigured)
return;
(struct) app.BufferBuffer* (local variable) _error_ bufbuf = null;
foreach (ref (parameter) bb; s.(field) _error_ s.buffersbuffers)
if (!b.b.busybusy)
{
buf = &b;
break;
}
if (buf is null || !ensureBuffer(*buf, s.s.widthwidth, s.s.heightheight))
return;
immutable uint (local variable) immutable(uint) bgbg = (parameter) ulong idxidx == (constant) ulong app.IDX_MAIN = 0LUIDX_MAIN ? 0xff20283c : 0xff3a3a44;
foreach ((parameter) yy; 0 .. buf.(field) _error_ buf.heightheight)
foreach ((parameter) xx; 0 .. buf.buf.widthwidth)
{
uint _error_ cc = bg;
if (idx != IDX_MAIN)
{
if (x == 0 || y == 0 || x == buf.buf.widthwidth - 1 || y == buf.buf.heightheight - 1)
c = 0xff8888a0; // panel border
else
{
immutable _error_ itemitem = itemAt(idx, x, y);
if (item >= 0)
c = item == s.s.hoverItemhoverItem ? 0xff5a6a92 : 0xff44444e;
}
}
buf.buf.pixelspixels[cast((unresolved type) size_tsize_t) y * buf.buf.widthwidth + x] = c;
}
wsi_surface_attach(s.s.surfacesurface, buf.buf.handlehandle, 0, 0);
wsi_surface_damage_buffer(s.s.surfacesurface, 0, 0, buf.buf.widthwidth, buf.buf.heightheight);
if ((parameter) ulong idxidx == (constant) ulong app.IDX_MAIN = 0LUIDX_MAIN && (__gshared global) int app.g_framesg_frames == 0 && g_frameCb is null)
{
g_frameCb = wsi_surface_frame(s.s.surfacesurface);
wsi_callback_add_listener(g_frameCb, &g_frameListener, null);
}
wsi_surface_commit(s.s.surfacesurface);
buf.buf.busybusy = true;
}
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* (parameter) xdg_toplevel* tt, int (parameter) int ww, int (parameter) int hh,
wl_array* states) nothrow @nogc
{
g_pendingW = w;
g_pendingH = h;
}
extern (C) void app.onXdgSurfaceConfigureonXdgSurfaceConfigure(void* data, xdg_surface* (parameter) xdg_surface* ss, uint (parameter) uint serialserial) nothrow @nogc
{
immutable _error_ idxidx = cast((unresolved type) size_tsize_t) data;
(unresolved type) SurfSurf* _error_ sfsf = &g_surfs[idx];
if (idx == IDX_MAIN)
{
sf.sf.widthwidth = g_pendingW > 0 ? g_pendingW : mainWidth;
sf.sf.heightheight = g_pendingH > 0 ? g_pendingH : mainHeight;
}
instrEvent("configure", "idx=%s serial=%u size=%dx%d",
g_surfNames[idx], serial, sf.sf.widthwidth, sf.sf.heightheight);
wsi_xdg_surface_ack_configure(s, serial);
immutable _error_ wasConfiguredwasConfigured = sf.sf.configuredconfigured;
sf.sf.configuredconfigured = true;
if (idx == IDX_MAIN && !wasConfigured)
instrFirstConfigure();
render(idx);
if (idx == IDX_MAIN && g_firstPaintUs < 0)
g_firstPaintUs = instrNowUs();
}
extern (C) void app.onToplevelCloseonToplevelClose(void* data, xdg_toplevel* (parameter) xdg_toplevel* tt) nothrow @nogc
{
instrCloseRequested();
g_running = false;
}
extern (C) void app.onToplevelConfigureBoundsonToplevelConfigureBounds(void* data, xdg_toplevel* (parameter) xdg_toplevel* tt, int (parameter) int ww, int (parameter) int hh) nothrow @nogc {}
extern (C) void app.onToplevelWmCapabilitiesonToplevelWmCapabilities(void* data, xdg_toplevel* (parameter) xdg_toplevel* tt, 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;
}
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();
}
__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};
// ----------------------------------------------------- scenario step machine
/// Time-driven steps for the input-less scenarios (noinput, stale) and the
/// deferred reposition probe of menu/noinput.
void void app.tick() nothrow @nogcTime-driven steps for the input-less scenarios (noinput, stale) and the
deferred reposition probe of menu/noinput.
tick() nothrow @nogc
{
immutable (local variable) immutable(_error_) nownow = instrNowUs();
if ((__gshared global) long app.g_repositionAtUsg_repositionAtUs >= 0 && now >= (__gshared global) long app.g_repositionAtUsg_repositionAtUs && !(__gshared global) bool app.g_repositionSentg_repositionSent)
{
(__gshared global) long app.g_repositionAtUsg_repositionAtUs = -1;
void app.sendReposition() nothrow @nogcThe v3 explicit-reposition probe: a fresh positioner (same recipe,
+24/+16 offset), xdg_popup.reposition(token=7) → expect repositioned(7)
followed by a new popup configure.
sendReposition();
}
if ((__gshared global) long app.g_firstPaintUsg_firstPaintUs < 0)
return;
immutable (local variable) immutable(_error_) tt = now - (__gshared global) long app.g_firstPaintUsg_firstPaintUs;
final switch ((__gshared global) app.Scenario app.g_scenariog_scenario)
{
case (enum) app.ScenarioScenario.(enum value) app.Scenario.menu = 1menu:
case (enum) app.ScenarioScenario.(enum value) app.Scenario.edge = 2edge:
break; // input-driven
case (enum) app.ScenarioScenario.(enum value) app.Scenario.noinput = 0noinput:
if ((__gshared global) int app.g_stepg_step == 0 && t > 300_000)
{
(__gshared global) int app.g_stepg_step = 1;
void app.openMenu(int clickX, int clickY, long serial) nothrow @nogcThe right-click menu (popup1). Anchor: 1x1 rect at the click position,
anchor point its bottom-right corner, gravity bottom_right ("down-right
of the cursor"), constraint_adjustment slide|flip on both axes.
openMenu(180, 180, -1); // no seat → no serial → no grab
}
else if ((__gshared global) int app.g_stepg_step == 1 && t > 1_300_000 && g_surfs[IDX_POPUP1].(field) _error_ (__error)[IDX_POPUP1].popuppopup !is null)
{
(__gshared global) int app.g_stepg_step = 2;
void app.openSubmenu(long serial) nothrow @nogcThe submenu (popup2), parented on POPUP1 — the chain the protocol
requires for a second grabbing popup. Anchored to item 2's rect, opening
to the right; flip_x so a constrained edge would mirror it to the left.
openSubmenu(-1);
}
else if ((__gshared global) int app.g_stepg_step == 2 && t > 2_000_000)
{
(__gshared global) int app.g_stepg_step = 3;
void app.dismissPopups(const(char)* cause) nothrow @nogcClient-side dismissal (item click / Esc): topmost first, per the chain
rule ("they must be destroyed in the reverse order they were created in").
dismissPopups("client-teardown");
}
break;
case (enum) app.ScenarioScenario.(enum value) app.Scenario.stale = 3stale:
if ((__gshared global) int app.g_stepg_step == 0 && t > 300_000)
{
(__gshared global) int app.g_stepg_step = 1;
// The probe: a grab serial that never came from any input event.
void app.openMenu(int clickX, int clickY, long serial) nothrow @nogcThe right-click menu (popup1). Anchor: 1x1 rect at the click position,
anchor point its bottom-right corner, gravity bottom_right ("down-right
of the cursor"), constraint_adjustment slide|flip on both axes.
openMenu(180, 180, 1);
}
else if ((__gshared global) int app.g_stepg_step == 1 && t > 1_800_000)
{
(__gshared global) int app.g_stepg_step = 2;
instrEvent("stale_grab_result",
"configure_received=%d popup_done_received=%d connection_alive=%d",
g_staleConfigured ? 1 : 0, g_staleDone ? 1 : 0,
wl_display_get_error(g_display) == 0 ? 1 : 0);
(__gshared global) bool app.g_runningg_running = false;
}
break;
}
}
// --------------------------------------------------------------------- main
int int D main(string[] args)main((alias) object.string = stringstring[] (parameter) string[] argsargs)
{
if ((parameter) string[] argsargs.(field) ulong string[].lengthlength >= 2 && (parameter) string[] argsargs[1].(field) ulong string.lengthlength >= 4 && (parameter) string[] argsargs[1][0 .. 4] == "inje")
{
import inject : injectMain;
return injectMain(args.args.lengthlength >= 3 ? args[2] : "menu");
}
instrInit("f15_wayland");
const (local variable) const(char*) scenscen = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_SCENARIO");
if ((local variable) const(char*) scenscen !is null)
foreach ((parameter) ulong ii, (parameter) immutable(char*) nn; (immutable global) immutable(char*[4]) app.g_scenarioNamesg_scenarioNames)
if (int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp((local variable) const(char*) scenscen, (local variable) immutable(char*) nn) == 0)
(__gshared global) app.Scenario app.g_scenariog_scenario = cast((enum) app.ScenarioScenario) (local variable) ulong ii;
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_xkbCtx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
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("globals", "xdg_wm_base_version=%u seat=%d scenario=%s",
g_wmBaseVersion, g_seat !is null ? 1 : 0, g_scenarioNames[g_scenario]);
if (g_seat is null && (__gshared global) app.Scenario app.g_scenariog_scenario != (enum) app.ScenarioScenario.(enum value) app.Scenario.noinput = 0noinput)
{
// grab/stale need a wl_seat object; headless weston has none.
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: scenario %s needs a wl_seat; falling back to noinput\n",
(immutable global) immutable(char*[4]) app.g_scenarioNamesg_scenarioNames[(__gshared global) app.Scenario app.g_scenariog_scenario]);
(__gshared global) app.Scenario app.g_scenariog_scenario = (enum) app.ScenarioScenario.(enum value) app.Scenario.noinput = 0noinput;
}
wsi_wm_base_add_listener(g_wmBase, &g_wmBaseListener, null);
(struct) app.SurfOne wl_surface with either the toplevel or a popup role.
Surf* (local variable) _error_ mm = &g_surfs[IDX_MAIN];
m.m.widthwidth = mainWidth;
m.m.heightheight = mainHeight;
m.m.surfacesurface = wsi_compositor_create_surface(g_compositor);
m.m.xdgSurfacexdgSurface = wsi_wm_base_get_xdg_surface(g_wmBase, m.m.surfacesurface);
wsi_xdg_surface_add_listener(m.m.xdgSurfacexdgSurface, &g_xdgSurfaceListener, cast(void*) IDX_MAIN);
m.m.topleveltoplevel = wsi_xdg_surface_get_toplevel(m.m.xdgSurfacexdgSurface);
wsi_toplevel_add_listener(m.m.topleveltoplevel, &g_toplevelListener, null);
const (local variable) const(char*) appIdEnvappIdEnv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_APP_ID");
const (local variable) const(char*) appIdappId = (local variable) const(char*) appIdEnvappIdEnv !is null ? (local variable) const(char*) appIdEnvappIdEnv : "wsi-f15-popup".(constant) immutable(char)* "wsi-f15-popup".ptr = "wsi-f15-popup"ptr;
wsi_toplevel_set_title(m.m.topleveltoplevel, appId);
wsi_toplevel_set_app_id(m.m.topleveltoplevel, appId);
instrWindowCreated();
wsi_surface_commit(m.m.surfacesurface); // mandatory no-buffer first commit
immutable (local variable) immutable(_error_) dispFddispFd = wl_display_get_fd(g_display);
bool (local variable) bool displayDeaddisplayDead = false;
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)
{
// A protocol error (the stale-grab probe's possible outcome)
// kills the connection — capture WHICH error before exiting.
wl_interface* (local variable) _error_ errIfaceerrIface;
uint (local variable) uint errIderrId;
immutable (local variable) immutable(_error_) codecode = wl_display_get_protocol_error(g_display, &errIface, &errId);
instrEvent("display_error", "errno=%d protocol_code=%u interface=%s id=%u",
wl_display_get_error(g_display), code,
errIface !is null ? errIface.name : "?", errId);
(local variable) bool displayDeaddisplayDead = true;
break;
}
void app.tick() nothrow @nogcTime-driven steps for the input-less scenarios (noinput, stale) and the
deferred reposition probe of menu/noinput.
tick();
if ((__gshared global) bool app.g_autoExitg_autoExit && instrNowUs() > (__gshared global) long app.g_runUsCapg_runUsCap)
(__gshared global) bool app.g_runningg_running = false;
}
instrEvent("summary",
"scenario=%s grab_used=%d popup_done_events=%d frames=%d display_dead=%d",
g_scenarioNames[g_scenario], g_grabUsed ? 1 : 0, g_doneEvents,
g_frames, displayDead ? 1 : 0);
if (!(local variable) bool displayDeaddisplayDead)
{
void app.dismissPopups(const(char)* cause) nothrow @nogcClient-side dismissal (item click / Esc): topmost first, per the chain
rule ("they must be destroyed in the reverse order they were created in").
dismissPopups("client-teardown");
foreach (ref (parameter) bb; g_surfs[IDX_MAIN].(field) _error_ (__error)[IDX_MAIN].buffersbuffers)
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_keyboard !is null)
wsi_keyboard_destroy(g_keyboard);
if (g_pointer !is null)
wsi_pointer_destroy(g_pointer);
if (g_seat !is null)
wsi_seat_destroy(g_seat);
wsi_toplevel_destroy(g_surfs[IDX_MAIN].g_surfs[IDX_MAIN].topleveltoplevel);
wsi_xdg_surface_destroy(g_surfs[IDX_MAIN].g_surfs[IDX_MAIN].xdgSurfacexdgSurface);
wsi_surface_destroy(g_surfs[IDX_MAIN].g_surfs[IDX_MAIN].surfacesurface);
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);
}
if (g_xkbState !is null)
xkb_state_unref(g_xkbState);
if (g_xkbKeymap !is null)
xkb_keymap_unref(g_xkbKeymap);
if (g_xkbCtx !is null)
xkb_context_unref(g_xkbCtx);
wl_display_disconnect(g_display);
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("ok: scenario=%s popup_done_events=%d display_dead=%d\n",
(immutable global) immutable(char*[4]) app.g_scenarioNamesg_scenarioNames[(__gshared global) app.Scenario app.g_scenariog_scenario], (__gshared global) int app.g_doneEventsg_doneEvents, (local variable) bool displayDeaddisplayDead ? 1 : 0);
return 0;
}