// F14 — Window state & vetoable close, Win32 implementation
// (../../../features/f14-window-state.md). Extends the scaffold
// (../scaffold/app.d) into a state-transition observatory:
//
// * ShowWindow(SW_MAXIMIZE / SW_MINIMIZE / SW_RESTORE) for the real states,
// and the borderless-fullscreen IDIOM for fullscreen (Win32 has no
// fullscreen window state): save GetWindowLongPtrW(GWL_STYLE) +
// GetWindowRect, strip WS_OVERLAPPEDWINDOW, SetWindowPos to the
// MonitorFromWindow rect with SWP_FRAMECHANGED; exit reverses it.
// * Every message until the state settles is logged with decoded payloads:
// WM_SIZE wParam (SIZE_RESTORED/MINIMIZED/MAXIMIZED/…),
// WM_WINDOWPOSCHANGING/WM_WINDOWPOSCHANGED (flags + rect),
// WM_GETMINMAXINFO (ptMaxSize/ptMaxPosition), WM_ACTIVATE (state +
// minimized flag + peer hwnd), WM_SETFOCUS/WM_KILLFOCUS (peer hwnd — the
// "where does focus go on minimize" probe), WM_SYSCOMMAND, WM_SHOWWINDOW.
// * After each transition, GetWindowPlacement is logged (showCmd decode +
// rcNormalPosition) — the normal-rect memory across max/min/fullscreen.
// * Vetoable close: a "dirty" flag (key D toggles it interactively). On
// WM_CLOSE with dirty set: log close_requested veto=1, clear the flag,
// return 0 WITHOUT DefWindowProcW — Win32's first-class veto (not
// forwarding to DefWindowProc IS the refusal; no DestroyWindow happens).
// Second WM_CLOSE → DefWindowProcW → DestroyWindow → WM_DESTROY →
// PostQuitMessage. The close source is visible because the demo drives it
// through the real chain: WM_SYSCOMMAND SC_CLOSE (what the title-bar X
// sends) → DefWindowProc → WM_CLOSE.
// * WSI_AUTO_EXIT=1 runs the scripted tour: maximize → restore → minimize →
// restore → fullscreen on → fullscreen off → dirty + SC_CLOSE (vetoed) →
// SC_CLOSE (closes). Exit 0. Without it: M maximize toggle, N minimize,
// F fullscreen toggle, R restore, D dirty toggle, close via the X button.
//
// 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 (constant) int app.TICK_MS = 16TICK_MS = 16;
enum (constant) int app.TICKS_PER_STEP = 25TICKS_PER_STEP = 25; // ~400 ms settle time between scripted transitions
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.stepstep; // scripted-tour position
bool (field) bool app.Demo.dirtydirty; // vetoable-close flag
bool (field) bool app.Demo.closeFromSelfcloseFromSelf; // we sent the WM_SYSCOMMAND that produced WM_CLOSE
// Borderless-fullscreen idiom state:
bool (field) bool app.Demo.fullscreenfullscreen;
LONG_PTR (field) _error_ app.Demo.savedStylesavedStyle;
RECT (field) _error_ app.Demo.savedRectsavedRect;
}
__gshared (struct) app.DemoDemo _error_ app.gg;
// ---------------------------------------------------------------------------
// Backbuffer (scaffold-identical).
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);
logEvent("buffer_alloc size=%dx%d", w, h);
}
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;
// Red border band when dirty, so the veto state is visible interactively.
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);
if (g.g.dirtydirty && (x < 8 || y < 8 || x >= w - 8 || y >= h - 8))
row[x] = 0xcc2222;
}
}
}
// ---------------------------------------------------------------------------
// Decoders + placement probe.
const(char)* app.sizeKindsizeKind(WPARAM (parameter) WPARAM ww) nothrow @nogc
{
switch (w)
{
case SIZE_RESTORED: return "SIZE_RESTORED";
case SIZE_MINIMIZED: return "SIZE_MINIMIZED";
case SIZE_MAXIMIZED: return "SIZE_MAXIMIZED";
case SIZE_MAXSHOW: return "SIZE_MAXSHOW";
case SIZE_MAXHIDE: return "SIZE_MAXHIDE";
default: return "?";
}
}
const(char)* app.showCmdNameshowCmdName(UINT c) nothrow @nogc
{
switch (c)
{
case SW_SHOWNORMAL: return "SW_SHOWNORMAL";
case SW_SHOWMINIMIZED: return "SW_SHOWMINIMIZED";
case SW_SHOWMAXIMIZED: return "SW_SHOWMAXIMIZED";
default: return "other";
}
}
const(char)* app.activateKindactivateKind(WPARAM (parameter) WPARAM ww) nothrow @nogc
{
switch (LOWORD(w))
{
case WA_INACTIVE: return "WA_INACTIVE";
case WA_ACTIVE: return "WA_ACTIVE";
case WA_CLICKACTIVE: return "WA_CLICKACTIVE";
default: return "?";
}
}
// GetWindowPlacement: showCmd + the remembered normal rect — logged after
// every transition to prove the normal-rect memory survives max/min/fullscreen.
void app.logPlacementlogPlacement(HWND (parameter) HWND hwndhwnd, const(char)* when) nothrow
{
WINDOWPLACEMENT _error_ wpwp;
wp.length = WINDOWPLACEMENT.sizeof;
if (!GetWindowPlacement(hwnd, &wp))
return;
const _error_ rr = wp.rcNormalPosition;
logEvent("placement when=%s showCmd=%s normal_rect=%ld,%ld-%ldx%ld iconic=%d zoomed=%d",
when, showCmdName(wp.showCmd), r.left, r.top,
r.right - r.left, r.bottom - r.top,
IsIconic(hwnd) ? 1 : 0, IsZoomed(hwnd) ? 1 : 0);
}
// ---------------------------------------------------------------------------
// The borderless-fullscreen idiom. Win32 has no fullscreen STATE — this is
// the documented community idiom (Raymond Chen, "How do I switch a window
// between normal and fullscreen?"): save style+rect, strip the frame, size to
// the monitor, restore both on the way out.
void app.enterFullscreenenterFullscreen(HWND (parameter) HWND hwndhwnd) nothrow
{
if (g.g.fullscreenfullscreen)
return;
g.g.savedStylesavedStyle = GetWindowLongPtrW(hwnd, GWL_STYLE);
GetWindowRect(hwnd, &g.g.savedRectsavedRect);
MONITORINFO _error_ mimi;
mi.cbSize = MONITORINFO.sizeof;
GetMonitorInfoW(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &mi);
const _error_ rr = mi.rcMonitor;
logEvent("state_request kind=fullscreen_enter monitor=%ld,%ld-%ldx%ld saved_rect=%ld,%ld-%ldx%ld",
r.left, r.top, r.right - r.left, r.bottom - r.top,
g.g.savedRectsavedRect.left, g.g.savedRectsavedRect.top,
g.g.savedRectsavedRect.right - g.g.savedRectsavedRect.left, g.g.savedRectsavedRect.bottom - g.g.savedRectsavedRect.top);
SetWindowLongPtrW(hwnd, GWL_STYLE, g.g.savedStylesavedStyle & ~cast(LONG_PTR) WS_OVERLAPPEDWINDOW);
SetWindowPos(hwnd, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
g.g.fullscreenfullscreen = true;
logPlacement(hwnd, "fullscreen_enter");
}
void app.exitFullscreenexitFullscreen(HWND (parameter) HWND hwndhwnd) nothrow
{
if (!g.g.fullscreenfullscreen)
return;
logEvent("state_request kind=fullscreen_exit");
SetWindowLongPtrW(hwnd, GWL_STYLE, g.g.savedStylesavedStyle);
const _error_ rr = g.g.savedRectsavedRect;
SetWindowPos(hwnd, null, r.left, r.top, r.right - r.left, r.bottom - r.top,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
g.g.fullscreenfullscreen = false;
logPlacement(hwnd, "fullscreen_exit");
}
// ---------------------------------------------------------------------------
// Scripted tour (WSI_AUTO_EXIT=1), one step every TICKS_PER_STEP timer ticks.
void app.runSteprunStep(HWND (parameter) HWND hwndhwnd, int (parameter) int nn) nothrow
{
switch (n)
{
case 0:
logPlacement(hwnd, "initial");
break;
case 1:
logEvent("state_request kind=maximize api=ShowWindow(SW_MAXIMIZE)");
ShowWindow(hwnd, SW_MAXIMIZE);
logPlacement(hwnd, "after_maximize");
break;
case 2:
logEvent("state_request kind=restore api=ShowWindow(SW_RESTORE)");
ShowWindow(hwnd, SW_RESTORE);
logPlacement(hwnd, "after_restore");
break;
case 3:
logEvent("state_request kind=minimize api=ShowWindow(SW_MINIMIZE)");
ShowWindow(hwnd, SW_MINIMIZE);
logPlacement(hwnd, "after_minimize");
logEvent("focus_probe foreground=%p focus=%p self=%p",
GetForegroundWindow(), GetFocus(), hwnd);
break;
case 4:
logEvent("state_request kind=restore api=ShowWindow(SW_RESTORE)");
ShowWindow(hwnd, SW_RESTORE);
logPlacement(hwnd, "after_restore");
break;
case 5:
enterFullscreen(hwnd);
break;
case 6:
exitFullscreen(hwnd);
break;
case 7:
g.g.dirtydirty = true;
logEvent("dirty set=1");
// Drive the close through the real user chain: the title-bar X sends
// WM_SYSCOMMAND SC_CLOSE, which DefWindowProc turns into WM_CLOSE.
g.g.closeFromSelfcloseFromSelf = true;
logEvent("state_request kind=close api=WM_SYSCOMMAND(SC_CLOSE) attempt=1");
SendMessageW(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
break;
case 8:
g.g.closeFromSelfcloseFromSelf = true;
logEvent("state_request kind=close api=WM_SYSCOMMAND(SC_CLOSE) attempt=2");
SendMessageW(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
break;
default:
break;
}
}
// ---------------------------------------------------------------------------
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_SHOWWINDOW:
logEvent("msg name=WM_SHOWWINDOW shown=%d", cast(int) wParam);
goto default;
case WM_GETMINMAXINFO:
// Sent when the size/position is about to change — notably on
// maximize, letting the app override the maximized size/position.
auto _error_ mmimmi = cast(MINMAXINFO*) lParam;
logEvent("msg name=WM_GETMINMAXINFO maxSize=%ldx%ld maxPos=%ld,%ld",
mmi.ptMaxSize.mmi.ptMaxSize.xx, mmi.ptMaxSize.mmi.ptMaxSize.yy, mmi.ptMaxPosition.mmi.ptMaxPosition.xx, mmi.ptMaxPosition.mmi.ptMaxPosition.yy);
goto default;
case WM_WINDOWPOSCHANGING:
auto _error_ wpgwpg = cast(WINDOWPOS*) lParam;
logEvent("msg name=WM_WINDOWPOSCHANGING rect=%d,%d-%dx%d flags=0x%x",
wpg.wpg.xx, wpg.wpg.yy, wpg.cx, wpg.cy, wpg.flags);
goto default;
case WM_WINDOWPOSCHANGED:
auto _error_ wpdwpd = cast(WINDOWPOS*) lParam;
logEvent("msg name=WM_WINDOWPOSCHANGED rect=%d,%d-%dx%d flags=0x%x",
wpd.wpd.xx, wpd.wpd.yy, wpd.cx, wpd.cy, wpd.flags);
goto default; // DefWindowProc synthesizes WM_SIZE/WM_MOVE
case WM_SIZE:
const _error_ ww = cast(int)(lParam & 0xffff);
const _error_ hh = cast(int)((lParam >> 16) & 0xffff);
logEvent("state_changed via=WM_SIZE kind=%s size=%dx%d", sizeKind(wParam), w, h);
if (wParam == SIZE_MINIMIZED)
return 0;
if (w != g.g.widthwidth || h != g.g.heightheight)
createBackbuffer(w, h);
return 0;
case WM_ACTIVATE:
logEvent("focus state=%s reason=WM_ACTIVATE minimized=%d other=%p",
activateKind(wParam), HIWORD(wParam) ? 1 : 0, cast(void*) lParam);
goto default;
case WM_ACTIVATEAPP:
logEvent("msg name=WM_ACTIVATEAPP active=%d", cast(int) wParam);
goto default;
case WM_SETFOCUS:
logEvent("focus state=in reason=WM_SETFOCUS prev=%p", cast(void*) wParam);
return 0;
case WM_KILLFOCUS:
// wParam = the window RECEIVING focus (may be null) — the
// where-does-focus-go-on-minimize probe.
logEvent("focus state=out reason=WM_KILLFOCUS next=%p", cast(void*) wParam);
return 0;
case WM_SYSCOMMAND:
logEvent("msg name=WM_SYSCOMMAND cmd=0x%llx", cast(ulong)(wParam & 0xfff0));
goto default;
case WM_CLOSE:
if (g.g.dirtydirty)
{
// The first-class veto: return 0 WITHOUT calling DefWindowProcW.
// DefWindowProc's WM_CLOSE handling is what calls DestroyWindow;
// not forwarding IS the refusal — no further protocol required.
logEvent("close_requested veto=1 src=%s dirty=1",
g.g.closeFromSelfcloseFromSelf ? "self_syscommand".ptr : "external".ptr);
g.g.dirtydirty = false;
g.g.closeFromSelfcloseFromSelf = false;
return 0;
}
logEvent("close_requested veto=0 src=%s dirty=0",
g.g.closeFromSelfcloseFromSelf ? "self_syscommand".ptr : "external".ptr);
g.g.closeFromSelfcloseFromSelf = false;
goto default; // DefWindowProcW → DestroyWindow
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_KEYDOWN:
switch (wParam)
{
case 'M':
logEvent("state_request kind=%s api=ShowWindow", IsZoomed(hwnd) ? "restore".ptr
: "maximize".ptr);
ShowWindow(hwnd, IsZoomed(hwnd) ? SW_RESTORE : SW_MAXIMIZE);
logPlacement(hwnd, "after_key_m");
break;
case 'N':
logEvent("state_request kind=minimize api=ShowWindow(SW_MINIMIZE)");
ShowWindow(hwnd, SW_MINIMIZE);
break;
case 'R':
logEvent("state_request kind=restore api=ShowWindow(SW_RESTORE)");
ShowWindow(hwnd, SW_RESTORE);
logPlacement(hwnd, "after_key_r");
break;
case 'F':
if (g.g.fullscreenfullscreen)
exitFullscreen(hwnd);
else
enterFullscreen(hwnd);
break;
case 'D':
g.g.dirtydirty = !g.g.dirtydirty;
logEvent("dirty set=%d", g.g.dirtydirty ? 1 : 0);
InvalidateRect(hwnd, null, FALSE);
break;
default:
break;
}
return 0;
case WM_TIMER:
if (wParam != TIMER_ID)
return 0;
++g.g.ticksticks;
InvalidateRect(hwnd, null, FALSE);
if (g.g.autoExitautoExit && g.g.ticksticks % TICKS_PER_STEP == 0)
runStep(hwnd, g.g.stepstep++);
return 0;
case WM_DESTROY:
logEvent("msg name=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.ptr, buf.ptr, buf.length);
return n >= 1 && n < buf.length && buf[0] == '1';
}
int int D main()main()
{
instrumentInit("f14_state_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);
auto (local variable) wstring clsNameclsName = "wsi-f14-class"w;
WNDCLASSEXW (local variable) _error_ wcwc;
wc.cbSize = WNDCLASSEXW.sizeof;
wc.lpfnWndProc = &wndProc;
wc.hInstance = hInst;
wc.lpszClassName = clsName.ptr;
wc.hCursor = LoadCursorW(null, IDC_ARROW);
if (!RegisterClassExW(&wc))
return 1;
HWND (local variable) _error_ hwndhwnd = CreateWindowExW(0, clsName.ptr, "wsi-f14-window-state"w.ptr,
WS_OVERLAPPEDWINDOW, 60, 40, 480, 320, null, null, hInst, null);
if (hwnd is null)
{
logEvent("error what=CreateWindowExW code=%lu", GetLastError());
return 1;
}
logEvent("window_created hwnd=%p", hwnd);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
SetTimer(hwnd, 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;
}