Files
TeslaRel410/emulator/pod-launch/Focus.cs
T
CydandClaude Opus 4.8 86d6b950e5 emulator: B2 RIO lamp/button bezel on the VDB heads + explode-view polish
Draw the cockpit RIO buttons around the mono-MFD and radar heads (explode
layout), lit by the host-commanded lamp state -- the display side of the
in-fork glass cockpit. Live-validated 2026-07-24 (operator).

- vpxlog.cpp: per-head button bezel in pal_draw, reading serialrio's new
  RIO_GetPanelState seam. The 5 MFD heads get red buttons (4 top / 4 bottom,
  100px tall tucked under a grown 640x500 display so a 10px lip shows); the
  radar gets amber Secondary/Screen side columns (6x 104px each) plus a
  centered bottom indicator strip (the 4 spares). Lamp byte decoded to
  off/dim/bright (vRIO RioLampState, brighter of the two brightness fields);
  a press shows white-hot. No labels -- the MFD shows each button's function.
  Also swapped the two upper-outer MFD window NAMES to match their (already
  position-swapped) desktop locations.
- serialrio.{cpp,h}: RIO_GetPanelState(lamps, pressed) accessor -- returns
  false when no rio port, so non-rio configs and other heads render normally.
- pod-launch: in explode (dev) layout the Division bridge is a normal, freely
  movable window -- not pinned topmost (Focus.cs) and nudged to 8,40 so its
  title bar clears the top of the screen (its client was at 0,0, pushing the
  frame off-screen). Cockpit/kiosk keeps the topmost + 0,0 borderless look.

Only affects the explode layout and only when a serial=rio port is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 10:43:41 -05:00

94 lines
4.2 KiB
C#

// Window layout, folded in from focus_dosbox.ps1:
// 1. the RENDERER (GL bridge) -> always-on-top, so the out-the-window view is
// never covered by a head window.
// 2. DOSBox-X -> parked at (x,y) with FOREGROUND focus, so its emulation
// thread keeps foreground priority -- else the RIO ACK deadline is missed
// and the board storms (the RIO focus-sensitivity issue).
// A topmost renderer stays visually above the focused-but-non-topmost DOSBox,
// yet DOSBox stays the active window.
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace VwePod
{
internal static class Focus
{
public static string Apply(int x, int y, bool pinRenderer)
{
var s = new StringBuilder();
// 1) renderer Z-order. Kiosk/cockpit: pin it always-on-top so a head
// window never covers the out-the-window view. Explode (dev): leave
// it a normal window so the operator can freely move/restack it.
IntPtr r = FindRenderer();
if (r != IntPtr.Zero)
{
SetWindowPos(r, pinRenderer ? HWND_TOPMOST : HWND_NOTOPMOST,
0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
s.Append(pinRenderer ? "renderer topmost; " : "renderer movable; ");
}
else s.Append("renderer NOT found; ");
// 2) DOSBox -> parked + foreground
IntPtr d = FindWindow("DOSBox-X");
if (d == IntPtr.Zero) return s.Append("DOSBox NOT found").ToString();
SetWindowPos(d, IntPtr.Zero, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
ShowWindow(d, SW_RESTORE);
uint fgThread = GetWindowThreadProcessId(GetForegroundWindow(), out _);
uint me = GetCurrentThreadId();
AttachThreadInput(me, fgThread, true);
BringWindowToTop(d);
SetForegroundWindow(d);
AttachThreadInput(me, fgThread, false);
s.Append("DOSBox at ").Append(x).Append(',').Append(y)
.Append(" focused=").Append(GetForegroundWindow() == d);
return s.ToString();
}
private static IntPtr FindRenderer()
{
IntPtr h = FindWindow("dpl3-revive renderer");
return h != IntPtr.Zero ? h : FindWindow("VelociRender");
}
private static IntPtr FindWindow(string needle)
{
IntPtr found = IntPtr.Zero;
EnumWindows((h, l) =>
{
if (!IsWindowVisible(h)) return true;
var b = new byte[512];
int n = GetWindowTextA(h, b, b.Length);
if (n > 0 && Encoding.ASCII.GetString(b, 0, n).Contains(needle)) { found = h; return false; }
return true;
}, IntPtr.Zero);
return found;
}
// ---- native ----
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
private const uint SWP_NOSIZE = 0x0001, SWP_NOMOVE = 0x0002, SWP_NOZORDER = 0x0004;
private const int SW_RESTORE = 9;
private delegate bool EnumProc(IntPtr h, IntPtr l);
[DllImport("user32.dll")] private static extern bool EnumWindows(EnumProc cb, IntPtr l);
[DllImport("user32.dll")] private static extern int GetWindowTextA(IntPtr h, byte[] b, int max);
[DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h);
[DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int w, int hh, uint flags);
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr h);
[DllImport("user32.dll")] private static extern bool BringWindowToTop(IntPtr h);
[DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr h, int cmd);
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
[DllImport("user32.dll")] private static extern bool AttachThreadInput(uint a, uint b, bool attach);
[DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId();
}
}