// F15 — Popup with grab, Win32 implementation
// (../../../features/f15-popup.md). Extends the scaffold (../scaffold/app.d)
// with the capture-variant context menu the spec asks for:
//
// * Right-click on the main window opens a WS_POPUP + WS_EX_NOACTIVATE +
// WS_EX_TOPMOST menu window (3 items, hover highlight) at the pointer,
// then takes SetCapture on it — Win32's "grab": all MOUSE input is routed
// to the capture window (in its client coords, which go negative outside
// it). Keyboard input is NOT captured — it follows focus, which
// WS_EX_NOACTIVATE deliberately leaves on the main window, so Esc arrives
// as the main window's WM_KEYDOWN.
// * Outside-click dismissal = capture-routed WM_LBUTTONDOWN whose screen
// coords hit-test outside the popup chain. Hit-testing is done in SCREEN
// coordinates against the app-known chain rects (popup + submenu) — the
// one-capture-owner + hit-test pattern.
// * Placement is pure app math (no positioner object exists): anchor at the
// click, flip-x/flip-y against the monitor work area
// (MonitorFromPoint/GetMonitorInfoW) when the menu would overflow —
// every term of the computation is logged (popup_place …).
// * Submenu: hovering the last item opens a second WS_POPUP and the demo
// deliberately moves capture to it (SetCapture(sub)) to MEASURE the
// capture-is-single-window problem: the parent popup receives
// WM_CAPTURECHANGED naming the thief; naive "capture lost => dismiss"
// code would close the whole menu, so WM_CAPTURECHANGED must be filtered
// through chain knowledge. Closing the submenu hands capture back.
// * A theft probe (SetCapture by the main window while the menu is open)
// shows the real fragility: any SetCapture anywhere in the session kills
// the grab silently — the popup just gets WM_CAPTURECHANGED, logged and
// treated as dismissal (cause=capture_lost).
// * TrackPopupMenu probe: the system escape hatch is called once with
// TPM_RETURNCMD; WM_ENTERMENULOOP/WM_EXITMENULOOP and its blocking
// duration are logged. A pre-armed SetTimer fires INSIDE its modal loop
// (the same dispatch-from-modal-loop fact as F03) and EndMenu() ends it.
//
// WSI_AUTO_EXIT=1 drives everything with real injected input (SetCursorPos
// moves + SendInput button/key events): open, hover, item click, outside
// click, Esc, edge-anchored reposition, submenu chain, capture theft,
// TrackPopupMenu — then exits 0. Without it: right-click to open, interact.
//
// Only druntime's built-in core.sys.windows bindings — no third-party packages.
module (module) appapp;
import (package) corecore.(package) core.syssys.(package) core.sys.windowswindows.(module) core.sys.windows.windowsWindows API header module
Translated from MinGW API for MS-Windows 4.0
Source
core/sys/windows/windows.d
windows;
import instrument;
enum UINT_PTR (constant) _error_ app.TIMER_ID = __errorTIMER_ID = 1;
enum UINT_PTR (constant) _error_ app.MENU_TIMER_ID = __errorMENU_TIMER_ID = 2;
enum (constant) int app.TICK_MS = 16TICK_MS = 16;
enum (constant) int app.TICKS_PER_PHASE = 20TICKS_PER_PHASE = 20; // ~320 ms between scripted phases
enum (constant) int app.ITEM_W = 160ITEM_W = 160;
enum (constant) int app.ITEM_H = 24ITEM_H = 24;
enum (constant) int app.N_ITEMS = 3N_ITEMS = 3;
enum (constant) int app.MENU_H = 72MENU_H = (constant) int app.ITEM_H = 24ITEM_H * (constant) int app.N_ITEMS = 3N_ITEMS;
struct (struct) app.MenuMenu
{
HWND (field) _error_ app.Menu.hwndhwnd;
RECT (field) _error_ app.Menu.rectrect; // screen coords
}
struct (struct) app.DemoDemo
{
HDC (field) _error_ app.Demo.memDcmemDc;
HBITMAP (field) _error_ app.Demo.dibdib, (field) _error_ app.Demo.stockBmpstockBmp;
uint* (field) uint* app.Demo.pixelspixels;
int (field) int app.Demo.widthwidth, (field) int app.Demo.heightheight;
uint (field) uint app.Demo.frameframe, (field) uint app.Demo.ticksticks;
bool (field) bool app.Demo.autoExitautoExit;
int (field) int app.Demo.phasephase;
HWND (field) _error_ app.Demo.hwndMainhwndMain;
(struct) app.MenuMenu[2] (field) _error_ app.Demo.menusmenus; // [0] = popup, [1] = submenu
int (field) int app.Demo.nOpennOpen; // 0, 1 or 2
int (field) int app.Demo.hoverMenuhoverMenu = -1, (field) int app.Demo.hoverItemhoverItem = -1;
bool (field) bool app.Demo.swallowNextUpswallowNextUp; // the opening right-click's release
bool (field) bool app.Demo.inMenuLoopinMenuLoop; // inside TrackPopupMenu (pause the phase driver)
long (field) long app.Demo.menuLoopT0menuLoopT0; // TrackPopupMenu entry timestamp
}
__gshared (struct) app.DemoDemo _error_ app.gg;
// ---------------------------------------------------------------------------
// Main-window backbuffer (scaffold-identical, trimmed).
void void app.createBackbuffer(int w, int h) nothrowcreateBackbuffer(int (parameter) int ww, int (parameter) int hh) nothrow
{
if (g.(field) _error_ g.dibdib !is null)
{
SelectObject(g.g.memDcmemDc, g.g.stockBmpstockBmp);
DeleteObject(g.g.dibdib);
g.g.dibdib = null;
g.g.pixelspixels = null;
g.g.widthwidth = g.g.heightheight = 0;
}
if ((parameter) int ww <= 0 || (parameter) int hh <= 0)
return;
BITMAPINFO (local variable) _error_ bmibmi;
bmi.bmiHeader.biSize = BITMAPINFOHEADER.sizeof;
bmi.bmiHeader.biWidth = w;
bmi.bmiHeader.biHeight = -h;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* (local variable) void* bitsbits;
g.g.dibdib = CreateDIBSection(null, &bmi, DIB_RGB_COLORS, &bits, null, 0);
if (g.(field) _error_ g.dibdib is null)
return;
g.g.pixelspixels = cast(uint*) bits;
g.g.widthwidth = w;
g.g.heightheight = h;
g.g.stockBmpstockBmp = cast(HBITMAP) SelectObject(g.g.memDcmemDc, g.g.dibdib);
}
void void app.drawGradient() nothrowdrawGradient() nothrow
{
if (g.(field) _error_ g.pixelspixels is null)
return;
const (local variable) const(_error_) ww = g.(field) _error_ g.widthwidth, (local variable) const(_error_) hh = g.(field) _error_ g.heightheight;
const (local variable) const(_error_) blueblue = (g.(field) _error_ g.frameframe * 4) & 0xff;
foreach ((parameter) yy; 0 .. h)
{
uint* _error_ rowrow = g.g.pixelspixels + cast(size_t) y * w;
const _error_ greengreen = h > 1 ? (y * 255) / (h - 1) : 0;
foreach ((parameter) xx; 0 .. w)
{
const _error_ redred = w > 1 ? (x * 255) / (w - 1) : 0;
row[x] = cast(uint)((red << 16) | (green << 8) | blue);
}
}
}
// ---------------------------------------------------------------------------
// Popup placement: the app IS the positioner on Win32. Anchor at the pointer,
// gravity bottom-right; flip when the work area would be overflowed.
RECT app.placeMenuplaceMenu(int (parameter) int axax, int (parameter) int ayay) nothrow
{
POINT _error_ pp = POINT(ax, ay);
MONITORINFO _error_ mimi;
mi.cbSize = MONITORINFO.sizeof;
GetMonitorInfoW(MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY), &mi);
const _error_ wawa = mi.rcWork;
int _error_ xx = ax, _error_ yy = ay;
const(char)* _error_ adjXadjX = "none", _error_ adjYadjY = "none";
if (x + ITEM_W > wa.right)
{
x = ax - ITEM_W; // flip: open leftwards
adjX = "flip-x";
if (x < wa.left)
{
x = wa.right - ITEM_W; // slide as last resort
adjX = "slide-x";
}
}
if (y + MENU_H > wa.bottom)
{
y = ay - MENU_H;
adjY = "flip-y";
if (y < wa.top)
{
y = wa.bottom - MENU_H;
adjY = "slide-y";
}
}
logEvent("popup_place anchor=%d,%d gravity=bottom-right size=%dx%d work=%ld,%ld-%ldx%ld final=%d,%d adjust=%s,%s",
ax, ay, ITEM_W, MENU_H, wa.left, wa.top, wa.right - wa.left,
wa.bottom - wa.top, x, y, adjX, adjY);
return RECT(x, y, x + ITEM_W, y + MENU_H);
}
// ---------------------------------------------------------------------------
// Chain hit-testing in screen coordinates.
// Returns menu index (0/1) or -1; item receives the row index or -1.
int app.hitTesthitTest(POINT (parameter) POINT ss, out int (parameter) int itemitem) nothrow
{
item = -1;
foreach_reverse ((parameter) ii; 0 .. g.g.nOpennOpen) // submenu is on top
{
const _error_ rr = g.g.menusmenus[i].g.menus[i].rectrect;
if (s.s.xx >= r.left && s.s.xx < r.right && s.s.yy >= r.top && s.s.yy < r.bottom)
{
item = cast(int)((s.s.yy - r.top) / ITEM_H);
return cast(int) i;
}
}
return -1;
}
// ---------------------------------------------------------------------------
// Menu windows.
immutable wchar*[(constant) int app.N_ITEMS = 3N_ITEMS][2] (constant) int app.N_ITEMS = 3itemLabels = [
["Alpha"w.(constant) immutable(wchar)* "Alpha"w.ptr = "Alpha"wptr, "Beta"w.(constant) immutable(wchar)* "Beta"w.ptr = "Beta"wptr, "Gamma ▸"w.(constant) immutable(wchar)* "Gamma \u25b8"w.ptr = "Gamma \u25b8"wptr],
["Sub-1"w.(constant) immutable(wchar)* "Sub-1"w.ptr = "Sub-1"wptr, "Sub-2"w.(constant) immutable(wchar)* "Sub-2"w.ptr = "Sub-2"wptr, "Sub-3"w.(constant) immutable(wchar)* "Sub-3"w.ptr = "Sub-3"wptr],
];
void void app.openMenu(int idx, int ax, int ay, const(char)* cause) nothrowopenMenu(int (parameter) int idxidx, int (parameter) int axax, int (parameter) int ayay, const(char)* (parameter) const(char)* causecause) nothrow
{
logEvent("popup_open menu=%d anchor=%d,%d cause=%s", idx, ax, ay, cause);
const (local variable) const(_error_) rr = placeMenu(ax, ay);
HWND (local variable) _error_ hwndhwnd = CreateWindowExW(WS_EX_TOPMOST | WS_EX_NOACTIVATE,
"wsi-f15-menu"w."wsi-f15-menu"w.ptrptr, null, WS_POPUP | WS_BORDER,
r.left, r.top, ITEM_W, MENU_H, g.g.hwndMainhwndMain, null,
GetModuleHandleW(null), null);
g.(field) _error_ g.menusmenus[idx].(__error)[idx].hwndhwnd = hwnd;
ShowWindow(hwnd, SW_SHOWNOACTIVATE);
GetWindowRect(hwnd, &g.g.menusmenus[idx].g.menus[idx].rectrect); // authoritative placement
const (local variable) const(_error_) rrrr = g.(field) _error_ g.menusmenus[idx].(field) _error_ (__error)[idx].rectrect;
logEvent("popup_placed menu=%d rect=%ld,%ld-%ldx%ld", idx, rr.left, rr.top,
rr.right - rr.left, rr.bottom - rr.top);
g.g.nOpennOpen = idx + 1;
// The grab: route all mouse input to this menu window. Capture is a
// single per-queue slot — taking it for the submenu STEALS it from the
// parent popup (measured via the parent's WM_CAPTURECHANGED).
HWND (local variable) _error_ prevprev = SetCapture(hwnd);
logEvent("grab state=acquired menu=%d owner=%p prev=%p readback=%p",
idx, hwnd, prev, GetCapture());
}
void void app.closeSubmenu(const(char)* cause) nothrowcloseSubmenu(const(char)* (parameter) const(char)* causecause) nothrow
{
if (g.(field) _error_ g.nOpennOpen < 2)
return;
logEvent("popup_dismiss menu=1 cause=%s", cause);
HWND (local variable) _error_ hh = g.(field) _error_ g.menusmenus[1].(field) _error_ (__error)[1].hwndhwnd;
g.(field) _error_ g.menusmenus[1].(__error)[1].hwndhwnd = null;
g.g.nOpennOpen = 1;
SetCapture(g.g.menusmenus[0].g.menus[0].hwndhwnd); // hand the grab back to the parent
logEvent("grab state=returned_to_parent owner=%p", GetCapture());
DestroyWindow(h);
}
void void app.dismissChain(const(char)* cause) nothrowdismissChain(const(char)* (parameter) const(char)* causecause) nothrow
{
if (g.(field) _error_ g.nOpennOpen == 0)
return;
logEvent("popup_dismiss cause=%s open=%d", cause, g.g.nOpennOpen);
// Destroy top-down; release capture first so the destroys do not generate
// misleading WM_CAPTURECHANGED noise (one is sent anyway on ReleaseCapture).
g.g.nOpennOpen = 0;
g.g.hoverMenuhoverMenu = g.g.hoverItemhoverItem = -1;
ReleaseCapture();
foreach_reverse ((local variable) int ii; 0 .. 2)
if (g.(field) _error_ g.menusmenus[i].(field) _error_ (__error)[i].hwndhwnd !is null)
{
DestroyWindow(g.g.menusmenus[i].g.menus[i].hwndhwnd);
g.(field) _error_ g.menusmenus[i].(__error)[i].hwndhwnd = null;
}
logEvent("grab state=released readback=%p", GetCapture());
}
int app.menuIndexOfmenuIndexOf(HWND (parameter) HWND hwndhwnd) nothrow @nogc
{
if (hwnd is null)
return -1;
foreach ((parameter) ii; 0 .. 2)
if (g.g.menusmenus[i].g.menus[i].hwndhwnd is hwnd)
return cast(int) i;
return -1;
}
// ---------------------------------------------------------------------------
// Menu WndProc: paint + capture-routed mouse handling.
extern (Windows) LRESULT app.menuProcmenuProc(HWND (parameter) HWND hwndhwnd, UINT (parameter) UINT msgmsg, WPARAM wParam, LPARAM lParam) nothrow
{
const _error_ selfself = menuIndexOf(hwnd);
switch (msg)
{
case WM_PAINT:
PAINTSTRUCT _error_ psps;
HDC _error_ hdchdc = BeginPaint(hwnd, &ps);
HBRUSH _error_ normalnormal = CreateSolidBrush(RGB(232, 232, 232));
HBRUSH _error_ hothot = CreateSolidBrush(RGB(60, 120, 216));
SetBkMode(hdc, TRANSPARENT);
foreach ((parameter) ii; 0 .. N_ITEMS)
{
RECT _error_ rr = RECT(0, i * ITEM_H, ITEM_W, (i + 1) * ITEM_H);
const _error_ isHotisHot = self == g.g.hoverMenuhoverMenu && i == g.g.hoverItemhoverItem;
FillRect(hdc, &r, isHot ? hot : normal);
SetTextColor(hdc, isHot ? RGB(255, 255, 255) : RGB(20, 20, 20));
r.left += 8;
if (self >= 0)
DrawTextW(hdc, itemLabels[self][i], -1, &r,
DT_SINGLELINE | DT_VCENTER | DT_LEFT);
}
DeleteObject(normal);
DeleteObject(hot);
EndPaint(hwnd, &ps);
return 0;
case WM_MOUSEMOVE:
// Capture-routed: coords are THIS window's client coords, possibly
// negative / beyond its size. Convert to screen and chain-hit-test.
POINT _error_ ss = POINT(cast(short)(lParam & 0xffff), cast(short)((lParam >> 16) & 0xffff));
ClientToScreen(hwnd, &s);
int _error_ itemitem;
const _error_ mm = hitTest(s, item);
if (m != g.g.hoverMenuhoverMenu || item != g.g.hoverItemhoverItem)
{
g.g.hoverMenuhoverMenu = m;
g.g.hoverItemhoverItem = item;
logEvent("hover menu=%d item=%d screen=%ld,%ld routed_to=%d", m, item, s.s.xx, s.s.yy, self);
foreach ((parameter) ii; 0 .. g.g.nOpennOpen)
InvalidateRect(g.g.menusmenus[i].g.menus[i].hwndhwnd, null, FALSE);
// Submenu opens on hovering the parent's last item, closes when
// the hover returns to a different parent item.
if (m == 0 && item == N_ITEMS - 1 && g.g.nOpennOpen == 1)
{
const _error_ prpr = g.g.menusmenus[0].g.menus[0].rectrect;
openMenu(1, pr.right, pr.top + (N_ITEMS - 1) * ITEM_H, "submenu_hover");
}
else if (m == 0 && item != N_ITEMS - 1 && g.g.nOpennOpen == 2)
closeSubmenu("parent_hover");
}
return 0;
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
POINT _error_ scsc = POINT(cast(short)(lParam & 0xffff), cast(short)((lParam >> 16) & 0xffff));
ClientToScreen(hwnd, &sc);
int _error_ itit;
const _error_ mhmh = hitTest(sc, it);
if (mh < 0)
{
logEvent("button state=down screen=%ld,%ld routed_to=%d hit=outside", sc.sc.xx, sc.sc.yy, self);
dismissChain("outside_click");
}
else
{
logEvent("button state=down screen=%ld,%ld routed_to=%d hit=menu%d_item%d",
sc.sc.xx, sc.sc.yy, self, mh, it);
if (!(mh == 0 && it == N_ITEMS - 1)) // submenu anchor item only hovers
{
logEvent("item_activated menu=%d item=%d", mh, it);
dismissChain("item_activated");
}
}
return 0;
case WM_RBUTTONUP:
case WM_LBUTTONUP:
if (g.g.swallowNextUpswallowNextUp)
{
// The release of the click that OPENED the menu arrives capture-
// routed; activating an item from it would be wrong.
g.g.swallowNextUpswallowNextUp = false;
logEvent("button state=release swallowed=open_click");
}
return 0;
case WM_CAPTURECHANGED:
// lParam = the window that NOW has capture. Sent to the previous
// owner whenever anyone calls SetCapture/ReleaseCapture — this is the
// single-slot fragility. Filter through chain knowledge.
HWND _error_ thiefthief = cast(HWND) lParam;
const _error_ thiefIdxthiefIdx = menuIndexOf(thief);
logEvent("msg name=WM_CAPTURECHANGED menu=%d new_owner=%p chain_member=%d",
self, thief, thiefIdx);
if (thiefIdx < 0 && self == 0 && g.g.nOpennOpen > 0)
{
// Someone outside the menu chain took (or released) capture:
// the grab is gone and we cannot see outside clicks any more.
dismissChain("capture_lost");
}
return 0;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE; // belt & braces with WS_EX_NOACTIVATE
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
}
// ---------------------------------------------------------------------------
// Input injection (auto mode): real cursor warps + real button/key events, so
// the capture routing is exercised, not simulated.
void void app.warpTo(int x, int y) nothrowwarpTo(int (parameter) int xx, int (parameter) int yy) nothrow
{
SetCursorPos(x, y);
logEvent("inject method=SetCursorPos screen=%d,%d", x, y);
}
void app.clickclick(DWORD downFlag, DWORD upFlag, const(char)* name) nothrow
{
INPUT[2] _error_ inpinp;
inp[0].type = INPUT_MOUSE;
inp[0].inp[0].mimi.dwFlags = downFlag;
inp[1].type = INPUT_MOUSE;
inp[1].inp[1].mimi.dwFlags = upFlag;
const _error_ nn = SendInput(2, inp.inp.ptrptr, INPUT.sizeof);
logEvent("inject method=SendInput kind=%s sent=%u", name, n);
}
void void app.pressEsc() nothrowpressEsc() nothrow
{
INPUT[2] (local variable) _error_ inpinp;
inp[0].type = INPUT_KEYBOARD;
inp[0].ki.wVk = VK_ESCAPE;
inp[1].type = INPUT_KEYBOARD;
inp[1].ki.wVk = VK_ESCAPE;
inp[1].ki.dwFlags = KEYEVENTF_KEYUP;
const (local variable) const(_error_) nn = SendInput(2, inp.inp.ptrptr, INPUT.sizeof);
logEvent("inject method=SendInput kind=esc sent=%u", n);
}
POINT app.mainCentermainCenter() nothrow
{
POINT _error_ pp = POINT(g.g.widthwidth / 2, g.g.heightheight / 2);
ClientToScreen(g.g.hwndMainhwndMain, &p);
return p;
}
POINT app.itemCenteritemCenter(int (parameter) int menumenu, int (parameter) int itemitem) nothrow @nogc
{
const _error_ rr = g.g.menusmenus[menu].g.menus[menu].rectrect;
return POINT(r.left + ITEM_W / 2, r.top + item * ITEM_H + ITEM_H / 2);
}
// ---------------------------------------------------------------------------
// TrackPopupMenu probe — the system escape hatch, measured.
void void app.probeTrackPopupMenu() nothrowprobeTrackPopupMenu() nothrow
{
HMENU (local variable) _error_ menumenu = CreatePopupMenu();
AppendMenuW(menu, MF_STRING, 101, "Sys-Alpha"w."Sys-Alpha"w.ptrptr);
AppendMenuW(menu, MF_STRING, 102, "Sys-Beta"w."Sys-Beta"w.ptrptr);
AppendMenuW(menu, MF_STRING, 103, "Sys-Gamma"w."Sys-Gamma"w.ptrptr);
const (local variable) const(_error_) pp = mainCenter();
// The pre-armed timer fires INSIDE TrackPopupMenu's modal loop (same
// dispatch behavior as the F03 size/move loop) and calls EndMenu().
SetTimer(g.g.hwndMainhwndMain, MENU_TIMER_ID, 400, null);
g.g.menuLoopT0menuLoopT0 = nowUs();
g.g.inMenuLoopinMenuLoop = true;
logEvent("trackpopupmenu state=calling pos=%ld,%ld", p.p.xx, p.p.yy);
const (local variable) const(_error_) rr = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN,
p.p.xx, p.p.yy, 0, g.g.hwndMainhwndMain, null);
g.g.inMenuLoopinMenuLoop = false;
logEvent("trackpopupmenu state=returned cmd=%d blocked_us=%lld err=%lu",
cast(int) r, nowUs() - g.g.menuLoopT0menuLoopT0, GetLastError());
KillTimer(g.g.hwndMainhwndMain, MENU_TIMER_ID);
DestroyMenu(menu);
}
// ---------------------------------------------------------------------------
// Scripted tour.
void void app.runPhase(int n) nothrowrunPhase(int (parameter) int nn) nothrow
{
POINT (local variable) _error_ cc = mainCenter();
switch ((parameter) int nn)
{
case 0: // open at center via a real right-click
void app.warpTo(int x, int y) nothrowwarpTo(c.(field) _error_ c.xx, c.(field) _error_ c.yy);
break;
case 1:
click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, "right_click");
break;
case 2: // hover item 0, then 1
if (g.(field) _error_ g.nOpennOpen > 0)
{
const (local variable) const(_error_) pp = itemCenter(0, 0);
void app.warpTo(int x, int y) nothrowwarpTo(p.(field) _error_ p.xx, p.(field) _error_ p.yy);
}
break;
case 3:
if (g.(field) _error_ g.nOpennOpen > 0)
{
const (local variable) const(_error_) pp = itemCenter(0, 1);
void app.warpTo(int x, int y) nothrowwarpTo(p.(field) _error_ p.xx, p.(field) _error_ p.yy);
}
break;
case 4: // activate item 1
click(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, "left_click");
break;
case 5: // reopen
void app.warpTo(int x, int y) nothrowwarpTo(c.(field) _error_ c.xx, c.(field) _error_ c.yy);
click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, "right_click");
break;
case 6: // outside click (top-left of the main client, away from the menu)
POINT (local variable) _error_ oo = POINT(8, 8);
ClientToScreen(g.g.hwndMainhwndMain, &o);
void app.warpTo(int x, int y) nothrowwarpTo(o.(field) _error_ o.xx, o.(field) _error_ o.yy);
break;
case 7:
click(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, "left_click");
break;
case 8: // reopen, dismiss via Esc (keyboard follows FOCUS, not capture)
void app.warpTo(int x, int y) nothrowwarpTo(c.(field) _error_ c.xx, c.(field) _error_ c.yy);
click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, "right_click");
break;
case 9:
logEvent("focus_probe focus=%p main=%p (keyboard goes to focus, not capture)",
GetFocus(), g.g.hwndMainhwndMain);
void app.pressEsc() nothrowpressEsc();
break;
case 10: // edge probe: anchor at the work-area bottom-right corner
MONITORINFO (local variable) _error_ mimi;
mi.cbSize = MONITORINFO.sizeof;
GetMonitorInfoW(MonitorFromWindow(g.g.hwndMainhwndMain, MONITOR_DEFAULTTOPRIMARY), &mi);
g.g.swallowNextUpswallowNextUp = false;
void app.openMenu(int idx, int ax, int ay, const(char)* cause) nothrowopenMenu(0, mi.rcWork.right - 4, mi.rcWork.bottom - 4, "edge_probe");
break;
case 11:
// May the popup exceed the output bounds at all? Move it half off
// the bottom-right corner and read the rect back (no WM/compositor
// veto expected on Win32 — measured, not assumed).
if (g.(field) _error_ g.nOpennOpen > 0)
{
MONITORINFO (local variable) _error_ mi2mi2;
mi2.cbSize = MONITORINFO.sizeof;
GetMonitorInfoW(MonitorFromWindow(g.g.hwndMainhwndMain, MONITOR_DEFAULTTOPRIMARY), &mi2);
const (local variable) const(_error_) wantwant = POINT(mi2.rcMonitor.right - ITEM_W / 2,
mi2.rcMonitor.bottom - MENU_H / 2);
SetWindowPos(g.g.menusmenus[0].g.menus[0].hwndhwnd, null, want.want.xx, want.want.yy, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
RECT (local variable) _error_ rbrb;
GetWindowRect(g.g.menusmenus[0].g.menus[0].hwndhwnd, &rb);
g.(field) _error_ g.menusmenus[0].(__error)[0].rectrect = rb;
logEvent("offscreen_probe requested=%ld,%ld readback=%ld,%ld-%ldx%ld monitor_br=%ld,%ld",
want.want.xx, want.want.yy, rb.left, rb.top, rb.right - rb.left,
rb.bottom - rb.top, mi2.rcMonitor.right, mi2.rcMonitor.bottom);
}
void app.pressEsc() nothrowpressEsc();
break;
case 12: // reopen; walk to the submenu anchor item
void app.warpTo(int x, int y) nothrowwarpTo(c.(field) _error_ c.xx, c.(field) _error_ c.yy);
click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, "right_click");
break;
case 13:
if (g.(field) _error_ g.nOpennOpen > 0)
{
const (local variable) const(_error_) pp = itemCenter(0, N_ITEMS - 1); // opens the submenu
void app.warpTo(int x, int y) nothrowwarpTo(p.(field) _error_ p.xx, p.(field) _error_ p.yy);
}
break;
case 14:
if (g.(field) _error_ g.nOpennOpen > 1)
{
const (local variable) const(_error_) pp = itemCenter(1, 1); // hover Sub-2
void app.warpTo(int x, int y) nothrowwarpTo(p.(field) _error_ p.xx, p.(field) _error_ p.yy);
}
break;
case 15:
click(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, "left_click");
break;
case 16: // capture-theft probe
void app.warpTo(int x, int y) nothrowwarpTo(c.(field) _error_ c.xx, c.(field) _error_ c.yy);
click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, "right_click");
break;
case 17:
if (g.(field) _error_ g.nOpennOpen > 0)
{
logEvent("capture_theft_probe api=SetCapture(main)");
SetCapture(g.g.hwndMainhwndMain); // any window may steal — silently
ReleaseCapture();
}
break;
case 18:
void app.probeTrackPopupMenu() nothrowprobeTrackPopupMenu();
break;
case 19:
DestroyWindow(g.g.hwndMainhwndMain);
break;
default:
break;
}
}
// ---------------------------------------------------------------------------
// Main WndProc.
extern (Windows) LRESULT app.wndProcwndProc(HWND (parameter) HWND hwndhwnd, UINT (parameter) UINT msgmsg, WPARAM wParam, LPARAM lParam) nothrow
{
switch (msg)
{
case WM_CREATE:
g.g.memDcmemDc = CreateCompatibleDC(null);
return 0;
case WM_SIZE:
const _error_ ww = cast(int)(lParam & 0xffff);
const _error_ hh = cast(int)((lParam >> 16) & 0xffff);
if (wParam != SIZE_MINIMIZED && (w != g.g.widthwidth || h != g.g.heightheight))
createBackbuffer(w, h);
return 0;
case WM_RBUTTONDOWN:
// Open the menu at the pointer (screen coords).
POINT _error_ pp = POINT(cast(short)(lParam & 0xffff), cast(short)((lParam >> 16) & 0xffff));
ClientToScreen(hwnd, &p);
if (g.g.nOpennOpen == 0)
{
g.g.swallowNextUpswallowNextUp = true; // the matching button-up is capture-routed
openMenu(0, p.p.xx, p.p.yy, "right_click");
}
return 0;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE && g.g.nOpennOpen > 0)
{
logEvent("key vk=VK_ESCAPE routed_to=main_focus_window");
dismissChain("esc");
}
return 0;
case WM_ACTIVATE:
logEvent("focus state=%s reason=WM_ACTIVATE other=%p",
LOWORD(wParam) == WA_INACTIVE ? "out"."out".ptrptr : "in"."in".ptrptr, cast(void*) lParam);
goto default;
case WM_KILLFOCUS:
logEvent("focus state=out reason=WM_KILLFOCUS next=%p", cast(void*) wParam);
return 0;
case WM_SETFOCUS:
logEvent("focus state=in reason=WM_SETFOCUS prev=%p", cast(void*) wParam);
return 0;
case WM_CAPTURECHANGED:
logEvent("msg name=WM_CAPTURECHANGED menu=main new_owner=%p", cast(void*) lParam);
return 0;
case WM_ENTERMENULOOP:
logEvent("msg name=WM_ENTERMENULOOP track=%d dt_us=%lld",
cast(int) wParam, nowUs() - g.g.menuLoopT0menuLoopT0);
return 0;
case WM_EXITMENULOOP:
logEvent("msg name=WM_EXITMENULOOP track=%d dt_us=%lld",
cast(int) wParam, nowUs() - g.g.menuLoopT0menuLoopT0);
return 0;
case WM_INITMENUPOPUP:
logEvent("msg name=WM_INITMENUPOPUP menu=%p", cast(void*) wParam);
goto default;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
PAINTSTRUCT _error_ psps;
HDC _error_ hdchdc = BeginPaint(hwnd, &ps);
++g.g.frameframe;
drawGradient();
if (g.g.pixelspixels !is null)
BitBlt(hdc, 0, 0, g.g.widthwidth, g.g.heightheight, g.g.memDcmemDc, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
return 0;
case WM_TIMER:
if (wParam == MENU_TIMER_ID)
{
// Fires inside TrackPopupMenu's modal loop — proof + exit lever.
logEvent("timer id=menu inside_menu_loop dt_us=%lld", nowUs() - g.g.menuLoopT0menuLoopT0);
EndMenu();
return 0;
}
if (wParam != TIMER_ID)
return 0;
++g.g.ticksticks;
InvalidateRect(hwnd, null, FALSE);
// Regular ticks are dispatched inside TrackPopupMenu's modal loop
// too — pause the phase driver there so only the MENU_TIMER acts.
if (g.g.autoExitautoExit && !g.g.inMenuLoopinMenuLoop && g.g.ticksticks % TICKS_PER_PHASE == 0)
runPhase(g.g.phasephase++);
return 0;
case WM_DESTROY:
KillTimer(hwnd, TIMER_ID);
createBackbuffer(0, 0);
if (g.g.memDcmemDc !is null)
{
DeleteDC(g.g.memDcmemDc);
g.g.memDcmemDc = null;
}
PostQuitMessage(0);
return 0;
default:
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
}
// ---------------------------------------------------------------------------
bool bool app.wantAutoExit() nothrowwantAutoExit() nothrow
{
WCHAR[8] (local variable) _error_ bufbuf;
const (local variable) const(_error_) nn = GetEnvironmentVariableW("WSI_AUTO_EXIT"w."WSI_AUTO_EXIT"w.ptrptr, buf.buf.ptrptr, buf.length);
return n >= 1 && n < buf.length && buf[0] == '1';
}
int int D main()main()
{
instrumentInit("f15_popup_win32");
logEvent("init_start");
g.g.autoExitautoExit = wantAutoExit();
logEvent("mode auto_exit=%d", g.g.autoExitautoExit ? 1 : 0);
HINSTANCE (local variable) _error_ hInsthInst = GetModuleHandleW(null);
HCURSOR (local variable) _error_ arrowarrow = LoadCursorW(null, IDC_ARROW);
WNDCLASSEXW (local variable) _error_ wcwc;
wc.cbSize = WNDCLASSEXW.sizeof;
wc.lpfnWndProc = &wndProc;
wc.hInstance = hInst;
wc.lpszClassName = "wsi-f15-class"w."wsi-f15-class"w.ptrptr;
wc.hCursor = arrow;
if (!RegisterClassExW(&wc))
return 1;
WNDCLASSEXW (local variable) _error_ mcmc;
mc.cbSize = WNDCLASSEXW.sizeof;
mc.lpfnWndProc = &menuProc;
mc.hInstance = hInst;
mc.lpszClassName = "wsi-f15-menu"w."wsi-f15-menu"w.ptrptr;
mc.hCursor = arrow;
if (!RegisterClassExW(&mc))
return 1;
g.g.hwndMainhwndMain = CreateWindowExW(0, "wsi-f15-class"w."wsi-f15-class"w.ptrptr, "wsi-f15-popup"w."wsi-f15-popup"w.ptrptr,
WS_OVERLAPPEDWINDOW, 60, 40, 480, 320, null, null, hInst, null);
if (g.(field) _error_ g.hwndMainhwndMain is null)
{
logEvent("error what=CreateWindowExW code=%lu", GetLastError());
return 1;
}
logEvent("window_created hwnd=%p", g.g.hwndMainhwndMain);
ShowWindow(g.g.hwndMainhwndMain, SW_SHOW);
UpdateWindow(g.g.hwndMainhwndMain);
SetTimer(g.g.hwndMainhwndMain, TIMER_ID, TICK_MS, null);
MSG (local variable) _error_ msgmsg;
while (GetMessageW(&msg, null, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
logEvent("exit code=%d", cast(int) msg.wParam);
return cast(int) msg.wParam;
}