// X11 F15 demo — popup with grab (../../f15-popup.md, spec
// ../../../features/f15-popup.md). Evolves the scaffold (../scaffold/app.d)
// event loop; rendering is server-side XFillRectangle (the subject is the
// secondary-surface + grab model, not pixels).
//
// What it exercises:
// * a menu-like popup: override-redirect window (CWOverrideRedirect — the
// WM never sees it) + XGrabPointer (owner_events=False,
// ButtonPress|ButtonRelease|PointerMotion) + XGrabKeyboard, raised with
// XRaiseWindow; 3 fake items with hover highlight (motion inside the
// grab); item click activates; outside click dismisses (the grab is WHY
// the popup sees clicks it does not own); Esc dismisses
// * grab routing choice: ONE pointer grab on the first popup,
// owner_events=False, so every pointer event arrives relative to the
// grab window and hit-testing is done in root coordinates against the
// app-known popup-chain rects (the alternative — owner_events=True or
// re-grabbing on the submenu — is discussed in the findings doc)
// * edge correctness: opening near the bottom-right screen corner — the
// APP must clamp; the math is logged (no compositor/positioner exists)
// * nested submenu one level deep (second override-redirect window),
// opened on hovering item 2; stacking verified via XQueryTree
// * probes: XGrabPointer return codes when a second client already holds
// a grab (AlreadyGrabbed); event starvation of a second client's window
// while the grab is held (driven mode, see run.sh)
//
// Modes: WSI_AUTO_EXIT=1 self-drives placement/stacking/grab-code probes
// (no synthetic input needed) and exits 0. WSI_DRIVEN=1 waits ~14 s for
// xdotool-injected clicks/keys (run.sh): right-click opens the menu at the
// pointer, hover/submenu/item-click/outside-click/Esc are all real pointer
// events through the grab. Headless-safe: no display -> `SKIP:`, exit 0.
module (module) appapp;
import (module) cc; // ImportC: Xlib + Xutil + Xatom (+ unused XShm) + 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;
// Macros ImportC cannot export, re-declared (see ../scaffold/app.d):
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.PointerMotionMask = 64LPointerMotionMask = 1L << 6,
(enum value) app.ExposureMask = 32768LExposureMask = 1L << 15,
(enum value) app.StructureNotifyMask = 131072LStructureNotifyMask = 1L << 17,
(enum value) app.FocusChangeMask = 2097152LFocusChangeMask = 1L << 21,
}
enum // XEvent.type discriminators
{
(enum value) app.KeyPress = 2KeyPress = 2,
(enum value) app.ButtonPress = 4ButtonPress = 4,
(enum value) app.ButtonRelease = 5ButtonRelease = 5,
(enum value) app.MotionNotify = 6MotionNotify = 6,
(enum value) app.FocusIn = 9FocusIn = 9,
(enum value) app.FocusOut = 10FocusOut = 10,
(enum value) app.Expose = 12Expose = 12,
(enum value) app.MapNotify = 19MapNotify = 19,
(enum value) app.ConfigureNotify = 22ConfigureNotify = 22,
(enum value) app.ClientMessage = 33ClientMessage = 33,
}
enum : c_ulong // XCreateWindow attribute mask bits
{
(enum value) app.CWBackPixel = 2LUCWBackPixel = 1L << 1,
(enum value) app.CWBorderPixel = 8LUCWBorderPixel = 1L << 3,
(enum value) app.CWOverrideRedirect = 512LUCWOverrideRedirect = 1L << 9,
(enum value) app.CWSaveUnder = 1024LUCWSaveUnder = 1L << 10,
(enum value) app.CWEventMask = 2048LUCWEventMask = 1L << 11,
}
enum (constant) int app.False = 0False = 0;
enum (constant) int app.True = 1True = 1;
enum (constant) int app.POLLIN = 1POLLIN = 0x001;
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.InputOutput = 1InputOutput = 1;
enum (constant) int app.CopyFromParent = 0CopyFromParent = 0;
enum (constant) int app.XK_Escape = 65307XK_Escape = 0xff1b;
// XGrabPointer/XGrabKeyboard return codes (<X11/X.h>):
static immutable (alias) object.string = stringstring[5] (immutable global) immutable(string[5]) app.grabCodesgrabCodes =
["GrabSuccess", "AlreadyGrabbed", "GrabInvalidTime", "GrabNotViewable", "GrabFrozen"];
// FocusIn/FocusOut mode decode (<X11/X.h>) — grab-generated focus events
// (NotifyGrab/NotifyUngrab) are NOT window-manager focus:
static immutable (alias) object.string = stringstring[4] (immutable global) immutable(string[4]) app.focusModesfocusModes =
["NotifyNormal", "NotifyGrab", "NotifyUngrab", "NotifyWhileGrabbed"];
enum (constant) int app.itemH = 24itemH = 24, (constant) int app.menuW = 160menuW = 160, (constant) int app.menuItems = 3menuItems = 3;
enum (constant) int app.menuH = 72menuH = (constant) int app.itemH = 24itemH * (constant) int app.menuItems = 3menuItems;
struct (struct) app.MenuMenu
{
Window (field) _error_ app.Menu.winwin;
int (field) int app.Menu.xx, (field) int app.Menu.yy; // root coordinates
bool (field) bool app.Menu.openopen;
int (field) int app.Menu.hoverhover = -1;
}
struct (struct) app.DemoDemo
{
Display* (field) _error_ app.Demo.dpydpy, (field) _error_ app.Demo.dpy2dpy2; // dpy2: the independent "second client" connection
Window (field) _error_ app.Demo.rootroot, (field) _error_ app.Demo.mainmain, (field) _error_ app.Demo.secondsecond;
int (field) int app.Demo.screenscreen, (field) int app.Demo.screenWscreenW, (field) int app.Demo.screenHscreenH;
GC (field) _error_ app.Demo.gcgc;
Atom (field) _error_ app.Demo.wmProtocolswmProtocols, (field) _error_ app.Demo.wmDeletewmDelete;
(struct) app.MenuMenu[2] (field) _error_ app.Demo.menusmenus; // [0] the popup menu, [1] its submenu
bool (field) bool app.Demo.runningrunning = true, (field) bool app.Demo.grabbedgrabbed, (field) bool app.Demo.swallowReleaseswallowRelease;
int (field) int app.Demo.opensopens, (field) int app.Demo.dismissalsdismissals, (field) int app.Demo.activationsactivations, (field) int app.Demo.hoverChangeshoverChanges;
int (field) int app.Demo.secondClientEventssecondClientEvents, (field) int app.Demo.popupFocusEventspopupFocusEvents;
}
__gshared int (__gshared global) int app.g_xerrorsg_xerrors;
extern (C) int app.onXErroronXError(Display* (parameter) Display* dpydpy, XErrorEvent* e) @nogc nothrow
{
++g_xerrors;
emitf("x_error", "code=%d request=%d", e.error_code, e.request_code);
return 0;
}
Window app.makeOverrideRedirectmakeOverrideRedirect(ref (struct) app.DemoDemo (parameter) Demo dd, int (parameter) int xx, int (parameter) int yy, uint w, uint h) @nogc nothrow
{
XSetWindowAttributes _error_ attrsattrs;
attrs.override_redirect = True; // the WM neither decorates nor manages it
attrs.background_pixel = 0x202020;
attrs.border_pixel = 0x808080;
attrs.save_under = True;
attrs.event_mask = ExposureMask | FocusChangeMask;
return XCreateWindow(d.d.dpydpy, d.d.rootroot, x, y, w, h, 1, CopyFromParent,
InputOutput, null /* CopyFromParent visual */ ,
CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWSaveUnder | CWEventMask,
&attrs);
}
void app.drawMenudrawMenu(ref (struct) app.DemoDemo (parameter) Demo dd, ref (struct) app.MenuMenu (parameter) Menu mm) @nogc nothrow
{
if (!m.m.openopen)
return;
foreach ((parameter) ii; 0 .. menuItems)
{
XSetForeground(d.d.dpydpy, d.d.gcgc, i == m.m.hoverhover ? 0x6090d0 : 0x383838);
XFillRectangle(d.d.dpydpy, m.m.winwin, d.d.gcgc, 2, i * itemH + 2,
menuW - 4, itemH - 4);
}
XFlush(d.d.dpydpy);
}
/// Is the popup (or submenu) on top of the stack? XQueryTree returns root's
/// children bottom-to-top; override-redirect windows fight no WM for slots,
/// but a WM may still restack other windows around them.
void app.logStackingIs the popup (or submenu) on top of the stack? XQueryTree returns root's
children bottom-to-top; override-redirect windows fight no WM for slots,
but a WM may still restack other windows around them.
logStacking(ref (struct) app.DemoDemo (parameter) Demo dd, Window expectTop, const(char)* label) @nogc nothrow
{
Window _error_ rootRetrootRet, _error_ parentRetparentRet;
Window* _error_ childrenchildren;
uint _error_ nn;
XQueryTree(d.d.dpydpy, d.d.rootroot, &rootRet, &parentRet, &children, &n);
const _error_ toptop = n ? children[n - 1] : 0;
emitf("stacking", "probe=%s topmost=0x%lx expected=0x%lx on_top=%d",
label, top, expectTop, cast(int)(top == expectTop));
if (children !is null)
XFree(children);
}
/// Open the popup at the pointer/anchor with app-side edge clamping — on X11
/// nobody else will move it (contrast: a Wayland xdg_positioner lets the
/// compositor slide/flip it). The clamp math is the finding; log it.
void app.openMenuOpen the popup at the pointer/anchor with app-side edge clamping — on X11
nobody else will move it (contrast: a Wayland xdg_positioner lets the
compositor slide/flip it). The clamp math is the finding; log it.
openMenu(ref (struct) app.DemoDemo (parameter) Demo dd, (alias) object.size_t = ulongsize_t idx, int anchorX, int anchorY,
const(char)* cause) @nogc nothrow
{
(unresolved type) MenuMenu* _error_ mm = &d.d.menusmenus[idx];
int _error_ xx = anchorX, _error_ yy = anchorY;
if (x + menuW + 2 > d.d.screenWscreenW)
x = d.d.screenWscreenW - menuW - 2;
if (y + menuH + 2 > d.d.screenHscreenH)
y = d.d.screenHscreenH - menuH - 2;
emitf("popup_open", "menu=%zu anchor=%d,%d gravity=bottom-right cause=%s",
idx, anchorX, anchorY, cause);
if (m.m.winwin == None)
m.m.winwin = makeOverrideRedirect(d, x, y, menuW, menuH);
else
XMoveWindow(d.d.dpydpy, m.m.winwin, x, y);
m.m.xx = x;
m.m.yy = y;
m.m.openopen = true;
m.m.hoverhover = -1;
XMapRaised(d.d.dpydpy, m.m.winwin); // map + XRaiseWindow in one request
emitf("popup_placed", "menu=%zu rect=%dx%d+%d+%d repositioned=%d "
~ "clamp=anchor+size>screen(%dx%d)", idx, menuW, menuH, x, y,
cast(int)(x != anchorX || y != anchorY), d.d.screenWscreenW, d.d.screenHscreenH);
++d.d.opensopens;
if (idx == 0 && !d.d.grabbedgrabbed)
{
// owner_events=False: during the grab EVERY pointer event in the
// whole session is reported to THIS client, relative to the grab
// window — including clicks over windows we do not own. That is
// the entire outside-click-dismissal mechanism.
const _error_ pgpg = XGrabPointer(d.d.dpydpy, m.m.winwin, False,
cast(uint)(ButtonPressMask | ButtonReleaseMask | PointerMotionMask),
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
const _error_ kgkg = XGrabKeyboard(d.d.dpydpy, m.m.winwin, False, GrabModeAsync,
GrabModeAsync, CurrentTime);
emitf("grab", "state=acquired pointer=%s keyboard=%s owner_events=0",
grabCodes[pg].ptr, grabCodes[kg].ptr);
d.d.grabbedgrabbed = pg == 0;
}
logStacking(d, m.m.winwin, "after_map_raised");
}
void app.dismissAlldismissAll(ref (struct) app.DemoDemo (parameter) Demo dd, const(char)* cause) @nogc nothrow
{
if (d.d.grabbedgrabbed)
{
XUngrabPointer(d.d.dpydpy, CurrentTime);
XUngrabKeyboard(d.d.dpydpy, CurrentTime);
emit("grab state=released");
d.d.grabbedgrabbed = false;
}
foreach_reverse (ref (parameter) mm; d.d.menusmenus)
if (m.m.openopen)
{
XUnmapWindow(d.d.dpydpy, m.m.winwin);
m.m.openopen = false;
}
XFlush(d.d.dpydpy);
++d.d.dismissalsdismissals;
emitf("popup_dismiss", "cause=%s", cause);
}
/// Root-coordinate hit test across the popup chain (submenu first: it is on
/// top). Returns menu index, item via `item`; -1 if outside everything.
int app.hitTestRoot-coordinate hit test across the popup chain (submenu first: it is on
top). Returns menu index, item via item; -1 if outside everything.
hitTest(ref (struct) app.DemoDemo (parameter) Demo dd, int xr, int yr, out int (parameter) int itemitem) @nogc nothrow
{
foreach_reverse ((parameter) ii; 0 .. d.d.menusmenus.d.menus.lengthlength)
{
const _error_ mm = &d.d.menusmenus[i];
if (m.m.openopen && xr >= m.m.xx && xr < m.m.xx + menuW && yr >= m.m.yy && yr < m.m.yy + menuH)
{
item = (yr - m.m.yy) / itemH;
return cast(int) i;
}
}
item = -1;
return -1;
}
/// The "another grab already exists" probe: the second client grabs first,
/// then we try — XGrabPointer returns AlreadyGrabbed (1) instead of any
/// event; the failure is a synchronous return code, not an error.
void app.probeGrabConflictThe "another grab already exists" probe: the second client grabs first,
then we try — XGrabPointer returns AlreadyGrabbed (1) instead of any
event; the failure is a synchronous return code, not an error.
probeGrabConflict(ref (struct) app.DemoDemo (parameter) Demo dd) @nogc nothrow
{
const _error_ g2g2 = XGrabPointer(d.d.dpy2dpy2, d.d.secondsecond, False,
cast(uint) ButtonPressMask, GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
XSync(d.d.dpy2dpy2, False);
emitf("probe", "name=second_client_grab result=%s", grabCodes[g2].ptr);
const _error_ g1g1 = XGrabPointer(d.d.dpydpy, d.d.mainmain, False,
cast(uint) ButtonPressMask, GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
emitf("probe", "name=our_grab_while_other_holds result=%s", grabCodes[g1].ptr);
XUngrabPointer(d.d.dpy2dpy2, CurrentTime);
XSync(d.d.dpy2dpy2, False);
const _error_ g3g3 = XGrabPointer(d.d.dpydpy, d.d.mainmain, False,
cast(uint) ButtonPressMask, GrabModeAsync, GrabModeAsync,
None, None, CurrentTime);
emitf("probe", "name=our_grab_after_release result=%s", grabCodes[g3].ptr);
if (g3 == 0)
XUngrabPointer(d.d.dpydpy, CurrentTime);
XFlush(d.d.dpydpy);
}
void app.handleMotionhandleMotion(ref (struct) app.DemoDemo (parameter) Demo dd, int xr, int yr) @nogc nothrow
{
int _error_ itemitem;
const _error_ mimi = hitTest(d, xr, yr, item);
if (mi < 0)
return;
(unresolved type) MenuMenu* _error_ mm = &d.d.menusmenus[mi];
if (item != m.m.hoverhover)
{
m.m.hoverhover = item;
++d.d.hoverChangeshoverChanges;
emitf("hover", "menu=%d item=%d", mi, item);
drawMenu(d, *m);
// Item 2 of the root menu carries the submenu: open it flush with
// the item's right edge; hovering items 0/1 closes it again.
if (mi == 0 && item == menuItems - 1 && !d.d.menusmenus[1].d.menus[1].openopen)
openMenu(d, 1, m.m.xx + menuW - 4, m.m.yy + item * itemH, "submenu_hover");
else if (mi == 0 && item < menuItems - 1 && d.d.menusmenus[1].d.menus[1].openopen)
{
XUnmapWindow(d.d.dpydpy, d.d.menusmenus[1].d.menus[1].winwin);
d.d.menusmenus[1].d.menus[1].openopen = false;
emit("popup_dismiss cause=submenu_parent_hover menu=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("f15_x11");
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;
(struct) app.DemoDemo (local variable) _error_ dd;
d.d.dpydpy = XOpenDisplay(null);
if (d.(field) _error_ d.dpydpy 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;
}
XSetErrorHandler(&onXError);
d.d.screenscreen = XDefaultScreen(d.d.dpydpy);
d.d.rootroot = XRootWindow(d.d.dpydpy, d.d.screenscreen);
d.d.screenWscreenW = XDisplayWidth(d.d.dpydpy, d.d.screenscreen);
d.d.screenHscreenH = XDisplayHeight(d.d.dpydpy, d.d.screenscreen);
d.d.gcgc = XDefaultGC(d.d.dpydpy, d.d.screenscreen);
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=XOpenDisplay fd=%d screen=%dx%d",
XConnectionNumber(d.d.dpydpy), d.(field) _error_ d.screenWscreenW, d.(field) _error_ d.screenHscreenH);
d.d.mainmain = XCreateSimpleWindow(d.d.dpydpy, d.d.rootroot, 20, 20, 480, 320, 1,
XBlackPixel(d.d.dpydpy, d.d.screenscreen), 0x4070b0);
XStoreName(d.d.dpydpy, d.d.mainmain, "Sparkles \xc2\xb7 X11 F15 popup");
d.d.wmProtocolswmProtocols = XInternAtom(d.d.dpydpy, "WM_PROTOCOLS", False);
d.d.wmDeletewmDelete = XInternAtom(d.d.dpydpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(d.d.dpydpy, d.d.mainmain, &d.d.wmDeletewmDelete, 1);
// ButtonReleaseMask matters: the release of the popup-opening click can
// reach the server BEFORE the XGrabPointer issued in response to the
// press — it is then delivered by the normal masks, not the grab.
XSelectInput(d.d.dpydpy, d.d.mainmain, ExposureMask | KeyPressMask | ButtonPressMask
| ButtonReleaseMask | StructureNotifyMask);
XMapWindow(d.d.dpydpy, d.d.mainmain);
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=480x320+20+20", d.(field) _error_ d.mainmain);
// The independent second client: its own connection, its own window. It
// exists to measure what a grab does to everyone else (run.sh clicks it
// while the popup grab is held: it must receive nothing).
d.d.dpy2dpy2 = XOpenDisplay(null);
d.d.secondsecond = XCreateSimpleWindow(d.d.dpy2dpy2, XRootWindow(d.d.dpy2dpy2, 0), 520, 20,
100, 100, 1, 0, 0xb0b040);
XStoreName(d.d.dpy2dpy2, d.d.secondsecond, "Sparkles \xc2\xb7 F15 second client");
XSelectInput(d.d.dpy2dpy2, d.d.secondsecond, ButtonPressMask | ExposureMask);
XMapWindow(d.d.dpy2dpy2, d.d.secondsecond);
XFlush(d.d.dpy2dpy2);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("second_client", "fd=%d xid=0x%lx size=100x100+520+20",
XConnectionNumber(d.d.dpy2dpy2), d.(field) _error_ d.secondsecond);
// Self-driven probe schedule (µs since init), driven mode skips it:
enum (enum) app.main.ActAct { (enum value) app.main.Act.open = 0open, (enum value) app.main.Act.openSub = 1openSub, (enum value) app.main.Act.dismiss = 2dismiss, (enum value) app.main.Act.edgeOpen = 3edgeOpen, (enum value) app.main.Act.grabConflict = 4grabConflict, (enum value) app.main.Act.quit = 5quit }
static immutable struct (struct) app.main.StepStep { long (field) immutable(long) app.main.Step.tt; (enum) app.main.ActAct (field) immutable(app.main.Act) app.main.Step.aa; }
static immutable (struct) app.main.StepStep[8] (immutable global) immutable(app.main.Step[8]) app.main.scheduleschedule = [
{400_000, (enum) app.main.ActAct.(enum value) app.main.Act.open = 0open}, {700_000, (enum) app.main.ActAct.(enum value) app.main.Act.openSub = 1openSub}, {1_000_000, (enum) app.main.ActAct.(enum value) app.main.Act.dismiss = 2dismiss},
{1_300_000, (enum) app.main.ActAct.(enum value) app.main.Act.edgeOpen = 3edgeOpen}, {1_700_000, (enum) app.main.ActAct.(enum value) app.main.Act.dismiss = 2dismiss},
{2_000_000, (enum) app.main.ActAct.(enum value) app.main.Act.grabConflict = 4grabConflict}, {2_400_000, (enum) app.main.ActAct.(enum value) app.main.Act.quit = 5quit}, {0, (enum) app.main.ActAct.(enum value) app.main.Act.quit = 5quit},
];
(alias) object.size_t = ulongsize_t (local variable) ulong nextStepnextStep;
const (local variable) const(int) deadlinedeadline = (local variable) const(bool) drivendriven ? 14_000_000 : 4_000_000;
const (local variable) const(_error_) fdfd = XConnectionNumber(d.d.dpydpy);
const (local variable) const(_error_) fd2fd2 = XConnectionNumber(d.d.dpy2dpy2);
while (d.(field) _error_ d.runningrunning)
{
while (XPending(d.d.dpydpy) > 0)
{
XEvent (local variable) _error_ evev;
XNextEvent(d.d.dpydpy, &ev);
switch (ev.type)
{
case ButtonPress:
d.d.swallowReleaseswallowRelease = false; // the swallow applies only to the
// release that immediately follows the opening press
emitf("button", "state=press n=%d window=0x%lx pos=%d,%d root=%d,%d "
~ "grabbed=%d", ev.xbutton.button, ev.xbutton.window,
ev.xbutton.ev.xbutton.xx, ev.xbutton.ev.xbutton.yy, ev.xbutton.x_root,
ev.xbutton.y_root, cast(int) d.d.grabbedgrabbed);
if (d.d.grabbedgrabbed)
{
int _error_ itemitem;
if (hitTest(d, ev.xbutton.x_root, ev.xbutton.y_root, item) < 0)
dismissAll(d, "outside_click");
}
else if (ev.xbutton.window == d.d.mainmain && ev.xbutton.button == 3)
{
openMenu(d, 0, ev.xbutton.x_root, ev.xbutton.y_root, "right_click");
// The release of the click that OPENED the menu would
// otherwise instantly activate the item now under the
// pointer — every menu toolkit swallows it.
d.d.swallowReleaseswallowRelease = true;
}
break;
case ButtonRelease:
if (d.d.swallowReleaseswallowRelease)
{
d.d.swallowReleaseswallowRelease = false;
emit("button state=release swallowed=open_click");
}
else if (d.d.grabbedgrabbed)
{
int _error_ itemitem;
const _error_ mimi = hitTest(d, ev.xbutton.x_root, ev.xbutton.y_root, item);
if (mi >= 0)
{
++d.d.activationsactivations;
emitf("popup_item_activated", "menu=%d item=%d", mi, item);
dismissAll(d, "item_activated");
}
}
break;
case MotionNotify:
if (d.d.grabbedgrabbed)
handleMotion(d, ev.xmotion.x_root, ev.xmotion.y_root);
break;
case KeyPress:
const _error_ symsym = XLookupKeysym(&ev.xkey, 0);
emitf("key", "sym=0x%lx via_grab=%d", sym, cast(int) d.d.grabbedgrabbed);
if (sym == XK_Escape && d.d.grabbedgrabbed)
dismissAll(d, "esc");
else if (sym == 'q' && !d.d.grabbedgrabbed)
d.d.runningrunning = false;
break;
case FocusIn:
case FocusOut:
// Popups never receive WM focus — keyboard arrives only via
// the grab. Any focus event reaching a popup is the server's
// grab bookkeeping; the mode says so.
foreach (ref (parameter) mm; d.d.menusmenus)
if (ev.xany.window == m.m.winwin)
{
++d.d.popupFocusEventspopupFocusEvents;
emitf("focus", "window=0x%lx state=%s mode=%s",
ev.xany.window, ev.type == FocusIn ? "in".ptr : "out".ptr,
focusModes[ev.xfocus.mode].ptr);
}
break;
case Expose:
if (ev.xexpose.count != 0)
break;
foreach (ref (parameter) mm; d.d.menusmenus)
if (ev.xexpose.window == m.m.winwin)
drawMenu(d, m);
break;
case ClientMessage:
if (cast(Atom) ev.xclient.data.l[0] == d.d.wmDeletewmDelete)
d.d.runningrunning = false;
break;
default:
break;
}
}
// Drain the second client's queue — its silence during the grab is
// the starvation measurement.
while (XPending(d.d.dpy2dpy2) > 0)
{
XEvent (local variable) _error_ ev2ev2;
XNextEvent(d.d.dpy2dpy2, &ev2);
if (ev2.type == (enum value) app.ButtonPress = 4ButtonPress)
{
++d.(field) _error_ d.secondClientEventssecondClientEvents;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("second_client", "event=button_press pos=%d,%d during_grab=%d",
ev2.xbutton.(field) _error_ ev2.xbutton.xx, ev2.xbutton.(field) _error_ ev2.xbutton.yy, cast(int) d.(field) _error_ d.grabbedgrabbed);
}
}
if ((local variable) const(bool) autoExitautoExit && !(local variable) const(bool) drivendriven && (local variable) ulong nextStepnextStep < (immutable global) immutable(app.main.Step[8]) app.main.scheduleschedule.(constant) ulong immutable(app.main.Step[8]).length = 8LUlength
&& long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs() >= (immutable global) immutable(app.main.Step[8]) app.main.scheduleschedule[(local variable) ulong nextStepnextStep].(field) immutable(long) app.main.Step.tt)
{
final switch ((immutable global) immutable(app.main.Step[8]) app.main.scheduleschedule[(local variable) ulong nextStepnextStep].(field) immutable(app.main.Act) app.main.Step.aa)
{
case (enum) app.main.ActAct.(enum value) app.main.Act.open = 0open:
openMenu(d, 0, 200, 200, "auto");
break;
case (enum) app.main.ActAct.(enum value) app.main.Act.openSub = 1openSub:
d.(field) _error_ d.menusmenus[0].(__error)[0].hoverhover = menuItems - 1;
drawMenu(d, d.d.menusmenus[0]);
openMenu(d, 1, d.d.menusmenus[0].d.menus[0].xx + menuW - 4,
d.d.menusmenus[0].d.menus[0].yy + (menuItems - 1) * itemH, "auto");
break;
case (enum) app.main.ActAct.(enum value) app.main.Act.dismiss = 2dismiss:
dismissAll(d, "auto");
break;
case (enum) app.main.ActAct.(enum value) app.main.Act.edgeOpen = 3edgeOpen:
openMenu(d, 0, d.d.screenWscreenW - 10, d.d.screenHscreenH - 10, "auto_edge");
break;
case (enum) app.main.ActAct.(enum value) app.main.Act.grabConflict = 4grabConflict:
probeGrabConflict(d);
break;
case (enum) app.main.ActAct.(enum value) app.main.Act.quit = 5quit:
d.d.runningrunning = false;
break;
}
++(local variable) ulong nextStepnextStep;
if ((local variable) ulong nextStepnextStep == (immutable global) immutable(app.main.Step[8]) app.main.scheduleschedule.(constant) ulong immutable(app.main.Step[8]).length = 8LUlength - 1)
break; // sentinel reached
}
if (((local variable) const(bool) autoExitautoExit || (local variable) const(bool) drivendriven) && long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs() > (local variable) const(int) deadlinedeadline)
{
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("auto_exit reason=deadline");
break;
}
pollfd[2] (local variable) _error_ pfdpfd;
pfd[0].(__error)[0].fdfd = fd;
pfd[0].events = POLLIN;
pfd[1].(__error)[1].fdfd = fd2;
pfd[1].events = POLLIN;
poll(pfd.ptr, 2, (autoExit || driven) ? 20 : -1);
}
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("summary", "opens=%d dismissals=%d activations=%d hover_changes=%d "
~ "second_client_events=%d popup_focus_events=%d x_errors=%d",
d.(field) _error_ d.opensopens, d.(field) _error_ d.dismissalsdismissals, d.(field) _error_ d.activationsactivations, d.(field) _error_ d.hoverChangeshoverChanges,
d.(field) _error_ d.secondClientEventssecondClientEvents, d.(field) _error_ d.popupFocusEventspopupFocusEvents, (__gshared global) int app.g_xerrorsg_xerrors);
XCloseDisplay(d.d.dpy2dpy2);
XCloseDisplay(d.d.dpydpy);
return 0;
}