#140 glass_layout.cfg lost every MFD line on a desktop teardown

Regression from d213c98 (the pod PadRIO/panel coupling fix).

BTGlassPanels_Destroy calls SaveLayout FIRST, unconditionally, before it
looks at whether any windows are left.  d213c98 added a SECOND caller
(~LBE4ControlsManager) alongside the existing one in ~PadRIO, so on the
desktop path both run: ~LBE4ControlsManager does `delete rioPointer`, which
fires ~PadRIO -> destroy #1 saves the live windows and zeroes gWinCount ->
destroy #2 then rewrites the whole file from an empty list.

Why only the MFDs vanished, which is the detail that identifies it: external
windows (plasma) already cached a last-known rect (gExtern[].haveLast) and
were written from the cache; the per-display glass windows had no cache and
were simply skipped once their HWND was gone.  Hence the reported signature,
"all the MFDs and secondary lines missing, but plasma was still there".

The pod was never affected -- no PadRIO there, so only one destroy, and it
runs BT_GLASS_LAYOUT=load off a frozen master regardless.

Two fixes, because the guard alone would leave the trap armed for the next
teardown-ordering change:
  1. glass windows get the same remembered-geometry cache the extern windows
     have, kept ACROSS teardown.  The file is now monotonic -- a save can
     update a line or add one, never drop one.
  2. the teardown save is guarded on there being windows to report.

Also corrects the comment at the L4CTRL call site, which claimed the second
call was "a no-op on the PadRIO path".  That claim is what made it look safe.

Verified (scratchpad/night13/layoutsave.sh -- the tester's round trip, not a
single launch, since the report was "saved fine, reset on relaunch"):
  run 1  one "saved 8 window position(s)" line (was two, the second wiping)
         cfg holds all 7 glass windows + plasma
  run 2  "restored 7 window position(s)", cfg byte-identical after the trip

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ
This commit is contained in:
Joe DiPrima
2026-08-07 01:03:32 -05:00
co-authored by Claude Opus 5
parent 6a96fb6420
commit c04bec0a52
3 changed files with 146 additions and 14 deletions
+63 -9
View File
@@ -916,9 +916,50 @@ static void
<< layoutFileName << "\n" << std::flush;
}
// Write every window's current on-screen frame rect. Whole-file rewrite (it is
// tiny), so partial/hard kills never leave a half-written file for long. Called
// on finished-drag and on teardown in save mode.
// LAST-KNOWN GEOMETRY, kept ACROSS teardown (2026-08-07, #140).
//
// SaveLayout rewrites the whole file, so it can only be as complete as what it
// can see -- and it could only see LIVE windows. Destroy() nulls every hwnd and
// zeroes gWinCount, so any save that ran after a teardown wrote a file with
// every MFD and radar line MISSING. The plasma window survived that because
// external windows already cached a last-known rect (gExtern[].haveLast); the
// per-display windows had no such cache, which is exactly the reported
// signature: "all the MFDs and secondary lines missing, but plasma was still
// there". Give the glass windows the same guarantee, so the file is monotonic
// -- a save can update a line or add one, never drop one.
struct SavedGeom
{
char title[64];
RECT r;
int noFrame;
};
static SavedGeom gLastGeom[16];
static int gLastGeomCount = 0;
static void
RememberGeom(const char *title, const RECT &r, int noFrame)
{
if (title == NULL || title[0] == '\0')
return;
for (int i = 0; i < gLastGeomCount; ++i)
if (strcmp(gLastGeom[i].title, title) == 0)
{
gLastGeom[i].r = r; gLastGeom[i].noFrame = noFrame;
return;
}
if (gLastGeomCount >= (int)(sizeof(gLastGeom) / sizeof(gLastGeom[0])))
return;
SavedGeom &g = gLastGeom[gLastGeomCount++];
strncpy(g.title, title, sizeof(g.title) - 1);
g.title[sizeof(g.title) - 1] = '\0';
g.r = r; g.noFrame = noFrame;
}
// Write every window's frame rect. Whole-file rewrite (it is tiny), so
// partial/hard kills never leave a half-written file for long. Called on
// finished-drag and on teardown in save mode. Live windows refresh the
// remembered geometry first; the FILE is then written from the remembered set,
// so a window that has already been torn down keeps its line.
static void
SaveLayout()
{
@@ -945,6 +986,7 @@ static void
"# this list too -- it can be dragged, remembered and set ,noframe.\n",
f);
int wrote = 0;
// 1. refresh the remembered geometry from whatever is currently alive
for (int i = 0; i < gWinCount; ++i)
{
GWin &gw = gWins[i];
@@ -953,10 +995,16 @@ static void
RECT r;
if (!GetWindowRect(gw.hwnd, &r))
continue;
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),
gw.noFrame ? ",noframe" : ""); // keep the hand-added option
RememberGeom(gw.title, r, gw.noFrame); // keep the hand-added option
}
// 2. write the remembered set -- including windows already torn down
for (int i = 0; i < gLastGeomCount; ++i)
{
const SavedGeom &g = gLastGeom[i];
fprintf(f, "%s=%ld,%ld,%ld,%ld%s\n", g.title,
(long)g.r.left, (long)g.r.top,
(long)(g.r.right - g.r.left), (long)(g.r.bottom - g.r.top),
g.noFrame ? ",noframe" : "");
++wrote;
}
// External windows (the plasma window) ride the same file. Cache the last
@@ -1741,8 +1789,14 @@ void
void
BTGlassPanels_Destroy()
{
SaveLayout(); // backstop for a clean teardown (WM_EXITSIZEMOVE already
// caught every finished drag); no-op unless mode==save
// Backstop for a clean teardown (WM_EXITSIZEMOVE already caught every
// finished drag); no-op unless mode==save. GUARDED on there being windows:
// this function has TWO callers (~PadRIO and ~LBE4ControlsManager), so on
// the desktop path it runs twice, and the second pass has nothing live to
// report. The remembered-geometry cache above already makes that harmless,
// but there is no reason to rewrite the file to say the same thing.
if (gWinCount > 0)
SaveLayout();
for (int i = 0; i < gWinCount; ++i)
{