glass_layout.cfg: ",noframe" per-line option strips a window's title bar

Append it to a window's line and that window becomes a bare WS_POPUP -- no
caption, no border, just the display and its buttons -- for a multi-monitor
wall where the chrome is only noise:

    Heat MFD=1920,0,657,539,noframe

Options are comma-separated after the four numbers and unknown ones are
ignored, so an older build reading a newer file loses the option but never the
line (the bindings.txt grammar rule, applied here).

TWO THINGS THAT WOULD HAVE MADE IT A TRAP, both handled:

 - A frameless window has NO TITLE BAR TO DRAG, which is the entire point of
   the sticky layout.  So a noframe window is dragged BY ITS SURFACE:
   WM_NCHITTEST returns HTCAPTION anywhere that is not a button, HTCLIENT over
   one.  Buttons stay clickable, and because Windows drives the move the drag
   still ends in WM_EXITSIZEMOVE -- so it still saves.
 - SaveLayout rewrites the WHOLE file, so it writes the flag back.  Without
   that, the first finished-drag after adding the option would have silently
   stripped it.

Ordering: the flag is read BEFORE frame sizing (a quiet LoadLayout pre-pass,
then the normal pass after ComputeLayout), because AdjustWindowRect -- and
therefore the ring placement -- depends on whether a window carries chrome.
The pre-pass is quiet so the restore still logs exactly once.

Verified live: two flagged windows came up with WS_CAPTION clear while the
other five kept it (read back with GetWindowLong(GWL_STYLE)); a click still
dispatched on the frameless radar (CLICK 'Secondary / Radar' addr=0x18); and a
WM_EXITSIZEMOVE save round-tripped both flags back into the rewritten file.
surround / exploded / layout=off / pod / dev all boot and simulate clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-29 10:57:00 -05:00
co-authored by Claude Opus 5
parent 4c7f6fd9b1
commit f5f4c6c198
4 changed files with 149 additions and 13 deletions
+28
View File
@@ -139,6 +139,34 @@ at exactly 777,333 after the re-snap fired). Save fires on `WM_EXITSIZEMOVE` (su
since it writes as soon as a drag finishes) with a teardown write as backstop. This is the native
analogue of TeslaRel410 `emulator/pod-launch`'s per-rig `--bridge-pos`/`--layout` placement args.
### `,noframe` — per-window chrome removal (2026-07-29) [T2 live]
Append it to a window's line and that window loses its title bar and border (`WS_POPUP`) — for a
multi-monitor wall where the chrome is just noise:
```
Heat MFD=1920,0,657,539,noframe
```
Hand-edited (the file is otherwise machine-written). Options are comma-separated after the
numbers and **unknown ones are ignored**, so an older build reading a newer file loses the option
but never the line — the `bindings.txt` grammar rule applied here.
Two consequences it has to carry, or the option would be a trap:
- **A frameless window has no title bar to drag** — which is the entire sticky-layout workflow.
So a `noframe` window is dragged **by its surface**: `WM_NCHITTEST` returns `HTCAPTION`
anywhere that is not a button and `HTCLIENT` over one, so buttons stay clickable and the drag
still ends in `WM_EXITSIZEMOVE` and still saves.
- **`SaveLayout` rewrites the whole file**, so it writes the flag back — otherwise the first save
after a drag would silently strip it.
The flag is read BEFORE frame sizing (a `LoadLayout(quiet)` pre-pass), because `AdjustWindowRect`
and therefore `ComputeLayout`'s ring placement depend on whether a window carries chrome.
Verified live: two flagged windows came up caption-less while the other five kept theirs, a click
still dispatched on the frameless radar (`CLICK 'Secondary / Radar' addr=0x18`), and a
finished-drag save round-tripped both flags back into the file.
**Verified 2026-07-26 [T2]:** `BT_RIOBANK_LOG=1` dumps every bank's rects;
`scratchpad/checkbank.py` reports the per-bank census and proves no address is SHADOWED (has a
point no earlier button covers); `scratchpad/clickbank.py` then posts a real click at every
+33
View File
@@ -764,3 +764,36 @@ per-rig `--bridge-pos`/`--layout` args.
- round-trip: relaunched `load` against the game-written cfg → after 12 s (past the re-snap) the
*Flight Controls* window was physically at (777,333), all 7 at their saved coords — the re-snap
guard held.
## 2026-07-29 — glass_layout.cfg gains a per-line ",noframe" option
Requested: strip a window's title bar from the layout file. Appending `,noframe` to a window's
line makes that window a bare `WS_POPUP` -- no caption, no border, nothing but the display and its
buttons -- for a multi-monitor wall where the chrome is only noise.
Heat MFD=1920,0,657,539,noframe
Options are comma-separated after the four numbers and unknown ones are IGNORED, so an older build
reading a newer file loses the option but never the line (the bindings.txt grammar rule applied
here). Hand-edited; the file is otherwise machine-written.
### The two things that would have made it a trap
1. **A frameless window has no title bar to drag** -- which is the entire point of the sticky
layout. So a noframe window is dragged BY ITS SURFACE: `WM_NCHITTEST` returns `HTCAPTION`
anywhere that is not a button and `HTCLIENT` over one. Buttons stay clickable, and because
Windows drives the move, the drag still ends in `WM_EXITSIZEMOVE` -- so it still SAVES.
2. **SaveLayout rewrites the whole file**, so it writes the flag back. Without that the first
finished-drag after adding the option would have silently stripped it.
### Ordering
The flag is read BEFORE frame sizing (`LoadLayout(quiet)` pre-pass, then the normal pass after
`ComputeLayout`): `AdjustWindowRect` -- and therefore the ring placement -- depends on whether a
window carries chrome. The pre-pass is quiet so the restore still logs exactly once.
### Verified live
- Two flagged windows (`Heat MFD`, `Secondary / Radar`) came up with `WS_CAPTION` CLEAR while the
other five kept it -- read back out of the live windows with `GetWindowLong(GWL_STYLE)`.
- A click still dispatched on the frameless radar: `[glasswin] CLICK 'Secondary / Radar' addr=0x18`.
- A finished-drag save (`WM_EXITSIZEMOVE`) round-tripped BOTH flags back into the rewritten file,
header comment and all.
- surround / exploded / layout=off / pod / dev: boot + simulate, zero faults.
+82 -13
View File
@@ -114,6 +114,7 @@ struct GWin
int frameW, frameH;
int wantX, wantY;
int restored; // wantX/wantY came from glass_layout.cfg -> don't re-snap
int noFrame; // ",noframe" in the cfg -> WS_POPUP, drag by the surface
};
static GWin gWins[8];
@@ -379,12 +380,38 @@ static void
// a later geometry change can't distort an old save. Windows carry WS_CAPTION
// in every mode, so they are always draggable -- the gate only controls whether
// a drag STICKS.
//
// PER-LINE OPTION ",noframe" (2026-07-29): append it to a window's line and
// that window loses its title bar and border (WS_POPUP) -- for a clean
// multi-monitor wall where the chrome is just noise. Hand-edited; the file
// is otherwise machine-written.
//
// Heat MFD=1920,0,657,539,noframe
//
// Two consequences it has to carry, or the option would be a trap:
// - a frameless window has NO TITLE BAR TO DRAG, which is how the whole
// sticky-layout workflow works. So a noframe window is draggable BY ITS
// SURFACE (WM_NCHITTEST -> HTCAPTION anywhere that is not a button); the
// buttons stay clickable, and the drag still ends in WM_EXITSIZEMOVE so it
// still saves.
// - SaveLayout rewrites the whole file, so it must WRITE THE FLAG BACK or the
// first save after a drag would silently strip it.
//###########################################################################
enum { LayoutOff = 0, LayoutLoad = 1, LayoutSave = 2 };
static const char *layoutFileName = "glass_layout.cfg";
// A framed window is a normal tool window; ",noframe" makes it a bare
// WS_POPUP -- no caption, no border, nothing but the display and its buttons.
static DWORD
GlassWindowStyle(const GWin &w)
{
return w.noFrame
? (DWORD)WS_POPUP
: (DWORD)(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX);
}
static int
GlassLayoutMode()
{
@@ -424,7 +451,7 @@ static GWin *
// computed spot. Restored windows are flagged so the WM_TIMER re-snap (which
// re-runs ComputeLayout once the main window appears) leaves them alone.
static void
LoadLayout()
LoadLayout(int quiet)
{
if (GlassLayoutMode() == LayoutOff)
return;
@@ -432,8 +459,9 @@ static void
FILE *f = fopen(layoutFileName, "rt");
if (f == NULL)
{
DEBUG_STREAM << "[glasswin] no " << layoutFileName
<< " yet (using computed placement)\n" << std::flush;
if (!quiet)
DEBUG_STREAM << "[glasswin] no " << layoutFileName
<< " yet (using computed placement)\n" << std::flush;
return;
}
@@ -459,17 +487,34 @@ static void
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
continue;
// Trailing options after the numbers, comma-separated. Unknown ones
// are ignored (so an older build reading a newer file just loses the
// option, never the line) -- the bindings.txt grammar rule.
int noframe = 0;
for (const char *opt = strchr(eq + 1, ','); opt != NULL; opt = strchr(opt + 1, ','))
{
const char *t = opt + 1;
while (*t == ' ' || *t == '\t') ++t;
if (_strnicmp(t, "noframe", 7) == 0)
{
noframe = 1;
break;
}
}
GWin *gw = FindGWinByTitle(s);
if (gw == NULL)
continue;
gw->wantX = x;
gw->wantY = y;
gw->noFrame = noframe;
gw->restored = 1;
++restored;
}
fclose(f);
DEBUG_STREAM << "[glasswin] restored " << restored << " window position(s) from "
<< layoutFileName << "\n" << std::flush;
if (!quiet)
DEBUG_STREAM << "[glasswin] restored " << restored << " window position(s) from "
<< layoutFileName << "\n" << std::flush;
}
// Write every window's current on-screen frame rect. Whole-file rewrite (it is
@@ -490,7 +535,10 @@ static void
}
fputs("# BT411 glass cockpit window layout (BT_GLASS_LAYOUT=save writes this\n"
"# on finished-drag/exit; =load restores it). <title>=<x>,<y>,<w>,<h>\n",
"# on finished-drag/exit; =load restores it). <title>=<x>,<y>,<w>,<h>\n"
"# Append ,noframe to a line to drop that window's title bar and border;\n"
"# a frameless window is dragged by its SURFACE instead (buttons still\n"
"# click). Hand-edit to add it -- saves preserve it.\n",
f);
int wrote = 0;
for (int i = 0; i < gWinCount; ++i)
@@ -501,9 +549,10 @@ static void
RECT r;
if (!GetWindowRect(gw.hwnd, &r))
continue;
fprintf(f, "%s=%ld,%ld,%ld,%ld\n", gw.title,
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", gw.title,
(long)r.left, (long)r.top,
(long)(r.right - r.left), (long)(r.bottom - r.top));
(long)(r.right - r.left), (long)(r.bottom - r.top),
gw.noFrame ? ",noframe" : ""); // keep the hand-added option
++wrote;
}
fclose(f);
@@ -793,6 +842,24 @@ static LRESULT CALLBACK
case WM_ERASEBKGND:
return 1; // PaintGlass fills the frame (double-buffered)
//
// ",noframe" windows have no title bar, so the SURFACE is the drag handle:
// report HTCAPTION anywhere that is not a button and Windows moves the
// window for us -- including the WM_EXITSIZEMOVE at the end, so a drag
// still sticks in save mode. Over a button we report HTCLIENT so the
// press reaches WM_LBUTTONDOWN as usual.
//
case WM_NCHITTEST:
if (w != NULL && w->noFrame)
{
POINT p;
p.x = (int)(short)LOWORD(lparam);
p.y = (int)(short)HIWORD(lparam);
ScreenToClient(window, &p);
return (HitTest(w, p.x, p.y) >= 0) ? HTCLIENT : HTCAPTION;
}
break;
case WM_TIMER:
if (wparam == RepaintTimerId)
{
@@ -926,18 +993,20 @@ void
wc.lpszClassName = L"BTGlassWin";
RegisterClassW(&wc);
DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
// The ",noframe" flag is per WINDOW and arrives with the layout file, so
// read that FIRST -- the frame size (and therefore ComputeLayout's ring
// placement) depends on which windows carry chrome.
LoadLayout(1); // quiet: this pass only needs the ",noframe" flags
// Frame sizes first (ComputeLayout positions by frame dims).
for (int i = 0; i < gWinCount; ++i)
{
RECT fr = { 0, 0, gWins[i].clientW, gWins[i].clientH };
AdjustWindowRect(&fr, style, FALSE);
AdjustWindowRect(&fr, GlassWindowStyle(gWins[i]), FALSE);
gWins[i].frameW = fr.right - fr.left;
gWins[i].frameH = fr.bottom - fr.top;
}
ComputeLayout();
LoadLayout(); // BT_GLASS_LAYOUT=load/save: restore saved positions over the ring
LoadLayout(0); // re-apply saved positions over the computed ring
for (int i = 0; i < gWinCount; ++i)
{
@@ -951,7 +1020,7 @@ void
// of the game's D3D swap chain, ending GDI-vs-D3D presentation strobing.
w.hwnd = CreateWindowExW(
WS_EX_TOPMOST | WS_EX_LAYERED,
L"BTGlassWin", wtitle, style,
L"BTGlassWin", wtitle, GlassWindowStyle(w),
w.wantX, w.wantY, w.frameW, w.frameH,
NULL, NULL, GetModuleHandleW(NULL), NULL);
if (w.hwnd == NULL)
+6
View File
@@ -297,6 +297,12 @@ static const char *kEnvironIniDefault =
"# load restore the saved layout but never change it (read-only)\n"
"# The positions live in glass_layout.cfg beside this file -- delete it to go\n"
"# back to the pod ring. Windows not in the file keep their computed spot.\n"
"#\n"
"# In that file you can also append ,noframe to a window's line to strip its\n"
"# title bar and border -- handy on a multi-monitor wall where the chrome is\n"
"# just noise. A frameless window is dragged by its SURFACE instead (the\n"
"# buttons still click), and saves keep the flag:\n"
"# Heat MFD=1920,0,657,539,noframe\n"
"#BT_GLASS_LAYOUT=save\n"
"\n"
"# Size of the secondary displays in the cockpit surround, as a percentage of\n"