pod: freeze the ALPHA-MR rig in environ.ini (BT_FIT, L4PLASMA=NONE)
The cart's display config was carried by a launcher .bat, so it only came up
right if the game was started one particular way. Move it into the file the
engine already reads before anything touches the environment.
content/environ.ini <- scratchpad/pod/podprofile.ini, merged idempotently
between markers by scratchpad/pod/mergeprofile.ps1
Two gates were missing for that to be enough:
BT_FIT=1 the env spelling of -fit, so the borderless main view does not
depend on one launcher's command line (shortcut, scheduled
task and autostart all have to produce the same rig)
L4PLASMA= NONE / OFF / 0 -> no marquee at all. Leaving it unset does
NOT work: the GLASS profile force-defaults it to SCREEN, which
drops a desktop plasma window on the cab's glass. The boot
banner now reports the live state instead of always claiming
"plasma window".
Also lands the bring-up engine work this depended on: monitor:<name|index>
layout binding (device-bound, not pixel-bound -- desktop rects move when a
display re-enumerates), ",bare" implying frameless, rotation-aware radar
surface sizing, BT_GAUGE_SEC_ROT accepting 0-3 (it silently forced 3 for
anything but 1), 180-degree ExpandPlaneToBGRA, and the BT_POD_CHANMAP /
BT_POD_IDENT / BT_POD_CHANTEST identification gates.
Verified on the cart, build 4.11.813, launcher carrying none of it:
[boot] environ.ini: 9 setting(s) applied
[boot] platform profile: GLASS (PadRIO; plasma off [L4PLASMA])
[cockpit] -fit: borderless 800x600
[glasswin] radar rotation 0 (none)
... all three surfaces on their intended \.\DISPLAYn
No [plasmawin] line in an otherwise-logging run = the ctor never ran.
KB: pod-hardware.md gains the ALPHA-MR section (mapping, the two wiring
deviations, the frozen profile, the session-0 remote-work traps) and its RGB
SPLIT "OPEN: which way is the cart wired" is now SETTLED -- it is splitter
wired, the composite is what lit it. glass-cockpit.md documents monitor:
binding, BT_FIT and L4PLASMA=NONE.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw7No5wLTpkaUgA3ANbtZZ
This commit is contained in:
co-authored by
Claude Opus 5
parent
bef051e837
commit
654277bb7b
+382
-12
@@ -123,6 +123,10 @@ struct GWin
|
||||
const char *groupPort[3];
|
||||
const char *groupAlt[3]; // the Eng<n> twin, or NULL
|
||||
int groupCount;
|
||||
|
||||
char monitorName[40]; // the PHYSICAL monitor this window landed on
|
||||
// (\.\DISPLAYn), stamped at creation; shown
|
||||
// by BT_POD_IDENT so the cab can be mapped.
|
||||
};
|
||||
|
||||
static GWin gWins[8];
|
||||
@@ -253,17 +257,26 @@ static void
|
||||
// (0x15 sits on the left column -- Cyd listed 0x10-0x14; move it if wanted.)
|
||||
static const int bottomAddrs[4] = { 0x16, 0x17, 0x1F, 0x1E };
|
||||
|
||||
// The SURFACE SIZE follows the rotation: a quarter turn (1 or 3) transposes
|
||||
// the 640x480 source to 480x640 portrait -- the pod's rotated CRT -- while
|
||||
// 0 (none) and 2 (180) keep it LANDSCAPE. Sizing this independently of the
|
||||
// rotation squashed the picture into the wrong aspect on a landscape
|
||||
// replacement panel (crash cart, 2026-08-06).
|
||||
const int quarterTurn = (gRadarRot == 1 || gRadarRot == 3);
|
||||
const int surfW = quarterTurn ? RadarSurfW : RadarSurfH; // 480 : 640
|
||||
const int surfH = quarterTurn ? RadarSurfH : RadarSurfW; // 640 : 480
|
||||
|
||||
BTRioBankMetrics metrics;
|
||||
BTRioBankMetricsFor(RadarSurfW, RadarSurfH, &metrics);
|
||||
BTRioBankMetricsFor(surfW, surfH, &metrics);
|
||||
|
||||
BTRioBank bank;
|
||||
BTRioBankLayout(BTRioBankRadar, 0, 0, RadarSurfW, RadarSurfH,
|
||||
BTRioBankLayout(BTRioBankRadar, 0, 0, surfW, surfH,
|
||||
0x10, 0x18, &metrics, bottomAddrs, &bank);
|
||||
|
||||
BTRioBankDump("Secondary / Radar", &bank);
|
||||
int dx = -bank.boundsX, dy = -bank.boundsY;
|
||||
AdoptBank(w, bank, ClrYellow, dx, dy);
|
||||
SetRect(&w.surfaceRect, dx, dy, dx + RadarSurfW, dy + RadarSurfH);
|
||||
SetRect(&w.surfaceRect, dx, dy, dx + surfW, dy + surfH);
|
||||
w.clientW = bank.boundsW;
|
||||
w.clientH = bank.boundsH;
|
||||
}
|
||||
@@ -426,6 +439,116 @@ static const char *layoutFileName = "glass_layout.cfg";
|
||||
// The Flight Controls window (no surface at all) is not created.
|
||||
// BT_POD_SURFACES=1 -> every display window goes bare
|
||||
// glass_layout.cfg "<title>=x,y,bare" -> just that one (mixed rigs)
|
||||
// BT_POD_CHANMAP -- per-cart channel override, e.g.
|
||||
// BT_POD_CHANMAP=Comm=blue,Heat=red
|
||||
// The 1995 config authors which colour line each surface rides (L4GAUGE.CFG),
|
||||
// but a rebuilt cab can have its RGB splitter wired to different panels than
|
||||
// the original -- on Nick's cart the coolant (Heat) and hot-box (Comm) screens
|
||||
// came out swapped, which is a WIRING difference, not a software bug. Rather
|
||||
// than edit authentic config, override the channel here, per rig.
|
||||
// Returns 0/1/2 (red/green/blue) for a named port, or -1 if unmapped.
|
||||
static int
|
||||
PodChannelOverride(const char *portName)
|
||||
{
|
||||
static int loaded = 0;
|
||||
static char names[8][20];
|
||||
static int chans[8];
|
||||
static int count = 0;
|
||||
if (!loaded)
|
||||
{
|
||||
loaded = 1;
|
||||
const char *e = getenv("BT_POD_CHANMAP");
|
||||
while (e != NULL && *e && count < 8)
|
||||
{
|
||||
while (*e == ' ' || *e == ',') ++e;
|
||||
const char *eq = strchr(e, '=');
|
||||
if (eq == NULL) break;
|
||||
int n = 0;
|
||||
for (const char *p = e; p < eq && n < 19; ++p) names[count][n++] = *p;
|
||||
names[count][n] = 0;
|
||||
const char *v = eq + 1;
|
||||
int ch = -1;
|
||||
if (_strnicmp(v, "red", 3) == 0) ch = L4GraphicsPort::RedChannel;
|
||||
else if (_strnicmp(v, "green", 5) == 0) ch = L4GraphicsPort::GreenChannel;
|
||||
else if (_strnicmp(v, "blue", 4) == 0) ch = L4GraphicsPort::BlueChannel;
|
||||
if (ch >= 0)
|
||||
{
|
||||
chans[count] = ch;
|
||||
DEBUG_STREAM << "[glasswin] channel override: " << names[count]
|
||||
<< " -> " << (ch == L4GraphicsPort::RedChannel ? "red"
|
||||
: ch == L4GraphicsPort::GreenChannel ? "green" : "blue")
|
||||
<< std::endl << std::flush;
|
||||
++count;
|
||||
}
|
||||
const char *nx = strchr(v, ',');
|
||||
if (nx == NULL) break;
|
||||
e = nx + 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; ++i)
|
||||
if (portName != NULL && _stricmp(names[i], portName) == 0)
|
||||
return chans[i];
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The channel a surface actually rides on THIS rig: the override if given,
|
||||
// else what the pod config authored.
|
||||
static int
|
||||
ResolvedChannelOf(const char *portName, L4GraphicsPort *port)
|
||||
{
|
||||
int ov = PodChannelOverride(portName);
|
||||
if (ov >= 0)
|
||||
return ov;
|
||||
return (port != NULL) ? (port->GetEnableID() & 0x7F) : -1;
|
||||
}
|
||||
|
||||
// BT_POD_IDENT=1 -- draw each surface's identity ON the glass (title, monitor,
|
||||
// and in RGB mode the channel->surface map). The only reliable way to learn
|
||||
// which physical cab panel a Windows display drives.
|
||||
static int
|
||||
PodIdentMode()
|
||||
{
|
||||
static int m = -1;
|
||||
if (m < 0)
|
||||
{
|
||||
const char *e = getenv("BT_POD_IDENT");
|
||||
m = (e != NULL && e[0] != 0 && e[0] != '0') ? 1 : 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// BT_POD_CHANTEST=<seconds> -- CHANNEL WALK, the instrument for mapping
|
||||
// splitter-wired glass. On a cab, three mono CRTs share one VGA port through
|
||||
// its R/G/B lines, so "which panel is which" cannot be read off Windows: the
|
||||
// desktop sees ONE display for three monitors. This drives ONE channel at a
|
||||
// time -- all-red, then all-green, then all-blue, N seconds each, with the
|
||||
// channel name drawn huge -- so the answer is simply: watch which panel lights
|
||||
// up, and when. Readable even over a rough livestream.
|
||||
// Returns the dwell in seconds (0 = off).
|
||||
static int
|
||||
PodChanTestSeconds()
|
||||
{
|
||||
static int s = -1;
|
||||
if (s < 0)
|
||||
{
|
||||
const char *e = getenv("BT_POD_CHANTEST");
|
||||
s = (e != NULL && e[0] != 0) ? atoi(e) : 0;
|
||||
if (s < 0) s = 0;
|
||||
if (e != NULL && e[0] != 0 && s == 0) s = 3; // "1"/"on" -> 3s default
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// Which channel is lit right now (0=red 1=green 2=blue), from the wall clock.
|
||||
static int
|
||||
PodChanTestPhase()
|
||||
{
|
||||
int dwell = PodChanTestSeconds();
|
||||
if (dwell <= 0)
|
||||
return -1;
|
||||
return (int)((GetTickCount() / (DWORD)(dwell * 1000)) % 3);
|
||||
}
|
||||
|
||||
// BT_POD_RGB=1 -- the pod's REAL video wiring (see pod-hardware.md §THE RGB
|
||||
// SPLIT): one VGA port per window, its R/G/B analog lines split to three
|
||||
// monochrome MFD monitors. Implies pod surface mode.
|
||||
@@ -609,6 +732,73 @@ void
|
||||
}
|
||||
|
||||
// Restore saved positions over the just-computed pod-faithful defaults. Called
|
||||
//---------------------------------------------------------------------------
|
||||
// MONITOR BINDING (2026-08-06) -- "monitor:<name-or-index>" instead of x,y.
|
||||
//
|
||||
// Absolute desktop coordinates do not survive a reboot on a pod: Windows
|
||||
// renumbers the virtual desktop whenever adapters enumerate differently (a USB
|
||||
// display adapter is especially prone), so a layout pinned to 800,122 can land
|
||||
// on the wrong glass -- or off-screen -- after a power cycle. Binding to the
|
||||
// MONITOR instead and resolving its rect at startup is stable across boots.
|
||||
// Heat MFD=monitor:\.\DISPLAY4,bare (device name -- what the receipts print)
|
||||
// Heat MFD=monitor:2,bare (index into the enumeration order)
|
||||
// The surface is centred on that monitor; an exact-size panel fills it.
|
||||
//---------------------------------------------------------------------------
|
||||
struct MonScan { int index; const char *want; int wantIndex; RECT rect; int found; };
|
||||
|
||||
static BOOL CALLBACK
|
||||
MonScanProc(HMONITOR mon, HDC, LPRECT, LPARAM param)
|
||||
{
|
||||
MonScan *sc = (MonScan *)param;
|
||||
MONITORINFOEXA mi;
|
||||
memset(&mi, 0, sizeof(mi));
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfoA(mon, (MONITORINFO *)&mi))
|
||||
{
|
||||
int hit = 0;
|
||||
if (sc->want != NULL)
|
||||
{
|
||||
// Match the full device name OR just its tail, so a hand-written
|
||||
// cfg can say `monitor:DISPLAY4` and skip the \\.\ prefix entirely
|
||||
// (backslashes in a config file are a foot-gun -- every shell,
|
||||
// editor and copy-paste path mangles them differently).
|
||||
size_t dl = strlen(mi.szDevice), wl = strlen(sc->want);
|
||||
if (_stricmp(mi.szDevice, sc->want) == 0)
|
||||
hit = 1;
|
||||
else if (wl > 0 && wl <= dl
|
||||
&& _stricmp(mi.szDevice + (dl - wl), sc->want) == 0)
|
||||
hit = 1;
|
||||
}
|
||||
if (!hit && sc->wantIndex >= 0 && sc->index == sc->wantIndex)
|
||||
hit = 1;
|
||||
if (hit && !sc->found)
|
||||
{
|
||||
sc->rect = mi.rcMonitor;
|
||||
sc->found = 1;
|
||||
}
|
||||
}
|
||||
++sc->index;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Resolve "monitor:<spec>" to a rect. Returns 1 on success.
|
||||
static int
|
||||
ResolveMonitorSpec(const char *spec, RECT *out)
|
||||
{
|
||||
while (*spec == ' ' || *spec == ' ') ++spec;
|
||||
MonScan sc;
|
||||
memset(&sc, 0, sizeof(sc));
|
||||
sc.wantIndex = -1;
|
||||
if (spec[0] >= '0' && spec[0] <= '9')
|
||||
sc.wantIndex = atoi(spec);
|
||||
else
|
||||
sc.want = spec;
|
||||
EnumDisplayMonitors(NULL, NULL, MonScanProc, (LPARAM)&sc);
|
||||
if (sc.found)
|
||||
*out = sc.rect;
|
||||
return sc.found;
|
||||
}
|
||||
|
||||
// from Create AFTER ComputeLayout, so any window not named in the file keeps its
|
||||
// computed spot. Restored windows are flagged so the WM_TIMER re-snap (which
|
||||
// re-runs ComputeLayout once the main window appears) leaves them alone.
|
||||
@@ -646,7 +836,34 @@ static void
|
||||
*end = '\0';
|
||||
|
||||
int x = 0, y = 0, w = 0, h = 0;
|
||||
if (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
|
||||
int haveXY = (sscanf(eq + 1, "%d,%d,%d,%d", &x, &y, &w, &h) >= 2);
|
||||
int viaMonitor = 0;
|
||||
if (!haveXY)
|
||||
{
|
||||
// "monitor:<name|index>" -- boot-stable binding (see the resolver).
|
||||
const char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') ++v;
|
||||
if (_strnicmp(v, "monitor:", 8) == 0 || _strnicmp(v, "mon:", 4) == 0)
|
||||
{
|
||||
char spec[64];
|
||||
const char *p2 = strchr(v, ':') + 1;
|
||||
int n2 = 0;
|
||||
while (*p2 && *p2 != ',' && n2 < (int)sizeof(spec) - 1) spec[n2++] = *p2++;
|
||||
spec[n2] = 0;
|
||||
RECT mr;
|
||||
if (ResolveMonitorSpec(spec, &mr))
|
||||
{
|
||||
x = mr.left; y = mr.top;
|
||||
w = mr.right - mr.left; h = mr.bottom - mr.top;
|
||||
haveXY = 1;
|
||||
viaMonitor = 1;
|
||||
}
|
||||
else
|
||||
DEBUG_STREAM << "[glasswin] monitor '" << spec
|
||||
<< "' not found -- leaving computed placement\n" << std::flush;
|
||||
}
|
||||
}
|
||||
if (!haveXY)
|
||||
continue;
|
||||
|
||||
// Trailing options after the numbers, comma-separated. Unknown ones
|
||||
@@ -666,11 +883,30 @@ static void
|
||||
GWin *gw = FindGWinByTitle(s);
|
||||
if (gw == NULL)
|
||||
continue;
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
gw->noFrame = noframe;
|
||||
// "bare" is a pod panel: it always implies frameless. (Without this a
|
||||
// cfg line saying only ",bare" silently re-framed a window that pod
|
||||
// surface mode had already cropped -- caught on the bench 2026-08-06.)
|
||||
gw->noFrame = (noframe || bare) ? 1 : 0;
|
||||
if (bare && gw->buttonCount > 0)
|
||||
MakeBareSurface(*gw); // per-window pod panel (mixed rigs)
|
||||
if (viaMonitor)
|
||||
{
|
||||
// x,y,w,h is the MONITOR rect -- centre this surface on it (an
|
||||
// exact-size pod panel fills it edge to edge).
|
||||
int sw = gw->clientW > 0 ? gw->clientW : (gw->surfaceRect.right - gw->surfaceRect.left);
|
||||
int sh = gw->clientH > 0 ? gw->clientH : (gw->surfaceRect.bottom - gw->surfaceRect.top);
|
||||
gw->wantX = x + ((w - sw) / 2);
|
||||
gw->wantY = y + ((h - sh) / 2);
|
||||
DEBUG_STREAM << "[glasswin] '" << gw->title
|
||||
<< "' bound to monitor " << x << "," << y << " " << w << "x" << h
|
||||
<< " -> window at " << gw->wantX << "," << gw->wantY
|
||||
<< std::endl << std::flush;
|
||||
}
|
||||
else
|
||||
{
|
||||
gw->wantX = x;
|
||||
gw->wantY = y;
|
||||
}
|
||||
gw->restored = 1;
|
||||
++restored;
|
||||
}
|
||||
@@ -841,6 +1077,19 @@ static void
|
||||
mask = 0xFF; // index the low (palette) byte
|
||||
palId = SVGA16::SecondaryPalette;
|
||||
}
|
||||
// RGB mode: the base surface rides a CHANNEL, so its tint is that pure
|
||||
// primary -- resolved live (override first), never the build-time guess.
|
||||
if (w->groupCount > 0 && tint >= 0)
|
||||
{
|
||||
switch (ResolvedChannelOf(w->portPrimary, port))
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: tint = (int)0x00FF0000; break;
|
||||
case L4GraphicsPort::GreenChannel: tint = (int)0x0000FF00; break;
|
||||
case L4GraphicsPort::BlueChannel: tint = (int)0x000000FF; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
int ow = 0, oh = 0;
|
||||
svga->ExpandPlaneToBGRA(mask, palId, tint, w->rotate, gStage, &ow, &oh);
|
||||
|
||||
@@ -880,9 +1129,9 @@ static void
|
||||
if (ga != NULL && ga->GetEnableID() != L4GraphicsPort::BlankColor)
|
||||
gp = ga;
|
||||
}
|
||||
const int ch = gp->GetEnableID();
|
||||
const int ch = ResolvedChannelOf(w->groupPort[gi], gp);
|
||||
unsigned long chMask;
|
||||
switch (ch & 0x7F)
|
||||
switch (ch)
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: chMask = 0x00FF0000ul; break;
|
||||
case L4GraphicsPort::GreenChannel: chMask = 0x0000FF00ul; break;
|
||||
@@ -1069,6 +1318,113 @@ static void
|
||||
// (NO full-face flash overlay here either -- same wrongness as the
|
||||
// L4VB16 one, removed 2026-08-03: the buttons are big rects tucked
|
||||
// under the surface by design, and the protruding edge is the lamp.)
|
||||
|
||||
// ---- CHANNEL WALK (BT_POD_CHANTEST) ---------------------------------
|
||||
// Overwrite the whole surface with ONE channel at full intensity, so
|
||||
// exactly one monitor of a splitter-fed trio lights. Cycles R->G->B.
|
||||
{
|
||||
const int phase = PodChanTestPhase();
|
||||
if (phase >= 0)
|
||||
{
|
||||
static const COLORREF chColor[3] =
|
||||
{ RGB(255, 0, 0), RGB(0, 255, 0), RGB(0, 0, 255) };
|
||||
static const char *chName[3] = { "RED", "GREEN", "BLUE" };
|
||||
HBRUSH fill = CreateSolidBrush(chColor[phase]);
|
||||
FillRect(dc, &w->surfaceRect, fill);
|
||||
DeleteObject(fill);
|
||||
|
||||
// Name the channel AND what the pod authors onto it here, so a
|
||||
// lit panel identifies itself completely: "GREEN = Mfd2".
|
||||
const char *who = "-";
|
||||
GaugeRenderer *cgr = BTResolveGaugeRenderer();
|
||||
if (cgr != NULL)
|
||||
{
|
||||
const char *cand[4]; int nc = 0;
|
||||
cand[nc++] = w->portPrimary;
|
||||
for (int gi = 0; gi < w->groupCount && nc < 4; ++gi)
|
||||
cand[nc++] = w->groupPort[gi];
|
||||
for (int i = 0; i < nc; ++i)
|
||||
{
|
||||
L4GraphicsPort *pp =
|
||||
static_cast<L4GraphicsPort*>(cgr->GetGraphicsPort(cand[i]));
|
||||
if (pp != NULL && (pp->GetEnableID() & 0x7F) == phase)
|
||||
{ who = cand[i]; break; }
|
||||
}
|
||||
}
|
||||
char big[96];
|
||||
sprintf(big, "%s %s = %s", w->monitorName, chName[phase], who);
|
||||
RECT br = w->surfaceRect;
|
||||
br.top += (br.bottom - br.top) / 3;
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
DrawLabelText(dc, big, titleFont, &br, RGB(0, 0, 0),
|
||||
DT_CENTER | DT_SINGLELINE | DT_VCENTER);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- PANEL IDENTIFY (BT_POD_IDENT=1) --------------------------------
|
||||
// Pod bring-up: nobody can tell which PHYSICAL panel a Windows display
|
||||
// is, and desktop coordinate order says nothing about where a panel
|
||||
// sits in the cab -- guessing that mapping is exactly how it came out
|
||||
// wrong. So label each surface ON ITS OWN GLASS with what it is, which
|
||||
// monitor it came from, and (in RGB mode) the channel->surface map.
|
||||
// Walk the pod, read the panels, write the layout from what you SAW.
|
||||
if (PodIdentMode())
|
||||
{
|
||||
char line1[96], line2[160];
|
||||
sprintf(line1, "%s", w->title);
|
||||
if (w->groupCount > 0)
|
||||
{
|
||||
// name the channels so a splitter-fed trio is self-describing
|
||||
char parts[128];
|
||||
parts[0] = 0;
|
||||
GaugeRenderer *igr = BTResolveGaugeRenderer();
|
||||
const char *names[4]; int chans[4]; int nn = 0;
|
||||
names[nn] = w->portPrimary; chans[nn] = -1; ++nn;
|
||||
for (int gi = 0; gi < w->groupCount && nn < 4; ++gi)
|
||||
{ names[nn] = w->groupPort[gi]; chans[nn] = -1; ++nn; }
|
||||
for (int i = 0; i < nn; ++i)
|
||||
{
|
||||
const char *chName = "?";
|
||||
if (igr != NULL)
|
||||
{
|
||||
L4GraphicsPort *pp =
|
||||
static_cast<L4GraphicsPort*>(igr->GetGraphicsPort(names[i]));
|
||||
if (pp != NULL)
|
||||
switch (pp->GetEnableID() & 0x7F)
|
||||
{
|
||||
case L4GraphicsPort::RedChannel: chName = "RED"; break;
|
||||
case L4GraphicsPort::GreenChannel: chName = "GREEN"; break;
|
||||
case L4GraphicsPort::BlueChannel: chName = "BLUE"; break;
|
||||
case L4GraphicsPort::AllChannels: chName = "RGB"; break;
|
||||
default: chName = "blank"; break;
|
||||
}
|
||||
}
|
||||
char one[48];
|
||||
sprintf(one, "%s%s=%s", (i ? " " : ""), chName, names[i]);
|
||||
if (strlen(parts) + strlen(one) < sizeof(parts) - 1)
|
||||
strcat(parts, one);
|
||||
}
|
||||
sprintf(line2, "%s %s", w->monitorName, parts);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(line2, "%s surface=%s", w->monitorName,
|
||||
w->portPrimary ? w->portPrimary : "-");
|
||||
}
|
||||
|
||||
RECT lr = w->surfaceRect;
|
||||
lr.bottom = lr.top + 64;
|
||||
HBRUSH back = CreateSolidBrush(RGB(0, 0, 0));
|
||||
FillRect(dc, &lr, back);
|
||||
DeleteObject(back);
|
||||
SetBkMode(dc, TRANSPARENT);
|
||||
RECT t1 = lr; t1.top += 4; t1.bottom = t1.top + 30;
|
||||
DrawLabelText(dc, line1, titleFont, &t1, RGB(255, 255, 0),
|
||||
DT_LEFT | DT_SINGLELINE | DT_VCENTER);
|
||||
RECT t2 = lr; t2.top += 32; t2.bottom = t2.top + 28;
|
||||
DrawLabelText(dc, line2, buttonFont, &t2, RGB(0, 255, 255),
|
||||
DT_LEFT | DT_SINGLELINE | DT_VCENTER);
|
||||
}
|
||||
}
|
||||
|
||||
BitBlt(winDC, 0, 0, client.right - client.left, client.bottom - client.top,
|
||||
@@ -1205,11 +1561,23 @@ void
|
||||
if (gWinCount != 0)
|
||||
return; // already up
|
||||
|
||||
// BT_GAUGE_SEC_ROT: the portrait-CRT unrotation direction (shared with the
|
||||
// D3D dev path; default 3 = CW = user-verified upright).
|
||||
// BT_GAUGE_SEC_ROT: how far to turn the secondary/radar surface.
|
||||
// 0 = none, 1 = 90 CCW, 2 = 180, 3 = 90 CW (default -- the pod's
|
||||
// portrait CRT, user-verified upright).
|
||||
// 0 and 2 keep the picture LANDSCAPE, which a modern replacement panel
|
||||
// needs. (Until 2026-08-06 this read only '1' and forced everything else
|
||||
// to 3, so BT_GAUGE_SEC_ROT=2 was silently ignored -- caught on the cart
|
||||
// when a 180 request kept coming back portrait.)
|
||||
{
|
||||
const char *rv = getenv("BT_GAUGE_SEC_ROT");
|
||||
gRadarRot = (rv != NULL && rv[0] == '1') ? 1 : 3;
|
||||
if (rv != NULL && rv[0] >= '0' && rv[0] <= '3')
|
||||
gRadarRot = rv[0] - '0';
|
||||
else
|
||||
gRadarRot = 3;
|
||||
DEBUG_STREAM << "[glasswin] radar rotation " << gRadarRot
|
||||
<< (gRadarRot == 3 ? " (90 CW)" : gRadarRot == 1 ? " (90 CCW)"
|
||||
: gRadarRot == 2 ? " (180)" : " (none)")
|
||||
<< std::endl << std::flush;
|
||||
}
|
||||
|
||||
memset(latched, 0, sizeof(latched));
|
||||
@@ -1348,6 +1716,8 @@ void
|
||||
if (mon != NULL && GetMonitorInfoA(mon, (MONITORINFO *)&mi))
|
||||
{
|
||||
devName = mi.szDevice;
|
||||
strncpy(w.monitorName, mi.szDevice, sizeof(w.monitorName) - 1);
|
||||
w.monitorName[sizeof(w.monitorName) - 1] = 0;
|
||||
mx = mi.rcMonitor.left; my = mi.rcMonitor.top;
|
||||
mw = mi.rcMonitor.right - mi.rcMonitor.left;
|
||||
mh = mi.rcMonitor.bottom - mi.rcMonitor.top;
|
||||
|
||||
Reference in New Issue
Block a user