app.dhover×276 error×57all
// F03 — modal-loop survival, Win32 (../../f03-modal-loop.md).
//
// Implements the Win32 headline of ../../../features/f03-modal-loop.md on top
// of the scaffold (../scaffold/app.d):
//
//   * A ~2 Hz full-window color-cycle animation. Crucially, the animation is
//     driven from the *main loop body* (PeekMessage drain -> tick -> bounded
//     MsgWaitForMultipleObjects wait), the way a game/UI loop renders — NOT
//     from a SetTimer. That is exactly the shape the Win32 modal size/move
//     loop starves: once DefWindowProcW enters its nested pump, the app's own
//     loop body stops running and the animation freezes.
//   * WSI_AUTO_EXIT=1 enters the modal loop *programmatically*, three ways in
//     sequence, each bracketed by WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE
//     ("modal_enter"/"modal_exit"):
//       1. SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT) — the
//          mouse-grab sizing variant custom-chrome apps use from
//          WM_NCLBUTTONDOWN (the loop expects the button to be held);
//       2. SendMessage(WM_SYSCOMMAND, SC_SIZE) — keyboard-mode sizing
//          ("Size" from the system menu; waits for arrow keys);
//       3. SendMessage(WM_SYSCOMMAND, SC_MOVE) — keyboard-mode move.
//     Because no human is present, a watchdog thread feeds each loop with
//     synthetic input via PostMessage (a WM_MOUSEMOVE, arrow-key WM_KEYDOWNs
//     that really size/move the window, then VK_RETURN to confirm) and
//     escalates to VK_ESCAPE -> WM_CANCELMODE -> WM_CLOSE if the loop refuses
//     to exit, so the freeze-measurement window is always bounded.
//   * WSI_MODAL_FIX=1 arms the survey-wide countermeasure: SetTimer on
//     WM_ENTERSIZEMOVE, render a tick from each WM_TIMER (the modal loop's
//     internal pump DOES dispatch timers), KillTimer on WM_EXITSIZEMOVE.
//     Ticks then continue *inside* the modal loop (src=timer).
//   * Every tick logs `tick t=... src=loop|timer`; inter-tick gaps are
//     tracked globally and per modal window, and each modal window emits a
//     `modal_summary ... max_gap_us=` line measuring the freeze (no-fix) or
//     its absence (fix).
//
// Without WSI_AUTO_EXIT the demo runs until closed — grab a border or the
// titlebar and watch the ticks (the Tier C interactive script on Windows).
//
// Only druntime's built-in core.sys.windows bindings — no third-party packages.
module 
(module) app
app
;
import
(package) core
core
.
(module) core.atomic

The atomic module provides basic support for lock-free concurrent programming.

Use the -preview=nosharedaccess compiler flag to detect unsafe individual read or write operations on shared data.

Source

core/atomic.d

Examples

int y = 2;
shared int x = y; // OK

//x++; // read modify write error
x.atomicOp!"+="(1); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 3);
//x = 5; // write error with preview flag
x.atomicStore(5); // OK
assert(x.atomicLoad() == 5);
@copyrightCopyright Sean Kelly 2005 - 2016.@licenseBoost License 1.0@authorsSean Kelly, Alex Rønne Petersen, Manu Evans
atomic
:
(alias template) app.atomicLoad = core.atomic.atomicLoad(MemoryOrder ms = MemoryOrder.seq, T)(auto ref return scope const T val) if (!is(T == shared(U), U) && !is(T == shared(inout(U)), U) && !is(T == shared(const(U)), U))

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
,
(alias template) app.atomicStore = core.atomic.atomicStore(MemoryOrder ms = MemoryOrder.seq, T, V)(ref T val, V newval) if (!is(T == shared) && !is(V == shared))

Writes 'newval' into 'val'. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.rel, and MemoryOrder.seq.

@paramval The target variable.@paramnewval The value to store.
atomicStore
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.windows

Windows API header module

Translated from MinGW API for MS-Windows 4.0

Source

core/sys/windows/windows.d

windows
;
import instrument;
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
struct
(struct) app.Demo
Demo
{ HDC
(field) _error_ app.Demo.memDc
memDc
; // memory DC the DIB section is selected into
undefined identifier `HDC`
HBITMAP
(field) _error_ app.Demo.dib
dib
; // top-down 32-bit DIB section (CPU-visible backbuffer)
undefined identifier `HBITMAP`
HBITMAP
(field) _error_ app.Demo.stockBmp
stockBmp
; // the 1x1 stock bitmap displaced by SelectObject
undefined identifier `HBITMAP`
uint*
(field) uint* app.Demo.pixels
pixels
; // DIB bits: 0x00RRGGBB, row-major, row 0 = top row
int
(field) int app.Demo.width
width
,
(field) int app.Demo.height
height
; // current client size, physical pixels
uint
(field) uint app.Demo.color
color
; // current animation color (solid fill)
uint
(field) uint app.Demo.tickCount
tickCount
; // animation ticks so far
long
(field) long app.Demo.lastTickUs
lastTickUs
; // timestamp of the previous tick
long
(field) long app.Demo.maxGapUs
maxGapUs
; // worst inter-tick gap over the whole run
bool
(field) bool app.Demo.modalFix
modalFix
; // WSI_MODAL_FIX=1: SetTimer countermeasure armed
bool
(field) bool app.Demo.autoExit
autoExit
; // WSI_AUTO_EXIT=1: bounded run with programmatic modal entry
bool
(field) bool app.Demo.firstPaintDone
firstPaintDone
;
// Current modal window (the attempts run strictly one at a time). const(char)*
(field) const(char)* app.Demo.curName
curName
; // attempt name for the log lines
long
(field) long app.Demo.curEnterUs
curEnterUs
,
(field) long app.Demo.curExitUs
curExitUs
;
long
(field) long app.Demo.curMaxGapUs
curMaxGapUs
; // worst tick gap from last pre-enter tick to first post-exit tick
uint
(field) uint app.Demo.curTicks
curTicks
; // ticks observed while inside the modal loop
uint
(field) uint app.Demo.curSizing
curSizing
,
(field) uint app.Demo.curMoving
curMoving
; // WM_SIZING / WM_MOVING seen inside the loop
bool
(field) bool app.Demo.bridgePending
bridgePending
; // modal exited; next tick closes the measurement
bool
(field) bool app.Demo.everEntered
everEntered
; // WM_ENTERSIZEMOVE seen for the current attempt
} __gshared
(struct) app.Demo
Demo
_error_ app.g
g
;
__gshared HWND
_error_ app.g_hwnd
g_hwnd
;
undefined identifier `HWND`
shared bool
(shared global) shared(bool) app.s_inModal
s_inModal
; // read by the watchdog thread
shared bool
(shared global) shared(bool) app.s_attemptOver
s_attemptOver
; // the SendMessage returned; watchdog exits early
enum UINT_PTR
(constant) _error_ app.MODAL_TIMER_ID = __error
MODAL_TIMER_ID
= 2; // the WM_ENTERSIZEMOVE countermeasure timer
undefined identifier `UINT_PTR`
enum
(constant) int app.TICK_MS = 16
TICK_MS
= 16; // ~60 Hz animation tick (loop body and modal timer alike)
enum
(constant) int app.CYCLE_US = 500000
CYCLE_US
= 500_000; // 2 Hz: one full hue cycle every 500 ms
enum
(constant) int app.WARMUP_TICKS = 30
WARMUP_TICKS
= 30; // ~0.5 s of animation before/between modal attempts
// WMSZ_* direction nibble ORed onto SC_SIZE (winuser.h; absent from druntime). enum
(constant) int app.WMSZ_BOTTOMRIGHT = 8
WMSZ_BOTTOMRIGHT
= 8;
// --------------------------------------------------------------------------- // Backbuffer: a DIB section reallocated on every client-size change // (same strategy as the scaffold; the arrow-key sizing resizes mid-loop). void
void app.createBackbuffer(int w, int h) nothrow
createBackbuffer
(int
(parameter) int w
w
, int
(parameter) int h
h
) nothrow
{ if (g.
(field) _error_ g.dib
dib
!is null)
{ SelectObject(g.
g.memDc
memDc
, g.
g.stockBmp
stockBmp
);
undefined identifier `SelectObject`
DeleteObject(g.
g.dib
dib
);
undefined identifier `DeleteObject`
g.
g.dib
dib
= null;
g.
g.pixels
pixels
= null;
g.
g.width
width
= g.
g.height
height
= 0;
} if (
(parameter) int w
w
<= 0 ||
(parameter) int h
h
<= 0)
return; BITMAPINFO
(local variable) _error_ bmi
bmi
;
undefined identifier `BITMAPINFO`
bmi.bmiHeader.biSize = BITMAPINFOHEADER.sizeof; bmi.bmiHeader.biWidth = w; bmi.bmiHeader.biHeight = -h; // negative height = top-down rows bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; void*
(local variable) void* bits
bits
;
g.
g.dib
dib
= CreateDIBSection(null, &bmi, DIB_RGB_COLORS, &bits, null, 0);
if (g.
(field) _error_ g.dib
dib
is null)
{ logEvent("error what=CreateDIBSection code=%lu", GetLastError());
undefined identifier `logEvent`
return; } g.
g.pixels
pixels
= cast(uint*) bits;
g.
g.width
width
= w;
g.
g.height
height
= h;
g.
g.stockBmp
stockBmp
= cast(HBITMAP) SelectObject(g.
g.memDc
memDc
, g.
g.dib
dib
);
logEvent("buffer_alloc size=%dx%d bytes=%d", w, h, w * h * 4);
undefined identifier `logEvent`
} // ~2 Hz full-saturation hue cycle: any >=1-frame freeze is a visible color // jump, and the per-tick log line is the measurable counterpart. uint
uint app.colorAt(long tUs) nothrow @nogc
colorAt
(long
(parameter) long tUs
tUs
) nothrow @nogc
{ const
(local variable) const(double) phase
phase
= cast(double)(
(parameter) long tUs
tUs
%
(constant) int app.CYCLE_US = 500000
CYCLE_US
) /
(constant) int app.CYCLE_US = 500000
CYCLE_US
;
const
(local variable) const(double) h
h
=
(local variable) const(double) phase
phase
* 6.0;
const
(local variable) const(int) sector
sector
= cast(int)
(local variable) const(double) h
h
% 6;
const
(local variable) const(double) f
f
=
(local variable) const(double) h
h
-
(local variable) const(int) sector
sector
;
const
(local variable) const(uint) down
down
= cast(uint)(255.0 * (1.0 -
(local variable) const(double) f
f
));
const
(local variable) const(uint) up
up
= cast(uint)(255.0 *
(local variable) const(double) f
f
);
uint
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(uint
(parameter) uint r
r
, uint
(parameter) uint gr
gr
, uint
(parameter) uint b
b
) @nogc nothrow
{ return (
(parameter) uint r
r
<< 16) | (
(parameter) uint gr
gr
<< 8) |
(parameter) uint b
b
;
} switch (
(local variable) const(int) sector
sector
)
{ case 0: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(255,
(local variable) const(uint) up
up
, 0);
case 1: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(
(local variable) const(uint) down
down
, 255, 0);
case 2: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(0, 255,
(local variable) const(uint) up
up
);
case 3: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(0,
(local variable) const(uint) down
down
, 255);
case 4: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(
(local variable) const(uint) up
up
, 0, 255);
default: return
uint app.colorAt.rgb(uint r, uint gr, uint b) pure nothrow @nogc @safe
rgb
(255, 0,
(local variable) const(uint) down
down
);
} } void
void app.fillSolid(uint color) nothrow
fillSolid
(uint
(parameter) uint color
color
) nothrow
{ if (g.
(field) _error_ g.pixels
pixels
is null)
return; const
(local variable) const(_error_) n
n
= cast(
(alias) object.size_t = ulong
size_t
) g.
(field) _error_ g.width
width
* g.
(field) _error_ g.height
height
;
foreach (
(parameter) i
i
; 0 .. n)
g.
g.pixels
pixels
[i] = color;
} // --------------------------------------------------------------------------- // The animation tick: advance the color, log, account the inter-tick gap, and // present synchronously. Called from the main loop body (src=loop) and — only // while the modal loop holds the thread, in fix mode — from WM_TIMER // (src=timer). void
void app.tick(const(char)* src) nothrow
tick
(const(char)*
(parameter) const(char)* src
src
) nothrow
{ const
(local variable) const(_error_) t
t
= nowUs();
undefined identifier `nowUs`
if (g.
(field) _error_ g.tickCount
tickCount
> 0)
{ const
(local variable) const(_error_) gap
gap
= t - g.
(field) _error_ g.lastTickUs
lastTickUs
;
if (gap > g.
(field) _error_ g.maxGapUs
maxGapUs
)
g.
g.maxGapUs
maxGapUs
= gap;
if (
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.s_inModal
s_inModal
))
{ if (gap > g.
(field) _error_ g.curMaxGapUs
curMaxGapUs
)
g.
g.curMaxGapUs
curMaxGapUs
= gap;
++g.
(field) _error_ g.curTicks
curTicks
;
} else if (g.
(field) _error_ g.bridgePending
bridgePending
)
{ // First tick after WM_EXITSIZEMOVE: the bridge gap (last tick // before/inside the loop -> this tick) completes the measurement. if (gap > g.
(field) _error_ g.curMaxGapUs
curMaxGapUs
)
g.
g.curMaxGapUs
curMaxGapUs
= gap;
g.
g.bridgePending
bridgePending
= false;
logEvent("modal_summary name=%s dur_us=%lld ticks_during=%u "
undefined identifier `logEvent`
~ "max_gap_us=%lld sizing=%u moving=%u", g.
g.curName
curName
, g.
g.curExitUs
curExitUs
- g.
g.curEnterUs
curEnterUs
, g.
g.curTicks
curTicks
,
g.
g.curMaxGapUs
curMaxGapUs
, g.
g.curSizing
curSizing
, g.
g.curMoving
curMoving
);
} } g.
g.lastTickUs
lastTickUs
= t;
++g.
(field) _error_ g.tickCount
tickCount
;
g.
g.color
color
= colorAt(t);
logEvent("tick t=%lld src=%s frame=%u", t, src, g.
g.tickCount
tickCount
);
undefined identifier `logEvent`
InvalidateRect(g_hwnd, null, FALSE);
undefined identifier `InvalidateRect`
UpdateWindow(g_hwnd); // present now — queued WM_PAINTs may never be seen
undefined identifier `UpdateWindow`
} // --------------------------------------------------------------------------- // Watchdog thread: feeds the modal loop synthetic input so the freeze window // is bounded with no human present, then escalates until the loop exits. // Plain CreateThread — it only Sleeps, injects input, and logs (no GC). // // Two feed styles, matching how the two loop modes really consume input: // * mouse (grab variants, the realistic drag): SendInput relative // MOUSEEVENTF_MOVEs while the button injected by the main thread is held, // then MOUSEEVENTF_LEFTUP — the loop's documented exit condition. // * kbd (keyboard variants): posted WM_KEYDOWN arrows pick the edge and // size/move the window, then VK_RETURN confirms. // Escalation ladder if the loop is still alive: VK_ESCAPE -> button-up -> // WM_CANCELMODE -> WM_CLOSE. enum
(enum) app.FeedKind
FeedKind
{
(enum value) app.FeedKind.mouse = 0
mouse
,
(enum value) app.FeedKind.kbd = 1
kbd
,
} enum
(constant) int app.FREEZE_HOLD_MS = 600
FREEZE_HOLD_MS
= 600; // evidence window before the feed starts
void
app.postKey
postKey
(DWORD vk, const(char)*
(parameter) const(char)* name
name
) nothrow
undefined identifier `DWORD`
{ logEvent("watchdog action=post_key vk=%s ok=%d", name, PostMessageW(g_hwnd, WM_KEYDOWN, vk, 0) ? 1 : 0); } void
app.sendMouse
sendMouse
(DWORD flags, int dx, int dy, const(char)* what) nothrow
undefined identifier `DWORD`
{ INPUT
_error_ inp
inp
;
inp.type = INPUT_MOUSE; inp.mi.dx = dx; inp.mi.dy = dy; inp.mi.dwFlags = flags; const
_error_ sent
sent
= SendInput(1, &inp, INPUT.sizeof);
logEvent("watchdog action=send_input what=%s sent=%u", what, sent); } // Sleep in 50 ms slices, bailing as soon as the SendMessage has returned. bool
bool app.sleepUnlessOver(int ms) nothrow
sleepUnlessOver
(int
(parameter) int ms
ms
) nothrow
{ foreach (
(local variable) int i
i
; 0 ..
(parameter) int ms
ms
/ 50)
{ if (
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.s_attemptOver
s_attemptOver
))
return false; Sleep(50);
undefined identifier `Sleep`
} return !
bool core.atomic.atomicLoad!(MemoryOrder.seq, bool)(ref return scope shared(const(bool)) val) pure nothrow @nogc @trusted

Loads 'val' from memory and returns it. The memory barrier specified by 'ms' is applied to the operation, which is fully sequenced by default. Valid memory orders are MemoryOrder.raw, MemoryOrder.acq, and MemoryOrder.seq.

@paramval The target variable.@returnsThe value of 'val'.
atomicLoad
(
(shared global) shared(bool) app.s_attemptOver
s_attemptOver
);
} extern (Windows) DWORD
app.watchdogProc
watchdogProc
(LPVOID param) nothrow
undefined identifier `DWORD`
undefined identifier `LPVOID`
{ const
_error_ feed
feed
= cast(
(unresolved type) FeedKind
FeedKind
) cast(
(unresolved type) size_t
size_t
) param;
// Let the freeze accumulate a measurable evidence window first. (For the // keyboard variants this also covers start_size_move's pre-loop, which // runs *before* WM_ENTERSIZEMOVE and eats the first input.) if (!sleepUnlessOver(FREEZE_HOLD_MS)) return 0; if (feed == FeedKind.
FeedKind.mouse
mouse
)
{ // Relative moves: each one reaches the modal loop as a real // WM_MOUSEMOVE and drives a WM_SIZING/WM_MOVING + window update. foreach (
(parameter) i
i
; 0 .. 4)
{ sendMouse(MOUSEEVENTF_MOVE, 16, 12, "move+16+12"); if (!sleepUnlessOver(50)) return 0; } if (!sleepUnlessOver(150)) return 0; sendMouse(MOUSEEVENTF_LEFTUP, 0, 0, "leftup"); // the documented exit } else { // Arrows: in the pre-loop they pick the resize edge; in the modal // loop proper they move the cursor 8 px per press (sizing/moving). foreach (
(parameter) i
i
; 0 .. 3)
{ postKey(VK_RIGHT, "RIGHT"); if (!sleepUnlessOver(50)) return 0; } foreach (
(parameter) i
i
; 0 .. 2)
{ postKey(VK_DOWN, "DOWN"); if (!sleepUnlessOver(50)) return 0; } if (!sleepUnlessOver(100)) return 0; postKey(VK_RETURN, "RETURN"); // confirm = exit the modal loop } // Escalation ladder, ~500 ms per rung, until the SendMessage returns. if (!sleepUnlessOver(500)) return 0; postKey(VK_ESCAPE, "ESCAPE"); if (!sleepUnlessOver(500)) return 0; sendMouse(MOUSEEVENTF_LEFTUP, 0, 0, "leftup_escalate"); if (!sleepUnlessOver(500)) return 0; logEvent("watchdog action=post_cancelmode ok=%d", PostMessageW(g_hwnd, WM_CANCELMODE, 0, 0) ? 1 : 0); if (!sleepUnlessOver(500)) return 0; logEvent("watchdog event=giveup name=%s action=post_close", g.
g.curName
curName
);
PostMessageW(g_hwnd, WM_CLOSE, 0, 0); return 0; } // --------------------------------------------------------------------------- // One programmatic modal-loop attempt. SendMessage to our own window is a // direct WndProc call; the unhandled WM_SYSCOMMAND falls through to // DefWindowProcW, which runs the entire interactive size/move modal loop // inside this call — it does not return until the loop exits. void
app.runModalAttempt
runModalAttempt
(const(char)*
(parameter) const(char)* name
name
, WPARAM
(parameter) WPARAM wparam
wparam
,
(enum) app.FeedKind
FeedKind
(parameter) FeedKind feed
feed
, bool
(parameter) bool holdButton
holdButton
) nothrow
undefined identifier `WPARAM`
{ g.
g.curName
curName
= name;
g.
g.curEnterUs
curEnterUs
= g.
g.curExitUs
curExitUs
= 0;
g.
g.curMaxGapUs
curMaxGapUs
= 0;
g.
g.curTicks
curTicks
= g.
g.curSizing
curSizing
= g.
g.curMoving
curMoving
= 0;
g.
g.bridgePending
bridgePending
= false;
g.
g.everEntered
everEntered
= false;
RECT
_error_ r
r
;
GetWindowRect(g_hwnd, &r); const
_error_ cx
cx
= (r.left + r.right) / 2,
_error_ cy
cy
= (r.top + r.bottom) / 2;
// Pre-flight: the conditions the size/move loop bails on — zoomed, // invisible — plus the activation state, since interactive size/move // presumes a foreground window. logEvent("probe name=%s visible=%d zoomed=%d iconic=%d foreground=%d active=%d", name, IsWindowVisible(g_hwnd) ? 1 : 0, IsZoomed(g_hwnd) ? 1 : 0, IsIconic(g_hwnd) ? 1 : 0, GetForegroundWindow() is g_hwnd ? 1 : 0, GetActiveWindow() is g_hwnd ? 1 : 0); // The grab variants model a real drag, where the user already holds the // left button (a custom-chrome app sends SC_SIZE|edge from // WM_NCLBUTTONDOWN). The loop polls VK_LBUTTON and treats button-up as // "drag over", so a *real* (injected) press must precede the request. SetCursorPos(cx, cy); if (holdButton) sendMouse(MOUSEEVENTF_LEFTDOWN, 0, 0, "leftdown"); logEvent("modal_request name=%s wparam=0x%04llx cursor=%d,%d", name, cast(ulong) wparam, cx, cy); atomicStore(s_attemptOver, false); HANDLE
_error_ th
th
= CreateThread(null, 0, &watchdogProc,
cast(void*) cast(
(unresolved type) size_t
size_t
) feed, 0, null);
const
_error_ t0
t0
= nowUs();
SendMessageW(g_hwnd, WM_SYSCOMMAND, wparam, cast(LPARAM)((cast(uint) cy << 16) | (cast(uint) cx & 0xffff))); atomicStore(s_attemptOver, true); logEvent("modal_request_returned name=%s dur_us=%lld entered=%d", name, nowUs() - t0, g.
g.everEntered
everEntered
? 1 : 0);
if (th !is null) { WaitForSingleObject(th, 5000); // no stray input into the next attempt CloseHandle(th); } if (holdButton) // defensive: never leave the synthetic button held sendMouse(MOUSEEVENTF_LEFTUP, 0, 0, "leftup_cleanup"); if (!g.
g.everEntered
everEntered
)
logEvent("modal_summary name=%s dur_us=0 ticks_during=0 max_gap_us=0 " ~ "sizing=0 moving=0 note=never_entered", name); } // --------------------------------------------------------------------------- // The window procedure. extern (Windows) LRESULT
app.wndProc
wndProc
(HWND hwnd, UINT
(parameter) UINT msg
msg
, WPARAM wParam, LPARAM lParam) nothrow
undefined identifier `LRESULT`
undefined identifier `HWND`
undefined identifier `UINT`
undefined identifier `WPARAM`
undefined identifier `LPARAM`
{ switch (msg) { case WM_CREATE: g.
g.memDc
memDc
= CreateCompatibleDC(null);
return 0; case WM_SYSCOMMAND: logEvent("msg name=WM_SYSCOMMAND sc=0x%04llx", cast(ulong) wParam); goto default; // DefWindowProcW runs the modal loop right here case WM_ENTERSIZEMOVE: g.
g.curEnterUs
curEnterUs
= nowUs();
g.
g.everEntered
everEntered
= true;
atomicStore(s_inModal, true); logEvent("modal_enter t=%lld fix=%d", g.
g.curEnterUs
curEnterUs
, g.
g.modalFix
modalFix
? 1 : 0);
if (g.
g.modalFix
modalFix
)
{ // The countermeasure: the modal loop's internal pump dispatches // WM_TIMER, so a timer armed here keeps the animation ticking // while GetMessageW never returns to our own loop. SetTimer(hwnd, MODAL_TIMER_ID, TICK_MS, null); logEvent("step name=SetTimer id=modal interval_ms=%d", TICK_MS); } goto default; case WM_EXITSIZEMOVE: g.
g.curExitUs
curExitUs
= nowUs();
if (g.
g.modalFix
modalFix
)
KillTimer(hwnd, MODAL_TIMER_ID); atomicStore(s_inModal, false); g.
g.bridgePending
bridgePending
= true;
logEvent("modal_exit t=%lld dur_us=%lld ticks_during=%u", g.
g.curExitUs
curExitUs
, g.
g.curExitUs
curExitUs
- g.
g.curEnterUs
curEnterUs
, g.
g.curTicks
curTicks
);
goto default; case WM_TIMER: if (wParam == MODAL_TIMER_ID && atomicLoad(s_inModal)) tick("timer"); // a frame from *inside* the modal loop return 0; case WM_CANCELMODE: logEvent("msg name=WM_CANCELMODE"); goto default; case WM_GETMINMAXINFO: // Queried by the size/move loop just before WM_ENTERSIZEMOVE — // seeing it proves the modal-loop machinery actually started. logEvent("msg name=WM_GETMINMAXINFO"); goto default; case WM_CAPTURECHANGED: logEvent("msg name=WM_CAPTURECHANGED"); return 0; case WM_SIZING: ++g.
g.curSizing
curSizing
;
const
_error_ sr
sr
= cast(RECT*) lParam;
logEvent("msg name=WM_SIZING edge=%d rect=%ld,%ld-%ld,%ld", cast(int) wParam, sr.left, sr.top, sr.right, sr.bottom); goto default; case WM_MOVING: ++g.
g.curMoving
curMoving
;
const
_error_ mr
mr
= cast(RECT*) lParam;
logEvent("msg name=WM_MOVING rect=%ld,%ld-%ld,%ld", mr.left, mr.top, mr.right, mr.bottom); goto default; case WM_KEYDOWN: // Watchdog keys that arrive here were NOT consumed by a modal loop. logEvent("msg name=WM_KEYDOWN vk=0x%02llx in_modal=%d", cast(ulong) wParam, atomicLoad(s_inModal) ? 1 : 0); return 0; case WM_SIZE: const
_error_ w
w
= cast(int)(lParam & 0xffff);
const
_error_ h
h
= cast(int)((lParam >> 16) & 0xffff);
logEvent("resize size=%dx%d", w, h); if (wParam == SIZE_MINIMIZED) return 0; if (w != g.
g.width
width
|| h != g.
g.height
height
)
createBackbuffer(w, h); return 0; case WM_MOVE: logEvent("msg name=WM_MOVE pos=%d,%d", cast(int) cast(short)(lParam & 0xffff), cast(int) cast(short)((lParam >> 16) & 0xffff)); return 0; case WM_ERASEBKGND: return 1; // the solid fill covers every pixel case WM_PAINT: PAINTSTRUCT
_error_ ps
ps
;
HDC
_error_ hdc
hdc
= BeginPaint(hwnd, &ps);
fillSolid(g.
g.color
color
);
if (g.
g.pixels
pixels
!is null)
BitBlt(hdc, 0, 0, g.
g.width
width
, g.
g.height
height
, g.
g.memDc
memDc
, 0, 0, SRCCOPY);
if (!g.
g.firstPaintDone
firstPaintDone
)
{ g.
g.firstPaintDone
firstPaintDone
= true;
logEvent("first_pixel_presented size=%dx%d", g.
g.width
width
, g.
g.height
height
);
} EndPaint(hwnd, &ps); return 0; case WM_CLOSE: logEvent("close_requested"); goto default; // DefWindowProcW responds with DestroyWindow case WM_DESTROY: logEvent("msg name=WM_DESTROY"); KillTimer(hwnd, MODAL_TIMER_ID); createBackbuffer(0, 0); // frees the DIB section if (g.
g.memDc
memDc
!is null)
{ DeleteDC(g.
g.memDc
memDc
);
g.
g.memDc
memDc
= null;
} PostQuitMessage(0); return 0; default: return DefWindowProcW(hwnd, msg, wParam, lParam); } } // --------------------------------------------------------------------------- bool
bool app.envFlag(const(wchar)* name) nothrow
envFlag
(const(wchar)*
(parameter) const(wchar)* name
name
) nothrow
{ WCHAR[8]
(local variable) _error_ buf
buf
;
undefined identifier `WCHAR`
const
(local variable) const(_error_) n
n
= GetEnvironmentVariableW(name, buf.ptr, buf.length);
undefined identifier `GetEnvironmentVariableW`
return n >= 1 && n < buf.length && buf[0] == '1'; } int
int D main()
main
()
{ instrumentInit("f03_modal_loop_win32");
undefined identifier `instrumentInit`
logEvent("init_start");
undefined identifier `logEvent`
g.
g.autoExit
autoExit
= envFlag("WSI_AUTO_EXIT"w.ptr);
g.
g.modalFix
modalFix
= envFlag("WSI_MODAL_FIX"w.ptr);
logEvent("mode auto_exit=%d modal_fix=%d", g.
g.autoExit
autoExit
? 1 : 0, g.
g.modalFix
modalFix
? 1 : 0);
undefined identifier `logEvent`
HINSTANCE
(local variable) _error_ hInst
hInst
= GetModuleHandleW(null);
undefined identifier `HINSTANCE`
undefined identifier `GetModuleHandleW`
HCURSOR
(local variable) _error_ arrow
arrow
= LoadCursorW(null, IDC_ARROW);
undefined identifier `HCURSOR`
undefined identifier `LoadCursorW`
auto
(local variable) wstring clsName
clsName
= "wsi-f03-class"w;
WNDCLASSEXW
(local variable) _error_ wc
wc
;
undefined identifier `WNDCLASSEXW`
wc.cbSize = WNDCLASSEXW.sizeof; wc.lpfnWndProc = &wndProc; wc.hInstance = hInst; wc.lpszClassName = clsName.ptr; wc.hCursor = arrow; logEvent("step name=RegisterClassExW");
undefined identifier `logEvent`
if (!RegisterClassExW(&wc))
undefined identifier `RegisterClassExW`
{ logEvent("error what=RegisterClassExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("step name=CreateWindowExW");
undefined identifier `logEvent`
g_hwnd = CreateWindowExW(0, clsName.ptr, "wsi-f03-modal-loop"w.ptr, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 320, null, null, hInst, null); if (g_hwnd is null) { logEvent("error what=CreateWindowExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("window_created");
undefined identifier `logEvent`
ShowWindow(g_hwnd, SW_SHOW);
undefined identifier `ShowWindow`
SetForegroundWindow(g_hwnd); // size/move presumes a foreground window
undefined identifier `SetForegroundWindow`
UpdateWindow(g_hwnd);
undefined identifier `UpdateWindow`
// The main loop: drain the queue, render one tick from the loop body, // sleep on the queue with a TICK_MS cap. No SetTimer drives the normal // animation — that is the point: this loop body is what the modal loop // starves, exactly like a PeekMessage game loop or a toolkit iteration. static struct
(struct) app.main.Attempt
Attempt
{
(alias) object.string = string
string
(field) string app.main.Attempt.name
name
;
WPARAM
(field) _error_ app.main.Attempt.wparam
wparam
;
undefined identifier `WPARAM`
(enum) app.FeedKind
FeedKind
(field) app.FeedKind app.main.Attempt.feed
feed
;
bool
(field) bool app.main.Attempt.holdButton
holdButton
;
} static immutable
(struct) app.main.Attempt
Attempt
[3]
(immutable global) immutable(_error_) app.main.attempts
attempts
= [
// The two realistic interactions (button held, mouse-fed): an // interactive border resize and a title-bar drag — F03 requirement 2. Attempt("sc_size_grab", SC_SIZE | WMSZ_BOTTOMRIGHT, FeedKind.
FeedKind.mouse
mouse
, true),
Attempt("sc_move_caption", SC_MOVE | 2 /* HTCAPTION nibble */, FeedKind.
FeedKind.mouse
mouse
, true),
// The keyboard variant ("Size" from the system menu), arrow-key-fed. Attempt("sc_size_kbd", SC_SIZE, FeedKind.
FeedKind.kbd
kbd
, false),
]; uint
(local variable) uint attemptIdx
attemptIdx
= 0;
uint
(local variable) uint nextActionTick
nextActionTick
=
(constant) int app.WARMUP_TICKS = 30
WARMUP_TICKS
;
bool
(local variable) bool running
running
= true;
int
(local variable) int exitCode
exitCode
= 0;
while (
(local variable) bool running
running
)
{ MSG
(local variable) _error_ msg
msg
;
undefined identifier `MSG`
while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE))
undefined identifier `PeekMessageW`
{ if (msg.message == WM_QUIT)
undefined identifier `WM_QUIT`
{
(local variable) bool running
running
= false;
(local variable) int exitCode
exitCode
= cast(int) msg.wParam;
break; } TranslateMessage(&msg);
undefined identifier `TranslateMessage`
DispatchMessageW(&msg);
undefined identifier `DispatchMessageW`
} if (!
(local variable) bool running
running
)
break;
void app.tick(const(char)* src) nothrow
tick
("loop");
if (g.
(field) _error_ g.autoExit
autoExit
&& g.
(field) _error_ g.tickCount
tickCount
>=
(local variable) uint nextActionTick
nextActionTick
)
{ if (
(local variable) uint attemptIdx
attemptIdx
< attempts.length)
{ const
(local variable) const(_error_) a
a
= attempts[attemptIdx++];
runModalAttempt(a.
a.name
name
.ptr, a.
a.wparam
wparam
, a.
a.feed
feed
, a.
a.holdButton
holdButton
);
(local variable) uint nextActionTick
nextActionTick
= g.
(field) _error_ g.tickCount
tickCount
+ WARMUP_TICKS;
} else { logEvent("summary mode=%s ticks=%u max_gap_us=%lld attempts=%d",
undefined identifier `logEvent`
g.
g.modalFix
modalFix
? "fix".ptr : "nofix".ptr, g.
g.tickCount
tickCount
,
g.
g.maxGapUs
maxGapUs
, cast(int) attempts.length);
DestroyWindow(g_hwnd);
undefined identifier `DestroyWindow`
continue; // drain WM_DESTROY ... WM_QUIT } } // Wake on any message or after TICK_MS — a poll-style frame cadence // without a busy loop. MsgWaitForMultipleObjects(0, null, FALSE, TICK_MS, QS_ALLINPUT);
undefined identifier `MsgWaitForMultipleObjects`
} logEvent("exit code=%d", exitCode);
undefined identifier `logEvent`
return
(local variable) int exitCode
exitCode
;
}