// X11 F10 demo — pointer: relative/raw motion, lock & confine (the feature
// contract: ../../../features/f10-pointer-capture.md; findings:
// ../../f10-pointer-capture.md). Built on the scaffold (../scaffold/app.d),
// drawing reduced to server-side lines so the pointer machinery is the subject.
//
// Scripted phases (WSI_AUTO_EXIT=1):
// 1 unlocked XI_Motion abs + XI_RawMotion raw-vs-accelerated pairs
// 2 locked mouselook: XIGrabDevice + XFixesHideCursor + warp-to-
// center; deltas from raw motion; unlock warps back to
// the saved position (X11 *can* warp — Wayland can only
// hint via set_cursor_position_hint)
// 3 confine_grab modal: XGrabPointer confine_to = InputOnly child over
// the center half (+ second-client AlreadyGrabbed probe)
// 4 confine_barrier ambient: XFixesCreatePointerBarrier on the rect edges
// 5 barrier_plus_grab grab without confine_to on top of live barriers
// Each confine phase warps to the window corners and reports XQueryPointer
// (`confine_probe target=… actual=… inside_rect=…`). Interactive keys:
// l=lock c=confine-grab b=barriers q=quit.
//
// WSI_DRIVEN=1 stretches each phase to 2 s and suppresses the self-warp
// probes so an external driver (xdotool, see ./run.sh) owns the pointer.
// Headless-safe: no display, or no XInputExtension -> prints `SKIP:`, exit 0.
module (module) appapp;
import (module) cc; // ImportC: Xlib + Xutil + XInput2 + Xfixes + 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;
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;
// ---------------------------------------------------------------------------
// Constants ImportC cannot export (all #defines): core masks/types, the
// whole of <X11/extensions/XI2.h>, and the XFixes barrier directions from
// <X11/extensions/xfixeswire.h>. Re-declared per the scaffold gotcha.
enum : c_long
{
(enum value) app.KeyPressMask = 1LKeyPressMask = 1L << 0, (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.Expose = 12Expose = 12, (enum value) app.MapNotify = 19MapNotify = 19, (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.None = 0None = 0;
enum (constant) int app.CurrentTime = 0CurrentTime = 0;
enum (constant) int app.GrabModeAsync = 1GrabModeAsync = 1;
enum (constant) int app.InputOnly = 2InputOnly = 2;
enum (constant) int app.POLLIN = 1POLLIN = 0x001;
immutable (alias) object.string = stringstring[5] (immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName =
["GrabSuccess", "AlreadyGrabbed", "GrabInvalidTime", "GrabNotViewable", "GrabFrozen"];
enum // XI2.h; XI_BarrierHit/Leave require announcing XI >= 2.3
{
(enum value) app.XIAllDevices = 0XIAllDevices = 0, (enum value) app.XIAllMasterDevices = 1XIAllMasterDevices = 1,
(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_Motion = 6XI_Motion = 6, (enum value) app.XI_RawMotion = 17XI_RawMotion = 17,
(enum value) app.XI_BarrierHit = 25XI_BarrierHit = 25, (enum value) app.XI_BarrierLeave = 26XI_BarrierLeave = 26,
}
enum // xfixeswire.h: directions a barrier is *transparent* in
{
(enum value) app.BarrierPositiveX = 1BarrierPositiveX = 1, (enum value) app.BarrierPositiveY = 2BarrierPositiveY = 2, (enum value) app.BarrierNegativeX = 4BarrierNegativeX = 4, (enum value) app.BarrierNegativeY = 8BarrierNegativeY = 8,
}
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));
}
/// Select XI2 `events` for `deviceid` on `w` (one XIEventMask, one request).
void app.xiSelectSelect XI2 events for deviceid on w (one XIEventMask, one request).
xiSelect(Display* (parameter) Display* dpydpy, Window w, int deviceid, scope const int[] events) @nogc nothrow
{
ubyte[4] _error_ bitsbits = 0;
foreach ((parameter) ee; events)
xiSetMask(bits[], e);
XIEventMask _error_ mm;
m.deviceid = deviceid;
m.mask_len = bits.length;
m.m.maskmask = bits.bits.ptrptr;
XISelectEvents(dpy, w, &m, 1);
}
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_f10");
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 pointer
const (local variable) const(int) unitUsunitUs = (local variable) const(bool) drivendriven ? 2_000_000 : 420_000; // per-phase duration
Display* (local variable) _error_ dpydpy = XOpenDisplay(null);
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;
}
const (local variable) const(_error_) screenscreen = XDefaultScreen(dpy);
const (local variable) const(_error_) rootroot = XRootWindow(dpy, screen);
// -- Extension handshakes -------------------------------------------------
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; // 2.3: raw events during grabs + barrier events
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 opcode=%d",
xiStatus, (local variable) int xiMajorxiMajor, (local variable) int xiMinorxiMinor, (local variable) int xiOpcodexiOpcode);
if (xiStatus != 0 /* Success */ || (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;
}
int (local variable) int fixMajorfixMajor, (local variable) int fixMinorfixMinor, (local variable) int fixEventBasefixEventBase, (local variable) int fixErrorBasefixErrorBase;
const (local variable) const(_error_) haveFixeshaveFixes = XFixesQueryExtension(dpy, &fixEventBase, &fixErrorBase) != 0
&& XFixesQueryVersion(dpy, &fixMajor, &fixMinor) != 0;
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=XFixesQueryVersion server=%d.%d barriers=%d",
(local variable) int fixMajorfixMajor, (local variable) int fixMinorfixMinor, cast(int)(haveFixes && (local variable) int fixMajorfixMajor >= 5));
// -- Device dump + master pointer id --------------------------------------
int (local variable) int masterPtrmasterPtr = 2; // conventional VCP id; replaced by the query below
int (local variable) int nDevsnDevs;
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 attachment=%d name=%s", d.deviceid,
(immutable global) immutable(string[5]) app.useNameuseName[d.use - 1].(field) _error_ useName[d.use - 1].ptrptr, d.attachment, d.name);
if (d.use == (enum value) app.XIMasterPointer = 1XIMasterPointer)
(local variable) int masterPtrmasterPtr = d.deviceid;
}
XIFreeDeviceInfo(devs);
// -- Window ----------------------------------------------------------------
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 F10 pointer capture");
Atom (local variable) _error_ wmDeletewmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, win, &wmDelete, 1);
XSelectInput(dpy, win, ExposureMask | KeyPressMask | StructureNotifyMask);
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);
// XI2 selections: raw motion is root-window-only by spec; abs motion +
// barrier events ride the same root/window split.
static immutable int[] (immutable global) immutable(int[]) app.main.rootEventsrootEvents = [(enum value) app.XI_RawMotion = 17XI_RawMotion, (enum value) app.XI_BarrierHit = 25XI_BarrierHit, (enum value) app.XI_BarrierLeave = 26XI_BarrierLeave];
static immutable int[] (immutable global) immutable(int[]) app.main.winEventswinEvents = [(enum value) app.XI_Motion = 6XI_Motion, (enum value) app.XI_ButtonPress = 4XI_ButtonPress];
xiSelect(dpy, root, XIAllMasterDevices, rootEvents);
xiSelect(dpy, win, XIAllMasterDevices, winEvents);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("step name=XISelectEvents root=RawMotion,BarrierHit/Leave win=Motion,ButtonPress");
GC (local variable) _error_ gcgc = XDefaultGC(dpy, screen);
// Window origin in root coordinates (no WM under Xvfb, but don't assume).
int (local variable) int orgXorgX, (local variable) int orgYorgY;
{
Window (local variable) _error_ dummydummy;
XTranslateCoordinates(dpy, win, root, 0, 0, &orgX, &orgY, &dummy);
}
// The confine region: center half of the window (window + root coords).
const (local variable) const(int) rxrx = (local variable) const(int) widthwidth / 4, (local variable) const(int) ryry = (local variable) const(int) heightheight / 4, (local variable) const(int) rwrw = (local variable) const(int) widthwidth / 2, (local variable) const(int) rhrh = (local variable) const(int) heightheight / 2;
const (local variable) const(int) cxcx = (local variable) const(int) widthwidth / 2, (local variable) const(int) cycy = (local variable) const(int) heightheight / 2; // lock center (window coords)
// -- Demo state ------------------------------------------------------------
Display* (local variable) _error_ dpy2dpy2 = null; // second client, for the AlreadyGrabbed probe
Window (local variable) _error_ confineChildconfineChild = None;
PointerBarrier[4] (local variable) _error_ barriersbarriers;
bool (local variable) bool lockedlocked, (local variable) bool confineGrabbedconfineGrabbed, (local variable) bool barriersUpbarriersUp, (local variable) bool plainGrabbedplainGrabbed;
int (local variable) int savedXsavedX = -1, (local variable) int savedYsavedY = -1; // root coords to restore on unlock
double (local variable) double yawyaw = 0, (local variable) double pitchpitch = 0; // accumulated raw deltas while locked
int (local variable) int rawWhileLockedrawWhileLocked, (local variable) int jitterWarpsjitterWarps, (local variable) int recenterWarpsrecenterWarps;
void void app.main.queryPtr(out int rootX, out int rootY, out int winX, out int winY) pure nothrow @nogc @safequeryPtr(out int (parameter) int rootXrootX, out int (parameter) int rootYrootY, out int (parameter) int winXwinX, out int (parameter) int winYwinY)
{
Window (local variable) _error_ rr, (local variable) _error_ chch;
uint (local variable) uint modsmods;
XQueryPointer(dpy, win, &r, &ch, &rootX, &rootY, &winX, &winY, &mods);
}
void void app.main.redraw() pure nothrow @nogc @saferedraw()
{
XClearWindow(dpy, win);
if ((local variable) bool confineGrabbedconfineGrabbed || (local variable) bool barriersUpbarriersUp)
XDrawRectangle(dpy, win, gc, rx, ry, rw, rh);
if ((local variable) bool lockedlocked)
{
const (local variable) const(int) pxpx = (local variable) const(int) cxcx + cast(int) (local variable) double yawyaw / 4, (local variable) const(int) pypy = (local variable) const(int) cycy + cast(int) (local variable) double pitchpitch / 4;
XDrawLine(dpy, win, gc, px - 8, py, px + 8, py);
XDrawLine(dpy, win, gc, px, py - 8, px, py + 8);
}
XFlush(dpy);
}
void void app.main.setLock(bool on) nothrow @nogc @systemsetLock(bool (parameter) bool onon)
{
if ((parameter) bool onon == (local variable) bool lockedlocked)
return;
if ((parameter) bool onon)
{
int (local variable) int wxwx, (local variable) int wywy;
void app.main.queryPtr(out int rootX, out int rootY, out int winX, out int winY) pure nothrow @nogc @safequeryPtr((local variable) int savedXsavedX, (local variable) int savedYsavedY, (local variable) int wxwx, (local variable) int wywy);
XFixesHideCursor(dpy, win);
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_Motion = 6XI_Motion);
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);
XIEventMask (local variable) _error_ mm;
m.deviceid = masterPtr;
m.mask_len = bits.length;
m.m.maskmask = bits.bits.ptrptr;
const (local variable) const(_error_) stst = XIGrabDevice(dpy, masterPtr, win, CurrentTime, None,
GrabModeAsync, GrabModeAsync, True, &m);
XWarpPointer(dpy, None, win, 0, 0, 0, 0, cx, cy);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("lock", "state=on grab=XIGrabDevice status=%s saved=%d,%d",
(immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName[st].(field) _error_ grabStatusName[st].ptrptr, (local variable) int savedXsavedX, (local variable) int savedYsavedY);
(local variable) double yawyaw = (local variable) double pitchpitch = 0;
(local variable) int rawWhileLockedrawWhileLocked = (local variable) int jitterWarpsjitterWarps = (local variable) int recenterWarpsrecenterWarps = 0;
}
else
{
XIUngrabDevice(dpy, masterPtr, CurrentTime);
XFixesShowCursor(dpy, win);
XWarpPointer(dpy, None, root, 0, 0, 0, 0, savedX, savedY); // X11 can warp
XSync(dpy, False);
int (local variable) int rxprxp, (local variable) int rypryp, (local variable) int wxwx, (local variable) int wywy;
void app.main.queryPtr(out int rootX, out int rootY, out int winX, out int winY) pure nothrow @nogc @safequeryPtr((local variable) int rxprxp, (local variable) int rypryp, (local variable) int wxwx, (local variable) int wywy);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("lock", "state=off restored=%d,%d wanted=%d,%d match=%d",
(local variable) int rxprxp, (local variable) int rypryp, (local variable) int savedXsavedX, (local variable) int savedYsavedY, cast(int)((local variable) int rxprxp == (local variable) int savedXsavedX && (local variable) int rypryp == (local variable) int savedYsavedY));
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("lock_stats", "raw_events=%d jitter_warps=%d recenter_warps=%d "
~ "yaw=%.1f pitch=%.1f", (local variable) int rawWhileLockedrawWhileLocked, (local variable) int jitterWarpsjitterWarps,
(local variable) int recenterWarpsrecenterWarps, (local variable) double yawyaw, (local variable) double pitchpitch);
}
(local variable) bool lockedlocked = (parameter) bool onon;
void app.main.redraw() pure nothrow @nogc @saferedraw();
}
void void app.main.setConfineGrab(bool on) nothrow @nogc @systemsetConfineGrab(bool (parameter) bool onon)
{
if ((parameter) bool onon == (local variable) bool confineGrabbedconfineGrabbed)
return;
if ((parameter) bool onon)
{
if (confineChild == None)
{
// InputOnly child over the center half: invisible, events-only —
// it exists purely to be a confine_to target. WSI_CONFINE_IO=1
// swaps in an InputOutput child to probe whether the window
// class changes the confinement behavior.
const (local variable) const(bool) inputOutputinputOutput = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_CONFINE_IO") !is null;
XSetWindowAttributes (local variable) _error_ attrsattrs;
confineChild = XCreateWindow(dpy, win, rx, ry, rw, rh, 0, 0,
inputOutput ? 1 : InputOnly, null, 0, &attrs);
XMapWindow(dpy, confineChild);
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=XCreateWindow class=%s xid=0x%lx",
(local variable) const(bool) inputOutputinputOutput ? "InputOutput".(constant) immutable(char)* "InputOutput".ptr = "InputOutput"ptr : "InputOnly".(constant) immutable(char)* "InputOnly".ptr = "InputOnly"ptr, confineChild);
}
const (local variable) const(_error_) stst = XGrabPointer(dpy, win, True, 0, GrabModeAsync,
GrabModeAsync, confineChild, None, CurrentTime);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("confine", "mode=grab rect=%dx%d+%d+%d confine_to=0x%lx status=%s",
(local variable) const(int) rwrw, (local variable) const(int) rhrh, (local variable) const(int) rxrx, (local variable) const(int) ryry, confineChild, (immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName[st].(field) _error_ grabStatusName[st].ptrptr);
// Failure probe: a *second client* grabbing while we hold the grab.
if (dpy2 is null)
dpy2 = XOpenDisplay(null);
if (dpy2 !is null)
{
const (local variable) const(_error_) st2st2 = XGrabPointer(dpy2, win, True, 0, GrabModeAsync,
GrabModeAsync, None, None, CurrentTime);
if (st2 == 0)
XUngrabPointer(dpy2, CurrentTime);
ubyte[4] (local variable) ubyte[4] bitsbits = 0;
XIEventMask (local variable) _error_ mm;
m.deviceid = masterPtr;
m.mask_len = bits.length;
m.m.maskmask = bits.bits.ptrptr;
const (local variable) const(_error_) st3st3 = XIGrabDevice(dpy2, masterPtr, win, CurrentTime, None,
GrabModeAsync, GrabModeAsync, True, &m);
if (st3 == 0)
XIUngrabDevice(dpy2, masterPtr, CurrentTime);
XFlush(dpy2);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("grab_probe", "client=second XGrabPointer=%s XIGrabDevice=%s",
(immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName[st2].(field) _error_ grabStatusName[st2].ptrptr, (immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName[st3].(field) _error_ grabStatusName[st3].ptrptr);
}
}
else
{
XUngrabPointer(dpy, CurrentTime);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("confine mode=grab state=off");
}
(local variable) bool confineGrabbedconfineGrabbed = (parameter) bool onon;
void app.main.redraw() pure nothrow @nogc @saferedraw();
}
void void app.main.setBarriers(bool on) nothrow @nogc @systemsetBarriers(bool (parameter) bool onon)
{
if ((parameter) bool onon == (local variable) bool barriersUpbarriersUp || !haveFixes || (local variable) int fixMajorfixMajor < 5)
return;
if ((parameter) bool onon)
{
// Four barriers boxing the region (root coords). Each is transparent
// *inward* only, so the pointer can enter but relative motion cannot
// leave. Non-modal: no grab, event delivery is untouched.
const (local variable) const(int) x1x1 = (local variable) int orgXorgX + (local variable) const(int) rxrx, (local variable) const(int) y1y1 = (local variable) int orgYorgY + (local variable) const(int) ryry, (local variable) const(int) x2x2 = (local variable) const(int) x1x1 + (local variable) const(int) rwrw, (local variable) const(int) y2y2 = (local variable) const(int) y1y1 + (local variable) const(int) rhrh;
barriers[0] = XFixesCreatePointerBarrier(dpy, root, x1, y1, x1, y2,
BarrierPositiveX, 0, null); // left edge: only +x passes
barriers[1] = XFixesCreatePointerBarrier(dpy, root, x2, y1, x2, y2,
BarrierNegativeX, 0, null); // right edge: only -x passes
barriers[2] = XFixesCreatePointerBarrier(dpy, root, x1, y1, x2, y1,
BarrierPositiveY, 0, null); // top edge: only +y passes
barriers[3] = XFixesCreatePointerBarrier(dpy, root, x1, y2, x2, y2,
BarrierNegativeY, 0, null); // bottom edge: only -y passes
XWarpPointer(dpy, None, win, 0, 0, 0, 0, cx, cy); // start inside
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("confine", "mode=barrier rect=%dx%d+%d+%d root_rect=%d,%d..%d,%d "
~ "ids=%lu,%lu,%lu,%lu", (local variable) const(int) rwrw, (local variable) const(int) rhrh, (local variable) const(int) rxrx, (local variable) const(int) ryry, (local variable) const(int) x1x1, (local variable) const(int) y1y1, (local variable) const(int) x2x2, (local variable) const(int) y2y2,
barriers[0], barriers[1], barriers[2], barriers[3]);
}
else
{
foreach ((parameter) bb; barriers)
XFixesDestroyPointerBarrier(dpy, b);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("confine mode=barrier state=off");
}
(local variable) bool barriersUpbarriersUp = (parameter) bool onon;
void app.main.redraw() pure nothrow @nogc @saferedraw();
}
void void app.main.setPlainGrab(bool on) pure nothrow @nogc @safesetPlainGrab(bool (parameter) bool onon) // grab-on-top-of-barriers interaction probe
{
if ((parameter) bool onon == (local variable) bool plainGrabbedplainGrabbed)
return;
if ((parameter) bool onon)
{
const (local variable) const(_error_) stst = XGrabPointer(dpy, win, True, 0, GrabModeAsync,
GrabModeAsync, None, None, CurrentTime);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("grab_probe", "client=first XGrabPointer=%s confine_to=None "
~ "barriers_active=%d", (immutable global) immutable(string[5]) app.grabStatusNamegrabStatusName[st].(field) _error_ grabStatusName[st].ptrptr, cast(int) (local variable) bool barriersUpbarriersUp);
}
else
XUngrabPointer(dpy, CurrentTime);
(local variable) bool plainGrabbedplainGrabbed = (parameter) bool onon;
}
/// Warp to a window-local target, round-trip, report where the pointer
/// actually ended up relative to the confine region.
void void app.main.confineProbe(const(char)* mode, int tx, int ty) nothrow @nogc @systemWarp to a window-local target, round-trip, report where the pointer
actually ended up relative to the confine region.
confineProbe(const(char)* (parameter) const(char)* modemode, int (parameter) int txtx, int (parameter) int tyty)
{
XWarpPointer(dpy, None, win, 0, 0, 0, 0, tx, ty);
XSync(dpy, False);
int (local variable) int rxprxp, (local variable) int rypryp, (local variable) int wxwx, (local variable) int wywy;
void app.main.queryPtr(out int rootX, out int rootY, out int winX, out int winY) pure nothrow @nogc @safequeryPtr((local variable) int rxprxp, (local variable) int rypryp, (local variable) int wxwx, (local variable) int wywy);
const (local variable) const(bool) insideinside = (local variable) int wxwx >= (local variable) const(int) rxrx && (local variable) int wxwx < (local variable) const(int) rxrx + (local variable) const(int) rwrw && (local variable) int wywy >= (local variable) const(int) ryry && (local variable) int wywy < (local variable) const(int) ryry + (local variable) const(int) rhrh;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("confine_probe", "mode=%s target=%d,%d actual=%d,%d inside_rect=%d",
(parameter) const(char)* modemode, (parameter) int txtx, (parameter) int tyty, (local variable) int wxwx, (local variable) int wywy, cast(int) (local variable) const(bool) insideinside);
}
// -- Scripted phases (auto mode) -------------------------------------------
static immutable (alias) object.string = stringstring[6] (immutable global) immutable(string[6]) app.main.phaseNamesphaseNames = [
"unlocked", "locked", "confine_grab", "confine_barrier",
"barrier_plus_grab", "done"
];
int (local variable) int phasephase = -1;
long (local variable) long phaseStartUsphaseStartUs;
long (local variable) long nextProbeUsnextProbeUs;
int (local variable) int probeIdxprobeIdx;
bool (local variable) bool sawExposesawExpose, (local variable) bool runningrunning = true;
static immutable int[2][4] (immutable global) immutable(int[2][4]) app.main.cornerscorners = [[8, 8], [471, 8], [8, 311], [471, 311]];
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();
(local variable) long nextProbeUsnextProbeUs = (local variable) long phaseStartUsphaseStartUs + 60_000;
(local variable) int probeIdxprobeIdx = 0;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("phase", "name=%s", (immutable global) immutable(string[6]) app.main.phaseNamesphaseNames[(parameter) int pp].(field) immutable(char)* immutable(string).ptrptr);
final switch ((parameter) int pp)
{
case 0:
break;
case 1:
void app.main.setLock(bool on) nothrow @nogc @systemsetLock(true);
break;
case 2:
void app.main.setLock(bool on) nothrow @nogc @systemsetLock(false);
void app.main.setConfineGrab(bool on) nothrow @nogc @systemsetConfineGrab(true);
break;
case 3:
void app.main.setConfineGrab(bool on) nothrow @nogc @systemsetConfineGrab(false);
void app.main.setBarriers(bool on) nothrow @nogc @systemsetBarriers(true);
break;
case 4:
void app.main.setPlainGrab(bool on) pure nothrow @nogc @safesetPlainGrab(true);
break;
case 5:
void app.main.setPlainGrab(bool on) pure nothrow @nogc @safesetPlainGrab(false);
void app.main.setBarriers(bool on) nothrow @nogc @systemsetBarriers(false);
(local variable) bool runningrunning = false;
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("auto_exit");
break;
}
}
void void app.main.runProbe() nothrow @nogc @systemrunProbe() // self-warp choreography; suppressed when xdotool drives
{
static immutable (alias) object.string = stringstring[3] (immutable global) immutable(string[3]) app.main.runProbe.probeModeprobeMode = ["grab", "barrier", "barrier_grab"];
if ((local variable) int phasephase == 0) // diagonal sweep: feeds abs motion + raw-vs-accel pairs
XWarpPointer(dpy, None, win, 0, 0, 0, 0,
40 + probeIdx * 80, 40 + probeIdx * 48);
else if ((local variable) int phasephase == 1) // mouselook jitter: warp off-center, handler re-centers
{
XWarpPointer(dpy, None, win, 0, 0, 0, 0,
cx + (probeIdx % 2 == 0 ? 24 : -16), cy + 10 - probeIdx * 4);
++(local variable) int jitterWarpsjitterWarps;
}
else if ((local variable) int phasephase >= 2 && (local variable) int phasephase <= 4)
void app.main.confineProbe(const(char)* mode, int tx, int ty) nothrow @nogc @systemWarp to a window-local target, round-trip, report where the pointer
actually ended up relative to the confine region.
confineProbe((immutable global) immutable(string[3]) app.main.runProbe.probeModeprobeMode[(local variable) int phasephase - 2].(field) immutable(char)* immutable(string).ptrptr,
(immutable global) immutable(int[2][4]) app.main.cornerscorners[(local variable) int probeIdxprobeIdx & 3][0], (immutable global) immutable(int[2][4]) app.main.cornerscorners[(local variable) int probeIdxprobeIdx & 3][1]);
++(local variable) int probeIdxprobeIdx;
}
// -- Event loop --------------------------------------------------------------
const (local variable) const(_error_) fdfd = XConnectionNumber(dpy);
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 KeyPress: // interactive toggles
const _error_ ksks = XLookupKeysym(&ev.xkey, 0);
if (ks == 'l')
setLock(!locked);
else if (ks == 'c')
setConfineGrab(!confineGrabbed);
else if (ks == 'b')
setBarriers(!barriersUp);
else 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)
{
switch (cookie.evtype)
{
case XI_RawMotion:
// One event, both flavors: values = post-acceleration/
// transform, raw_values = device deltas untouched.
const _error_ rere = cast(const(XIRawEvent)*) cookie.data;
double _error_ dxdx = 0, _error_ dydy = 0, _error_ rdxrdx = 0, _error_ rdyrdy = 0;
int _error_ idxidx;
foreach ((parameter) ii; 0 .. re.valuators.mask_len * 8)
{
if (!(re.valuators.re.valuators.maskmask[i >> 3] & (1 << (i & 7))))
continue;
if (i == 0)
{
dx = re.valuators.values[idx];
rdx = re.raw_values[idx];
}
else if (i == 1)
{
dy = re.valuators.values[idx];
rdy = re.raw_values[idx];
}
++idx;
}
const _error_ accelEqaccelEq = dx == rdx && dy == rdy;
if (locked)
{
yaw += rdx;
pitch += rdy;
++rawWhileLocked;
emitf("pointer", "rel dx=%.2f dy=%.2f raw=1 "
~ "accel_dx=%.2f accel_dy=%.2f accel_equal=%d "
~ "yaw=%.1f pitch=%.1f",
rdx, rdy, dx, dy, cast(int) accelEq, yaw, pitch);
}
else
emitf("raw_motion", "raw_dx=%.2f raw_dy=%.2f "
~ "accel_dx=%.2f accel_dy=%.2f accel_equal=%d device=%d",
rdx, rdy, dx, dy, cast(int) accelEq, re.deviceid);
break;
case XI_Motion:
const _error_ dede = cast(const(XIDeviceEvent)*) cookie.data;
const _error_ mxmx = cast(int) de.event_x, _error_ mymy = cast(int) de.event_y;
if (locked)
{
if (mx != cx || my != cy) // recenter (skip our own echo)
{
XWarpPointer(dpy, None, win, 0, 0, 0, 0, cx, cy);
++recenterWarps;
}
}
else
emitf("pointer", "abs x=%d y=%d root=%.0f,%.0f", mx, my,
de.root_x, de.root_y);
break;
case XI_BarrierHit:
case XI_BarrierLeave:
const _error_ bebe = cast(const(XIBarrierEvent)*) cookie.data;
emitf(cookie.evtype == XI_BarrierHit
? "barrier_hit"."barrier_hit".ptrptr : "barrier_leave"."barrier_leave".ptrptr,
"barrier=%lu root=%.0f,%.0f blocked_dx=%.1f blocked_dy=%.1f",
be.barrier, be.root_x, be.root_y, be.be.dxdx, be.be.dydy);
break;
default:
break;
}
}
XFreeEventData(dpy, cookie);
break;
default:
break;
}
}
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) unitUsunitUs)
void app.main.enterPhase(int p) nothrow @nogc @systementerPhase((local variable) int phasephase + 1);
else if (!(local variable) const(bool) drivendriven && (local variable) const(long) nownow >= (local variable) long nextProbeUsnextProbeUs)
{
void app.main.runProbe() nothrow @nogc @systemrunProbe();
(local variable) long nextProbeUsnextProbeUs = (local variable) const(long) nownow + 60_000;
}
if ((local variable) bool lockedlocked)
void app.main.redraw() pure nothrow @nogc @saferedraw(); // keep the crosshair live while deltas accumulate
}
XFlush(dpy);
pollfd (local variable) _error_ pfdpfd;
pfd.pfd.fdfd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
poll(&pfd, 1, autoExit ? 5 : -1);
}
// -- Teardown ----------------------------------------------------------------
void app.main.setPlainGrab(bool on) pure nothrow @nogc @safesetPlainGrab(false);
void app.main.setBarriers(bool on) nothrow @nogc @systemsetBarriers(false);
void app.main.setConfineGrab(bool on) nothrow @nogc @systemsetConfineGrab(false);
if ((local variable) bool lockedlocked)
void app.main.setLock(bool on) nothrow @nogc @systemsetLock(false);
if (dpy2 !is null)
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;
}