diff --git a/emulator/vpx-device/README.md b/emulator/vpx-device/README.md index 78235cf8..bd2c50c0 100644 --- a/emulator/vpx-device/README.md +++ b/emulator/vpx-device/README.md @@ -12,8 +12,12 @@ version control because the DOSBox-X source tree itself bottom, tucked under a 640x500 display so a 10px lip shows) and the radar with the amber Secondary/Screen side columns + a centered bottom indicator strip, lit from `serialrio`'s `RIO_GetPanelState` (off/dim/bright + white-hot - press). No labels -- the display shows each button's function. No-op when no - `serial=rio` port is present. + press; flashing lamps blink at the RioLampState rate). No labels -- the + display shows each button's function. **Click-to-press:** `rt_wndproc` + hit-tests a click against the bezel and presses that RIO address (thread-safe + via serialrio's queued `RIO_HostButton`); head windows are `WS_EX_NOACTIVATE` + so a click never pulls foreground off DOSBox (which would demote the emu + thread and stop the SDL pad poll). No-op when no `serial=rio` port is present. - **`vweawe.cpp`** — the dual-AWE32 sound device: two vendored EMU8000 cores at 0x620/0x640 (+0x400/+0x800 triplets), rear-card DSP/mixer stub at 0x240, autonomous render thread with direct winmm output. Needs the diff --git a/emulator/vpx-device/serialrio.cpp b/emulator/vpx-device/serialrio.cpp index 53cb8547..fcf6c59f 100644 --- a/emulator/vpx-device/serialrio.cpp +++ b/emulator/vpx-device/serialrio.cpp @@ -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 lk(panelMx); + panelQ.push_back(std::make_pair(addr, down)); +} + void CSerialRio::releaseAllKeys() { std::set 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> clicks; + { std::lock_guard 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 }; diff --git a/emulator/vpx-device/serialrio.h b/emulator/vpx-device/serialrio.h index 590276a7..839997ea 100644 --- a/emulator/vpx-device/serialrio.h +++ b/emulator/vpx-device/serialrio.h @@ -60,8 +60,10 @@ #include #include +#include #include #include +#include #include // ---- host-side hooks (called from sdlmain's SDL2 event loop) ---------------- @@ -78,6 +80,11 @@ void RIO_HostFocusLost(void); // serial=rio port is installed, so the VDB heads render normally. bool RIO_GetPanelState(uint8_t lamps[72], uint8_t pressed[72]); +// A panel button was clicked (down=true) or released in a VDB head window. +// Called from the VPX render thread (rt_wndproc), so it is thread-safe: the +// event is queued and applied on the emulator's next tick. No-op if no rio port. +void RIO_HostButton(int addr, bool down); + // ---- binding profile (transcribed from vRIO BindingProfile) ----------------- // key-axis modes: DEFLECT holds the axis at value while the key is down and // springs back; RATE ("slew" in bindings files) walks the axis by value/second @@ -106,6 +113,7 @@ public: bool hostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods); void hostFocusLost(); void getPanelState(uint8_t lampsOut[72], uint8_t pressedOut[72]); + void hostButton(int addr, bool down); // panel click (VPX thread -> queue) private: // ---- board -> game byte delivery (UART RX pacing; mirrors directserial) -- @@ -168,6 +176,8 @@ private: int last_sent[5] = {}; bool last_sent_valid[5] = {}; std::map holdCounts; // held count per RIO address + std::mutex panelMx; // guards panelQ (VPX thread <-> emu) + std::vector> panelQ; // pending panel clicks (addr, down) std::set heldKeys; // scancodes routed to the panel std::set toggled; // latched (toggle) addresses uint16_t prevPadButtons = 0; diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index df27344b..b325b4bf 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -1343,14 +1343,25 @@ static int mfd_hi_for_g(int g) { return -1; } +/* Flash phase for a flashing lamp (RioLampState bits 0-1: 1 slow / 2 med / 3 + * fast; 0 = solid = always on). Toggles on/off at half-periods 500/250/125ms + * off the wall clock, so a flashing lamp blinks between its level and off. */ +static bool rio_flash_on(int flash) { + if (flash == 0) return true; + unsigned int hp = flash == 1 ? 500u : flash == 2 ? 250u : 125u; + return ((GetTickCount() / hp) & 1u) == 0u; +} + /* Panel button color at the host lamp level (vRIO RioLampState: brighter of the * two brightness fields, bits 2-3 = 0x0C bright/0x04 dim, bits 4-5 = 0x30 bright/ - * 0x10 dim). MFD buttons are red, radar Secondary/Screen buttons are amber. A - * press shows white-hot so it's visible over any level. */ + * 0x10 dim; bits 0-1 = flash mode). MFD buttons are red, radar Secondary/Screen + * buttons are amber. A press shows white-hot so it's visible over any level. */ static void rio_button_color(uint8_t ls, bool pr, bool amber, float *r, float *g, float *b) { int f1 = ls & 0x0C, f2 = ls & 0x30; int level = (f1 == 0x0C || f2 == 0x30) ? 2 : (f1 || f2) ? 1 : 0; - if (pr) { *r = 1.00f; *g = 0.90f; *b = 0.62f; return; } /* pressed white-hot */ + int flash = ls & 0x03; + if (flash != 0 && !rio_flash_on(flash)) level = 0; /* flashing, off phase */ + if (pr) { *r = 1.00f; *g = 0.90f; *b = 0.62f; return; } /* pressed white-hot (press wins) */ if (amber) { if (level == 2) { *r = 1.00f; *g = 0.72f; *b = 0.05f; } /* bright amber */ else if (level == 1) { *r = 0.50f; *g = 0.36f; *b = 0.02f; } /* dim amber */ @@ -1717,8 +1728,69 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { SwapBuffers(dc); } +/* ---- click-to-press: map a click in a head window to a RIO button ---------- + * Mirrors the bezel geometry (draw_mfd_bezel / draw_radar_bezel): returns the + * RIO address under (mx,my) in client coords, or -1 for a non-button click. + * The radar bottom indicators (spares) are display-only, not clickable. */ +extern void RIO_HostButton(int addr, bool down); + +static int rio_hit_test(int g, int cw, int ch, int mx, int my) { + int hi = mfd_hi_for_g(g); + if (hi >= 0) { /* MFD head: 4 top / 4 bottom */ + int col = (cw > 0) ? mx * 4 / cw : 0; + if (col < 0) col = 0; + else if (col > 3) col = 3; + if (my >= 0 && my < RIO_BEZEL_BTN) return hi - col; /* top row (window top) */ + if (my >= ch - RIO_BEZEL_BTN && my < ch) return hi - 4 - col; /* bottom row */ + return -1; + } + if (g == 0) { /* radar: 6 left / 6 right */ + const int BTN_H = 104, GAP = 3, N = 6, BTN_W = 100; + int side = -1; + if (mx >= 0 && mx < BTN_W) side = 0; /* left = Secondary 0x10.. */ + else if (mx >= cw - BTN_W && mx < cw) side = 1; /* right = Screen 0x18.. */ + if (side < 0) return -1; + int stack = N * BTN_H + (N - 1) * GAP; + int margin = ((ch - RIO_RADAR_BOT) - stack) / 2; + int rel = my - margin; + if (rel < 0) return -1; + int i = rel / (BTN_H + GAP); + if (i < 0 || i >= N) return -1; + if (rel - i * (BTN_H + GAP) >= BTN_H) return -1; /* landed in the 3px gap */ + return (side == 0 ? 0x10 : 0x18) + i; + } + return -1; +} + +static int g_click_addr[10] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* held addr per head */ + static LRESULT CALLBACK rt_wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { if (msg == WM_CLOSE) { ShowWindow(w, SW_MINIMIZE); return 0; } + if (msg == WM_MOUSEACTIVATE) return MA_NOACTIVATE; /* click a button, don't steal DOSBox focus */ + if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP) { + int tag = (int)(LONG_PTR)GetWindowLongPtrA(w, GWLP_USERDATA); /* head windows store g+1 */ + int g = tag - 1; + if (g >= 0 && g < 10 && pal_radar_cw) { /* bezel exists only in explode layout */ + if (msg == WM_LBUTTONDOWN) { + RECT cr; GetClientRect(w, &cr); + int addr = rio_hit_test(g, cr.right, cr.bottom, + (short)LOWORD(lp), (short)HIWORD(lp)); + if (addr >= 0) { + if (g_click_addr[g] >= 0) RIO_HostButton(g_click_addr[g], false); + g_click_addr[g] = addr; + RIO_HostButton(addr, true); + SetCapture(w); + } + } else { /* WM_LBUTTONUP */ + if (g_click_addr[g] >= 0) { + RIO_HostButton(g_click_addr[g], false); + g_click_addr[g] = -1; + ReleaseCapture(); + } + } + return 0; + } + } return DefWindowProcA(w, msg, wp, lp); } @@ -1860,6 +1932,14 @@ static DWORD WINAPI rt_main(LPVOID) { env_rect(en, &x, &y, &w, &h); phave[g] = make_gl_window(pal_titles[g], w, h, x, y, cockpit, &pwnd[g], &pdc[g], &pgl[g]); + if (phave[g]) { + /* tag with g+1 so rt_wndproc maps a click to this head (click-to-press) */ + SetWindowLongPtrA(pwnd[g], GWLP_USERDATA, (LONG_PTR)(g + 1)); + /* WS_EX_NOACTIVATE: clicking a button never pulls foreground off + * DOSBox (else the emu thread demotes and SDL stops polling the pad). */ + SetWindowLongPtrA(pwnd[g], GWL_EXSTYLE, + GetWindowLongPtrA(pwnd[g], GWL_EXSTYLE) | WS_EX_NOACTIVATE); + } } VFrame cur;