emulator: RIO panel click-to-press + lamp flash blink

Make the in-fork glass cockpit fully interactive and finish the lamp model.

- Click-to-press: clicking a button's region in a VDB head window presses that
  RIO address into the game (lights white-hot while held, releases on mouse-up).
  vpxlog rt_wndproc hit-tests the click against the bezel geometry (MFD 4 top /
  4 bottom, radar 6 left / 6 right; the radar bottom indicators are display-only,
  not clickable); head windows are tagged via GWLP_USERDATA. Clicks arrive on the
  VPX render thread, so the seam serialrio RIO_HostButton() queues them under a
  mutex and pollInput() drains them on the emu tick through the same incHold/
  decHold path as the keyboard/pad -- so a click also lights the bezel.
- Flash blink: decode the lamp byte's flash bits (RioLampState 1 slow / 2 med /
  3 fast) -- an unpressed flashing lamp now toggles between its brightness level
  and off (half-periods 500/250/125ms); solid lamps unchanged, a press still wins.
- Focus: head windows are WS_EX_NOACTIVATE so a button click never pulls
  foreground off DOSBox (which would demote the emu thread and stop SDL polling
  the pad); a click uses MA_NOACTIVATE too. Do NOT enable SDL background joystick
  events -- that raced the emu-thread controller poll against SDL's main-thread
  pump and crashed the process.

Explode layout only, and only when a serial=rio port is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-24 11:50:53 -05:00
co-authored by Claude Opus 4.8
parent f2424e9fdc
commit 9dd0aa8702
4 changed files with 125 additions and 5 deletions
+26
View File
@@ -217,6 +217,10 @@ bool RIO_GetPanelState(uint8_t lamps[72], uint8_t pressed[72]) {
return true;
}
void RIO_HostButton(int addr, bool down) {
if (rio_instance) rio_instance->hostButton(addr, down);
}
// ============================================================================
CSerialRio::CSerialRio(Bitu id, CommandLine* cmd) : CSerial(id, cmd) {
@@ -584,6 +588,13 @@ void CSerialRio::getPanelState(uint8_t lampsOut[72], uint8_t pressedOut[72]) {
}
}
// panel click from the VPX render thread -- queue it; pollInput() (emu thread)
// drains and applies it, so it composes with the keyboard/pad hold counts.
void CSerialRio::hostButton(int addr, bool down) {
std::lock_guard<std::mutex> lk(panelMx);
panelQ.push_back(std::make_pair(addr, down));
}
void CSerialRio::releaseAllKeys() {
std::set<int> keys = heldKeys;
heldKeys.clear();
@@ -781,6 +792,11 @@ void CSerialRio::applyReset(uint8_t target) {
// ---- gamepad + axis input ----------------------------------------------------
void CSerialRio::openPad() {
// NOTE: do NOT enable SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS here -- it
// makes SDL's main-thread event pump update the joystick concurrently with
// our emu-thread SDL_GameControllerUpdate (not thread-safe -> process crash,
// 2026-07-24). Keeping DOSBox focused (the head windows are WS_EX_NOACTIVATE
// so a click can't steal foreground) is what keeps the pad polling.
SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);
int n = SDL_NumJoysticks();
for (int i = 0; i < n; i++) {
@@ -815,6 +831,16 @@ void CSerialRio::pollInput() {
if (dt < 0.0) dt = 0.0;
if (dt > 0.1) dt = 0.1;
// apply panel clicks queued by the VPX window thread (RIO_HostButton) --
// same hold-count path as the keyboard/pad, so a held click lights the bezel
std::vector<std::pair<int,bool>> clicks;
{ std::lock_guard<std::mutex> lk(panelMx); clicks.swap(panelQ); }
for (size_t i = 0; i < clicks.size(); i++) {
if (clicks[i].second) incHold(clicks[i].first);
else decHold(clicks[i].first);
logf("panel %s 0x%02X", clicks[i].second ? "click" : "release", clicks[i].first);
}
// gamepad snapshot; everything rests at zero when absent/detached, so a
// detach releases whatever the pad held (button edges below see btns=0)
float src[6] = { 0, 0, 0, 0, 0, 0 };