// X11 F11 demo — scroll fidelity (the feature contract:
// ../../../features/f11-scroll.md; findings: ../../f11-scroll.md). Built on
// the scaffold (../scaffold/app.d), drawing reduced to a server-side ruler.
//
// X11 represents one physical scroll TWICE: legacy core buttons 4/5/6/7 and
// XI2 smooth-scroll valuators (XIScrollClass; deltas ride XI_Motion). This
// demo captures both streams for the same physical event, and measures the
// dedupe machinery between them:
//
// * device query: XIQueryDevice dump of every XIScrollClass (number,
// scroll_type, increment, flags) and XIValuatorClass — on Xvfb the
// honest answer is `scroll_class … none=1` for every device
// * two connections, one window: dpy (this client) selects XI2 button +
// motion events; dpy2 (a *second* client) selects core ButtonPress —
// core ButtonPressMask is one-client-exclusive per window, so splitting
// clients is also the only way to capture both streams concurrently
// * phase A `dual_client`: core stream on dpy2, XI2 stream on dpy
// * phase B `dual_selection`: dpy2 deselects; dpy selects BOTH core and
// XI2 button events — measures which representation(s) one client gets
// * phase C `core_only`: dpy clears its XI2 selection — does core
// delivery resume once no XI2 selection exists on the window?
// * XIPointerEmulated: logged (`emulated=0|1`) on every XI2 button event —
// are xdotool's `click 4/5` wheel events tagged as emulated?
// * a scrollable ruler (1 detent = 20 px) + per-gesture totals
// (`gesture_summary …`, gestures split on a 400 ms idle gap)
// * built-in pass (no xdotool): XSendEvent-fabricated button 4/5 presses —
// delivered with send_event=1 to core selectors only, never as XI2
//
// WSI_DRIVEN=1 stretches the phases to 4 s each so xdotool (see ./run.sh)
// can deliver real XTest wheel clicks. Headless-safe: no display, or no
// XInputExtension -> prints `SKIP:`, exit 0.
module (module) appapp;
import (module) cc; // ImportC: Xlib + Xutil + XInput2 + poll
import (module) instrumentinstrument;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.configD compatible types that correspond to various basic types in associated
C and C++ compilers.
Source
core/stdc/config.d
config : c_long, c_ulong;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf, (alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) app.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen;
// ---------------------------------------------------------------------------
// Constants ImportC cannot export (all #defines), re-declared per the
// scaffold gotcha: core masks/types and the whole of <X11/extensions/XI2.h>.
enum : c_long
{
(enum value) app.KeyPressMask = 1LKeyPressMask = 1L << 0,
(enum value) app.ButtonPressMask = 4LButtonPressMask = 1L << 2,
(enum value) app.ButtonReleaseMask = 8LButtonReleaseMask = 1L << 3,
(enum value) app.ExposureMask = 32768LExposureMask = 1L << 15,
(enum value) app.StructureNotifyMask = 131072LStructureNotifyMask = 1L << 17,
}
enum // core XEvent.type discriminators; GenericEvent carries all XI2 events
{
(enum value) app.KeyPress = 2KeyPress = 2, (enum value) app.ButtonPress = 4ButtonPress = 4, (enum value) app.ButtonRelease = 5ButtonRelease = 5,
(enum value) app.Expose = 12Expose = 12, (enum value) app.ClientMessage = 33ClientMessage = 33, (enum value) app.GenericEvent = 35GenericEvent = 35,
}
enum (constant) int app.False = 0False = 0;
enum (constant) int app.True = 1True = 1;
enum (constant) int app.POLLIN = 1POLLIN = 0x001;
enum // XI2.h
{
(enum value) app.XIAllDevices = 0XIAllDevices = 0,
(enum value) app.XIMasterPointer = 1XIMasterPointer = 1, (enum value) app.XIMasterKeyboard = 2XIMasterKeyboard = 2, (enum value) app.XISlavePointer = 3XISlavePointer = 3,
(enum value) app.XISlaveKeyboard = 4XISlaveKeyboard = 4, (enum value) app.XIFloatingSlave = 5XIFloatingSlave = 5,
(enum value) app.XI_ButtonPress = 4XI_ButtonPress = 4, (enum value) app.XI_ButtonRelease = 5XI_ButtonRelease = 5, (enum value) app.XI_Motion = 6XI_Motion = 6,
(enum value) app.XIButtonClass = 1XIButtonClass = 1, (enum value) app.XIValuatorClass = 2XIValuatorClass = 2, (enum value) app.XIScrollClass = 3XIScrollClass = 3,
(enum value) app.XIScrollTypeVertical = 1XIScrollTypeVertical = 1, (enum value) app.XIScrollTypeHorizontal = 2XIScrollTypeHorizontal = 2,
(enum value) app.XIScrollFlagNoEmulation = 1 << 0XIScrollFlagNoEmulation = 1 << 0, (enum value) app.XIScrollFlagPreferred = 1 << 1XIScrollFlagPreferred = 1 << 1,
(enum value) app.XIPointerEmulated = 1 << 16XIPointerEmulated = 1 << 16, // event flag: emulated from another event
}
immutable (alias) object.string = stringstring[5] (immutable global) immutable(string[5]) app.useNameuseName =
["MasterPointer", "MasterKeyboard", "SlavePointer", "SlaveKeyboard", "FloatingSlave"];
/// XISetMask is a macro; same bit math, D-side.
void void app.xiSetMask(scope ubyte[] mask, int event) nothrow @nogcXISetMask is a macro; same bit math, D-side.
xiSetMask(scope ubyte[] (parameter) ubyte[] maskmask, int (parameter) int eventevent) @nogc nothrow
{
(parameter) ubyte[] maskmask[(parameter) int eventevent >> 3] |= cast(ubyte)(1 << ((parameter) int eventevent & 7));
}
/// One smooth-scroll axis discovered via XIQueryDevice: XI_Motion carries the
/// axis as an *absolute accumulating* valuator; one detent = `increment`.
struct (struct) app.ScrollAxisOne smooth-scroll axis discovered via XIQueryDevice: XI_Motion carries the
axis as an absolute accumulating valuator; one detent = increment.
ScrollAxis
{
int (field) int app.ScrollAxis.deviceiddeviceid, (field) int app.ScrollAxis.numbernumber, (field) int app.ScrollAxis.scrollTypescrollType;
double (field) double app.ScrollAxis.incrementincrement;
double (field) double app.ScrollAxis.lastlast; // last absolute valuator value seen (NaN until primed)
}
int int D main()main()
{
void instrument.initInstrument(const(char)* demoName) nothrow @nogcNames the demo, starts the monotonic clock, and emits init_start.
Call this first, before any platform API call.
initInstrument("x11_f11");
const (local variable) const(bool) autoExitautoExit = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_AUTO_EXIT") !is null;
const (local variable) const(bool) drivendriven = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_DRIVEN") !is null; // xdotool owns the input
const (local variable) const(int) phaseUsphaseUs = (local variable) const(bool) drivendriven ? 4_000_000 : 600_000;
Display* (local variable) _error_ dpydpy = XOpenDisplay(null); // client 1: the XI2 listener
if (dpy is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: no X11 display (XOpenDisplay returned null)\n");
return 0;
}
int (local variable) int xiOpcodexiOpcode, (local variable) int xiEventBasexiEventBase, (local variable) int xiErrorBasexiErrorBase;
if (!XQueryExtension(dpy, "XInputExtension", &xiOpcode, &xiEventBase, &xiErrorBase))
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: no XInputExtension on this display\n");
XCloseDisplay(dpy);
return 0;
}
int (local variable) int xiMajorxiMajor = 2, (local variable) int xiMinorxiMinor = 3;
const (local variable) const(_error_) xiStatusxiStatus = XIQueryVersion(dpy, &xiMajor, &xiMinor);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XIQueryVersion status=%d server=%d.%d", xiStatus, (local variable) int xiMajorxiMajor, (local variable) int xiMinorxiMinor);
if (xiStatus != 0 || (local variable) int xiMajorxiMajor < 2)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: server does not speak XI2 (got %d.%d)\n", (local variable) int xiMajorxiMajor, (local variable) int xiMinorxiMinor);
XCloseDisplay(dpy);
return 0;
}
const (local variable) const(_error_) screenscreen = XDefaultScreen(dpy);
const (local variable) const(_error_) rootroot = XRootWindow(dpy, screen);
// -- Scroll capability dump: which devices have XIScrollClass? ------------
// (Per the XI 2.1 smooth-scrolling spec a wheel device exposes one scroll
// class per axis; Xvfb's virtual pointers expose none — logged honestly.)
(struct) app.ScrollAxisOne smooth-scroll axis discovered via XIQueryDevice: XI_Motion carries the
axis as an absolute accumulating valuator; one detent = increment.
ScrollAxis[16] (local variable) app.ScrollAxis[16] axesaxes;
int (local variable) int nAxesnAxes;
int (local variable) int nDevsnDevs;
int (local variable) int masterPtrmasterPtr = 2; // conventional VCP id; replaced by the query below
XIDeviceInfo* (local variable) _error_ devsdevs = XIQueryDevice(dpy, XIAllDevices, &nDevs);
foreach ((local variable) int ii; 0 .. (local variable) int nDevsnDevs)
{
const (local variable) const(_error_) dd = devs + (local variable) int ii;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("device", "id=%d use=%s name=%s", d.(field) _error_ d.deviceiddeviceid, (immutable global) immutable(string[5]) app.useNameuseName[d.use - 1].(field) _error_ useName[d.use - 1].ptrptr, d.name);
if (d.use == (enum value) app.XIMasterPointer = 1XIMasterPointer)
(local variable) int masterPtrmasterPtr = d.(field) _error_ d.deviceiddeviceid;
int (local variable) int foundfound;
foreach ((parameter) kk; 0 .. d.num_classes)
{
const _error_ anyany = d.classes[k];
if (any.type == XIScrollClass)
{
const _error_ scsc = cast(const(XIScrollClassInfo)*) any;
emitf("scroll_class", "device=%d number=%d type=%s increment=%g "
~ "flags=0x%x no_emulation=%d preferred=%d",
d.d.deviceiddeviceid, sc.sc.numbernumber,
sc.scroll_type == XIScrollTypeVertical ? "vertical"."vertical".ptrptr : "horizontal"."horizontal".ptrptr,
sc.sc.incrementincrement, sc.flags,
cast(int)((sc.flags & XIScrollFlagNoEmulation) != 0),
cast(int)((sc.flags & XIScrollFlagPreferred) != 0));
if (nAxes < axes.axes.lengthlength)
axes[nAxes++] = ScrollAxis(d.d.deviceiddeviceid, sc.sc.numbernumber,
sc.scroll_type, sc.sc.incrementincrement, double.nan);
++found;
}
else if (any.type == XIValuatorClass)
{
const _error_ vcvc = cast(const(XIValuatorClassInfo)*) any;
char* _error_ labellabel = vc.vc.labellabel ? XGetAtomName(dpy, vc.vc.labellabel) : null;
emitf("valuator_class", "device=%d number=%d label=%s min=%g max=%g mode=%d",
d.d.deviceiddeviceid, vc.vc.numbernumber, label ? label : "-"."-".ptrptr, vc.min, vc.max, vc.mode);
if (label)
XFree(label);
}
}
if (!(local variable) int foundfound)
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("scroll_class", "device=%d none=1", d.(field) _error_ d.deviceiddeviceid);
}
XIFreeDeviceInfo(devs);
// -- Window + the two-client selection split --------------------------------
const int (local variable) const(int) widthwidth = 480, (local variable) const(int) heightheight = 320;
Window (local variable) _error_ winwin = XCreateSimpleWindow(dpy, root, 20, 20, width, height, 1,
XBlackPixel(dpy, screen), XWhitePixel(dpy, screen));
XStoreName(dpy, win, "Sparkles · X11 F11 scroll");
Atom (local variable) _error_ wmDeletewmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, win, &wmDelete, 1);
const c_long (local variable) const(long) baseMaskbaseMask = (enum value) app.ExposureMask = 32768LExposureMask | (enum value) app.KeyPressMask = 1LKeyPressMask | (enum value) app.StructureNotifyMask = 131072LStructureNotifyMask;
XSelectInput(dpy, win, baseMask); // NO core buttons yet: dpy2 owns those
ubyte[4] (local variable) ubyte[4] bitsbits = 0;
void app.xiSetMask(scope ubyte[] mask, int event) nothrow @nogcXISetMask is a macro; same bit math, D-side.
xiSetMask((local variable) ubyte[4] bitsbits[], (enum value) app.XI_ButtonPress = 4XI_ButtonPress);
void app.xiSetMask(scope ubyte[] mask, int event) nothrow @nogcXISetMask is a macro; same bit math, D-side.
xiSetMask((local variable) ubyte[4] bitsbits[], (enum value) app.XI_ButtonRelease = 5XI_ButtonRelease);
void app.xiSetMask(scope ubyte[] mask, int event) nothrow @nogcXISetMask is a macro; same bit math, D-side.
xiSetMask((local variable) ubyte[4] bitsbits[], (enum value) app.XI_Motion = 6XI_Motion);
XIEventMask (local variable) _error_ mm;
m.m.deviceiddeviceid = XIAllDevices;
m.mask_len = bits.bits.lengthlength;
m.m.maskmask = bits.bits.ptrptr;
XISelectEvents(dpy, win, &m, 1);
XMapWindow(dpy, win);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("window_created", "xid=0x%lx size=%dx%d", win, (local variable) const(int) widthwidth, (local variable) const(int) heightheight);
// Client 2: core-protocol-only listener on the *same* window. Core
// ButtonPressMask is one-client-per-window-exclusive, so a second
// connection is the only way to hold core + XI2 selections concurrently.
Display* (local variable) _error_ dpy2dpy2 = XOpenDisplay(null);
if (dpy2 is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: could not open second display connection\n");
XCloseDisplay(dpy);
return 0;
}
XSelectInput(dpy2, win, ButtonPressMask | ButtonReleaseMask);
XFlush(dpy2);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("step name=XSelectInput conn=core_client mask=ButtonPress|ButtonRelease");
GC (local variable) _error_ gcgc = XDefaultGC(dpy, screen);
// -- Ruler + gesture state ---------------------------------------------------
enum (constant) int app.main.detentPx = 20detentPx = 20; // 1 wheel detent scrolls the ruler by 20 px
int (local variable) int vOffsetvOffset, (local variable) int hOffsethOffset; // ruler offsets in px
long (local variable) long lastScrollUslastScrollUs = -1, (local variable) long gestureStartUsgestureStartUs;
int (local variable) int gEventsgEvents, (local variable) int gCoregCore, (local variable) int gXi2gXi2, (local variable) int gEmulatedgEmulated, (local variable) int gVDetentsgVDetents, (local variable) int gHDetentsgHDetents;
double (local variable) double gSmoothVgSmoothV = 0, (local variable) double gSmoothHgSmoothH = 0;
void void app.main.redraw() nothrow @nogc @systemredraw()
{
XClearWindow(dpy, win);
foreach ((local variable) int yy; 0 .. (local variable) const(int) heightheight / (constant) int app.main.detentPx = 20detentPx + 2)
{
const (local variable) const(int) vv = (local variable) int yy * (constant) int app.main.detentPx = 20detentPx - ((local variable) int vOffsetvOffset % ((constant) int app.main.detentPx = 20detentPx * 5)) - (constant) int app.main.detentPx = 20detentPx;
const (local variable) const(int) lineNolineNo = ((local variable) const(int) vv + (local variable) int vOffsetvOffset) / (constant) int app.main.detentPx = 20detentPx;
const (local variable) const(bool) majormajor = (local variable) const(int) lineNolineNo % 5 == 0;
XDrawLine(dpy, win, gc, 40 + hOffset, v, (major ? 90 : 70) + hOffset, v);
if ((local variable) const(bool) majormajor)
{
char[16] (local variable) char[16] bufbuf;
const (local variable) const(int) nn = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf((local variable) char[16] bufbuf.(constant) char* char[16].ptr = &bufptr, (local variable) char[16] bufbuf.(constant) ulong char[16].length = 16LUlength, "%d", (local variable) const(int) lineNolineNo);
XDrawString(dpy, win, gc, 96 + hOffset, v + 4, buf.buf.ptrptr, n);
}
}
XFlush(dpy);
}
void void app.main.endGesture() nothrow @nogc @systemendGesture()
{
if ((local variable) int gEventsgEvents == 0)
return;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("gesture_summary", "events=%d core=%d xi2=%d emulated=%d "
~ "v_detents=%d h_detents=%d smooth_v=%g smooth_h=%g dur_ms=%lld",
(local variable) int gEventsgEvents, (local variable) int gCoregCore, (local variable) int gXi2gXi2, (local variable) int gEmulatedgEmulated, (local variable) int gVDetentsgVDetents, (local variable) int gHDetentsgHDetents,
(local variable) double gSmoothVgSmoothV, (local variable) double gSmoothHgSmoothH, ((local variable) long lastScrollUslastScrollUs - (local variable) long gestureStartUsgestureStartUs) / 1000);
(local variable) int gEventsgEvents = (local variable) int gCoregCore = (local variable) int gXi2gXi2 = (local variable) int gEmulatedgEmulated = (local variable) int gVDetentsgVDetents = (local variable) int gHDetentsgHDetents = 0;
(local variable) double gSmoothVgSmoothV = (local variable) double gSmoothHgSmoothH = 0;
}
/// Fold one scroll event into the running gesture (and the ruler, when it
/// is the stream that owns the ruler in the current phase).
void void app.main.countScroll(uint button, bool isXi2, bool emulated, bool moveRuler) nothrow @nogc @systemFold one scroll event into the running gesture (and the ruler, when it
is the stream that owns the ruler in the current phase).
countScroll(uint (parameter) uint buttonbutton, bool (parameter) bool isXi2isXi2, bool (parameter) bool emulatedemulated, bool (parameter) bool moveRulermoveRuler)
{
const (local variable) const(long) nownow = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
if ((local variable) long lastScrollUslastScrollUs >= 0 && (local variable) const(long) nownow - (local variable) long lastScrollUslastScrollUs > 400_000)
void app.main.endGesture() nothrow @nogc @systemendGesture();
if ((local variable) int gEventsgEvents == 0)
(local variable) long gestureStartUsgestureStartUs = (local variable) const(long) nownow;
(local variable) long lastScrollUslastScrollUs = (local variable) const(long) nownow;
++(local variable) int gEventsgEvents;
(parameter) bool isXi2isXi2 ? ++(local variable) int gXi2gXi2 : ++(local variable) int gCoregCore;
if ((parameter) bool emulatedemulated)
++(local variable) int gEmulatedgEmulated;
const (local variable) const(int) stepstep = ((parameter) uint buttonbutton == 4 || (parameter) uint buttonbutton == 6) ? -1 : 1;
if ((parameter) uint buttonbutton == 4 || (parameter) uint buttonbutton == 5)
(local variable) int gVDetentsgVDetents += (local variable) const(int) stepstep;
else
(local variable) int gHDetentsgHDetents += (local variable) const(int) stepstep;
if ((parameter) bool moveRulermoveRuler)
{
if ((parameter) uint buttonbutton == 4 || (parameter) uint buttonbutton == 5)
(local variable) int vOffsetvOffset += (local variable) const(int) stepstep * (constant) int app.main.detentPx = 20detentPx;
else
(local variable) int hOffsethOffset += (local variable) const(int) stepstep * (constant) int app.main.detentPx = 20detentPx;
void app.main.redraw() nothrow @nogc @systemredraw();
}
}
immutable(char)* immutable(char)* app.main.axisOf(uint b) pure nothrow @nogc @systemaxisOf(uint (parameter) uint bb) => ((parameter) uint bb == 4 || (parameter) uint bb == 5) ? "v".(constant) immutable(char)* "v".ptr = "v"ptr : "h".(constant) immutable(char)* "h".ptr = "h"ptr;
// -- Phases --------------------------------------------------------------
// A dual_client: core -> dpy2, XI2 -> dpy (both streams, one event?)
// B dual_selection: dpy2 deselects; dpy holds core AND XI2 selections
// C core_only: dpy clears the XI2 selection, keeps the core one
int (local variable) int phasephase = -1;
long (local variable) long phaseStartUsphaseStartUs;
bool (local variable) bool sawExposesawExpose, (local variable) bool runningrunning = true;
int (local variable) int sendProbessendProbes;
void void app.main.enterPhase(int p) nothrow @nogc @systementerPhase(int (parameter) int pp)
{
(local variable) int phasephase = (parameter) int pp;
(local variable) long phaseStartUsphaseStartUs = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
if ((parameter) int pp == 0)
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("phase name=dual_client");
else if ((parameter) int pp == 1)
{
XSelectInput(dpy2, win, 0);
// XSync, not XFlush: the server must have *processed* dpy2's
// deselect before dpy selects ButtonPress — only one client may
// hold a window's ButtonPress selection, so a still-queued
// deselect makes the next line's ChangeWindowAttributes die with
// BadAccess (seen on CI's Xvfb).
XSync(dpy2, False);
XSelectInput(dpy, win, baseMask | ButtonPressMask | ButtonReleaseMask);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("phase name=dual_selection core_client=deselected "
~ "xi2_client=core+xi2");
}
else if ((parameter) int pp == 2)
{
ubyte[4] (local variable) ubyte[4] nonenone = 0;
XIEventMask (local variable) _error_ m0m0;
m0.m0.deviceiddeviceid = XIAllDevices;
m0.mask_len = none.none.lengthlength;
m0.m0.maskmask = none.none.ptrptr;
XISelectEvents(dpy, win, &m0, 1);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("phase name=core_only xi2_selection=cleared");
}
else
{
void app.main.endGesture() nothrow @nogc @systemendGesture();
(local variable) bool runningrunning = false;
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("auto_exit");
}
}
/// Built-in probe (no xdotool): fabricate a core wheel press/release pair
/// with XSendEvent. It reaches core selectors only — fabricated events
/// never enter the input pipeline, so no XI2 twin and no ruler motion
/// from the server's point of view (this is why drivers use XTest).
void void app.main.sendEventProbe(uint button) nothrow @nogc @systemBuilt-in probe (no xdotool): fabricate a core wheel press/release pair
with XSendEvent. It reaches core selectors only — fabricated events
never enter the input pipeline, so no XI2 twin and no ruler motion
from the server's point of view (this is why drivers use XTest).
sendEventProbe(uint (parameter) uint buttonbutton)
{
XEvent (local variable) _error_ sese;
se.xbutton.type = ButtonPress;
se.xbutton.display = dpy;
se.xbutton.window = win;
se.xbutton.se.xbutton.buttonbutton = button;
se.xbutton.same_screen = True;
XSendEvent(dpy, win, True, ButtonPressMask, &se);
se.xbutton.type = ButtonRelease;
XSendEvent(dpy, win, True, ButtonReleaseMask, &se);
XFlush(dpy);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XSendEvent button=%u", (parameter) uint buttonbutton);
}
void app.main.handleCoreButtonhandleCoreButton(const(XButtonEvent)* be, const(char)* conn, bool (parameter) bool moveRulermoveRuler)
{
if (be.type == ButtonRelease)
return; // wheel scrolls are press+release pairs; count presses
if (be.be.buttonbutton >= 4 && be.be.buttonbutton <= 7)
{
emitf("scroll", "conn=%s kind=core axis=%s button=%u value=%+d send_event=%d",
conn, axisOf(be.be.buttonbutton), be.be.buttonbutton,
(be.be.buttonbutton == 4 || be.be.buttonbutton == 6) ? -1 : 1, cast(int) be.send_event);
countScroll(be.be.buttonbutton, false, false, moveRuler);
}
else
emitf("button", "conn=%s kind=core button=%u send_event=%d",
conn, be.be.buttonbutton, cast(int) be.send_event);
}
// -- Event loop: poll both connections ----------------------------------
const (local variable) const(_error_) fd1fd1 = XConnectionNumber(dpy), (local variable) const(_error_) fd2fd2 = XConnectionNumber(dpy2);
while ((local variable) bool runningrunning)
{
while (XPending(dpy) > 0)
{
XEvent (local variable) _error_ evev;
XNextEvent(dpy, &ev);
switch (ev.type)
{
case Expose:
if (ev.xexpose.count == 0)
{
redraw();
if (!sawExpose)
{
sawExpose = true;
if (autoExit)
enterPhase(0);
}
}
break;
case ButtonPress:
case ButtonRelease: // only selected in phases B and C
handleCoreButton(&ev.xbutton, "xi2_client", phase >= 1);
break;
case KeyPress:
const _error_ ksks = XLookupKeysym(&ev.xkey, 0);
if (ks == 'q' || ks == 0xff1b /* XK_Escape */ )
running = false;
break;
case ClientMessage:
if (cast(Atom) ev.xclient.data.l[0] == wmDelete)
running = false;
break;
case GenericEvent:
auto _error_ cookiecookie = &ev.xcookie;
if (XGetEventData(dpy, cookie) && cookie.extension == xiOpcode)
{
const _error_ dede = cast(const(XIDeviceEvent)*) cookie.data;
if (cookie.evtype == XI_ButtonPress)
{
const _error_ emulatedemulated = (de.flags & XIPointerEmulated) != 0;
// XIAllDevices delivers every event twice: once from
// the slave, once as the master copy. Dedupe by
// counting only the master copy (the measured trap).
const _error_ masterCopymasterCopy = de.de.deviceiddeviceid == masterPtr;
if (de.detail >= 4 && de.detail <= 7)
{
emitf("scroll", "conn=xi2_client kind=xi2 axis=%s button=%d "
~ "value=%+d emulated=%d flags=0x%x device=%d source=%d",
axisOf(de.detail), de.detail,
(de.detail == 4 || de.detail == 6) ? -1 : 1,
cast(int) emulated, de.flags, de.de.deviceiddeviceid, de.sourceid);
if (masterCopy)
countScroll(de.detail, true, emulated, true);
}
else
emitf("button", "conn=xi2_client kind=xi2 button=%d emulated=%d",
de.detail, cast(int) emulated);
}
else if (cookie.evtype == XI_Motion)
{
// Smooth scrolling: scroll axes ride XI_Motion as
// absolute accumulating valuators; delta/increment
// = detents. (No XIScrollClass on Xvfb -> never hit.)
int _error_ idxidx;
foreach ((parameter) ii; 0 .. de.valuators.mask_len * 8)
{
if (!(de.valuators.de.valuators.maskmask[i >> 3] & (1 << (i & 7))))
continue;
const _error_ vv = de.valuators.values[idx++];
foreach (ref (parameter) axax; axes[0 .. nAxes])
{
if (ax.ax.numbernumber != i
|| (ax.ax.deviceiddeviceid != de.de.deviceiddeviceid && ax.ax.deviceiddeviceid != de.sourceid))
continue;
if (ax.ax.lastlast == ax.ax.lastlast) // !NaN: delta is meaningful
emitf("scroll", "conn=xi2_client kind=smooth axis=%s "
~ "value=%g delta=%g detents=%g emulated=%d",
ax.ax.scrollTypescrollType == XIScrollTypeVertical ? "v"."v".ptrptr : "h"."h".ptrptr,
v, v - ax.ax.lastlast, (v - ax.ax.lastlast) / ax.ax.incrementincrement,
cast(int)((de.flags & XIPointerEmulated) != 0));
ax.ax.lastlast = v;
}
}
}
}
XFreeEventData(dpy, cookie);
break;
default:
break;
}
}
while (XPending(dpy2) > 0) // the core-only second client
{
XEvent (local variable) _error_ evev;
XNextEvent(dpy2, &ev);
if (ev.type == (enum value) app.ButtonPress = 4ButtonPress || ev.type == (enum value) app.ButtonRelease = 5ButtonRelease)
handleCoreButton(&ev.xbutton, "core_client", phase == 0);
}
if ((local variable) const(bool) autoExitautoExit && (local variable) bool sawExposesawExpose && (local variable) bool runningrunning)
{
const (local variable) const(long) nownow = long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs();
if ((local variable) const(long) nownow - (local variable) long phaseStartUsphaseStartUs >= (local variable) const(int) phaseUsphaseUs)
void app.main.enterPhase(int p) nothrow @nogc @systementerPhase((local variable) int phasephase + 1);
else if (!(local variable) const(bool) drivendriven && (local variable) int sendProbessendProbes < 3 * ((local variable) int phasephase + 1)
&& (local variable) const(long) nownow - (local variable) long phaseStartUsphaseStartUs >= 100_000 * ((local variable) int sendProbessendProbes % 3 + 1))
{
void app.main.sendEventProbe(uint button) nothrow @nogc @systemBuilt-in probe (no xdotool): fabricate a core wheel press/release pair
with XSendEvent. It reaches core selectors only — fabricated events
never enter the input pipeline, so no XI2 twin and no ruler motion
from the server's point of view (this is why drivers use XTest).
sendEventProbe((local variable) int sendProbessendProbes % 3 == 2 ? 6 : ((local variable) int sendProbessendProbes % 2 == 0 ? 4 : 5));
++(local variable) int sendProbessendProbes;
}
if ((local variable) long lastScrollUslastScrollUs >= 0 && (local variable) const(long) nownow - (local variable) long lastScrollUslastScrollUs > 400_000)
void app.main.endGesture() nothrow @nogc @systemendGesture();
}
XFlush(dpy);
pollfd[2] (local variable) _error_ pfdspfds;
pfds[0].fd = fd1;
pfds[1].fd = fd2;
pfds[0].events = pfds[1].events = POLLIN;
pfds[0].revents = pfds[1].revents = 0;
poll(pfds.pfds.ptrptr, 2, autoExit ? 5 : -1);
}
void app.main.endGesture() nothrow @nogc @systemendGesture();
XCloseDisplay(dpy2);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("teardown");
return 0;
}