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>
195 lines
9.1 KiB
C++
195 lines
9.1 KiB
C++
/*
|
|
* VWE fork: in-fork virtual RIO cockpit board (serial1=rio).
|
|
*
|
|
* A native DOSBox-X serial backend that *is* the cockpit RIO board: it speaks
|
|
* the device side of the RIO 9600-8N1 protocol to the game's UART, and sources
|
|
* its pilot input from an SDL game controller + the host keyboard inside the
|
|
* emulator. There is no external process and no pipe, so the game<->board
|
|
* round trip happens entirely in-process at the emulated UART's own byte
|
|
* cadence -- the same timing a real ISA UART sees on a physical pod, which
|
|
* removes the whole class of serial / pipe round-trip-latency dropouts
|
|
* (livelock, TXMAXIDLE, rxburst, 15s retry).
|
|
*
|
|
* The protocol state machine + input mapping are transcribed from the
|
|
* validated vRIO app (C:\VWE\vrio VRio.Core: VRioDevice, the Protocol codec,
|
|
* Input/InputRouter + BindingProfileFormat), minus its transport, wire pacer,
|
|
* panel UI and thread locks -- CSerial callbacks, the SDL reads and the host
|
|
* keyboard hook all run on the single emulator thread.
|
|
*
|
|
* Keyboard field (B1): sdlmain's SDL2 event loop offers every key to
|
|
* RIO_HostKeyEvent() first. While capture is ON (default; PAUSE or
|
|
* SCROLL LOCK toggles it -- plus an optional `togglekey:<name>`) a key bound
|
|
* in the profile presses its RIO address (button 0x00-0x47 / keypad
|
|
* 0x50-0x6F) or drives an axis, and is swallowed before the DOS keyboard
|
|
* sees it; unbound keys and Ctrl/Alt/Gui chords pass through (bound modifier
|
|
* keys are exempt from the chord rule), so DOSBox-X host hotkeys keep
|
|
* working. Toggling capture OFF releases everything held and gives the whole
|
|
* keyboard back to DOS (needed at the netnub loop / DOS prompts).
|
|
*
|
|
* Bindings: the built-in default profile = vRIO's pad + button field
|
|
* (number/QWERTY rows = upper MFD bank, home/bottom rows = lower bank,
|
|
* F1-F12 = Secondary/Screen columns, arrows+Space = hat+main) plus keyboard
|
|
* FLIGHT controls (operator 2026-07-22): numpad 8/2/4/6 = stick, 7/9 =
|
|
* pedals, LShift/LCtrl = throttle slew, numpad-5 = throttle zero; the
|
|
* internal keypad is unbound (mission-review-only plumbing). A bindings file
|
|
* in vRIO's grammar (`key <name> button <addr> [toggle]` / `key <name> axis
|
|
* <axis> deflect|slew|rate|set <n>` / `pad <button> button <addr> [toggle]`
|
|
* / `padaxis <src> axis <axis> [invert] [deadzone <d>] [rate <n>]`, '#'
|
|
* comments, sign may be a separate token) REPLACES the default: conf arg
|
|
* `bindings:<path>`, else env VWE_RIO_BINDINGS. vRIO's .NET key names (D1,
|
|
* NumPad0, OemMinus, ...) are aliased, so a vRIO bindings.txt ports over.
|
|
*
|
|
* This is an ADDITIVE dev / standalone backend. Real pods keep the physical
|
|
* board on `serial1=directserial realport:COM1`; vRIO-over-pipe keeps
|
|
* `serial1=namedpipe pipe:vrio`. Neither is touched by this file.
|
|
*
|
|
* Conf: serial1=rio [pad:<n>] [inverty] [bindings:<path>] [togglekey:<key>]
|
|
* [rxpollus:<us>] [rxburst:<n>]
|
|
* pad:<n> game-controller index to use (default: first present)
|
|
* inverty send the joystick Y in the physical direction (up = +)
|
|
* bindings:<p> bindings file (vRIO grammar; replaces the default)
|
|
* togglekey:<k> extra capture-toggle key (SDL name, e.g. End, F24)
|
|
* rx opts directserial semantics (board->game delivery pacing)
|
|
*/
|
|
|
|
#ifndef DOSBOX_SERIALRIO_H
|
|
#define DOSBOX_SERIALRIO_H
|
|
|
|
#include "dosbox.h"
|
|
#include "serialport.h"
|
|
|
|
#include <deque>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <set>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
// ---- host-side hooks (called from sdlmain's SDL2 event loop) ----------------
|
|
// Both are cheap no-ops when no serial<n>=rio port is installed.
|
|
// RIO_HostKeyEvent returns true when the key was consumed by the cockpit panel
|
|
// (bound + captured, or the PAUSE capture toggle) -- the caller must then skip
|
|
// its normal keyboard handling for that event.
|
|
bool RIO_HostKeyEvent(int sdl_scancode, bool pressed, bool repeat, unsigned int sdl_mods);
|
|
void RIO_HostFocusLost(void);
|
|
|
|
// Current panel state for the in-fork lamp/button bezel (B2): fills lamps[]
|
|
// with the host-commanded LampRequest byte and pressed[] with 1 where a button
|
|
// address is currently held. Returns false (and touches nothing) when no
|
|
// serial<n>=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
|
|
// and the position sticks; SET snaps the axis position to value on key press.
|
|
enum { RIO_KAX_DEFLECT = 0, RIO_KAX_RATE = 1, RIO_KAX_SET = 2 };
|
|
struct RioKeyButtonBind { int scancode; int addr; bool toggle; };
|
|
struct RioKeyAxisBind { int scancode; int axis; int mode; float value; };
|
|
struct RioPadButtonBind { uint16_t bit; int addr; bool toggle; };
|
|
struct RioPadAxisBind { int src; int axis; bool invert; float dz; float rate; };
|
|
|
|
class CSerialRio : public CSerial {
|
|
public:
|
|
CSerialRio(Bitu id, CommandLine* cmd);
|
|
virtual ~CSerialRio();
|
|
|
|
void updatePortConfig(uint16_t divider, uint8_t lcr);
|
|
void updateMSR();
|
|
void transmitByte(uint8_t val, bool first);
|
|
void setBreak(bool value);
|
|
void setRTSDTR(bool rts, bool dtr);
|
|
void setRTS(bool val);
|
|
void setDTR(bool val);
|
|
void handleUpperEvent(uint16_t type);
|
|
|
|
// host keyboard (forwarded by the RIO_Host* free functions)
|
|
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) --
|
|
bool doReceive();
|
|
std::deque<uint8_t> rxq; // bytes the board wants to send to the game
|
|
float rx_poll_ms = 1.0f;
|
|
float rx_burst_div = 1.0f;
|
|
Bitu rx_retry = 0;
|
|
Bitu rx_retry_max = 0;
|
|
Bitu rx_state = 0;
|
|
|
|
// ---- game -> board protocol RX parser --------------------------------
|
|
void feedByte(uint8_t ch);
|
|
uint8_t parse_buf[16] = {};
|
|
int parse_remaining = 0; // bytes still expected (0 = not in a packet)
|
|
int parse_count = 0; // bytes stored for the current packet
|
|
|
|
// ---- device (board) state --------------------------------------------
|
|
void onPacket(const uint8_t* body, int bodyLen, bool checksumValid);
|
|
void onControl(uint8_t b);
|
|
void applyReset(uint8_t target);
|
|
void emit(const uint8_t* p, int n); // queue board->game bytes
|
|
void emitControl(uint8_t c);
|
|
void sendEvent(const uint8_t* p, int n); // remember (for NAK resend) + emit
|
|
void pressAddress(int addr);
|
|
void releaseAddress(int addr);
|
|
void setAxis(int axis, int value);
|
|
|
|
int16_t axes[5] = {};
|
|
uint8_t lamps[72] = {};
|
|
std::vector<uint8_t> lastEventPacket; // last button/key/test event
|
|
int resends = 0;
|
|
bool analogMuted = false; // v4.2 wedge bug -- default OFF
|
|
uint8_t verMajor = 4, verMinor = 2;
|
|
bool invertY = false;
|
|
long analogRequests = 0;
|
|
bool dtr_state = false;
|
|
|
|
// ---- input profile + routing state ------------------------------------
|
|
void loadDefaultProfile();
|
|
bool loadBindingsFile(const std::string& path);
|
|
void computeBoundModMask();
|
|
void openPad();
|
|
void pollInput();
|
|
void incHold(int addr);
|
|
void decHold(int addr);
|
|
void toggleAddress(int addr);
|
|
void releaseAllKeys();
|
|
|
|
std::vector<RioKeyButtonBind> keyButtons;
|
|
std::vector<RioKeyAxisBind> keyAxes;
|
|
std::vector<RioPadButtonBind> padButtons;
|
|
std::vector<RioPadAxisBind> padAxes;
|
|
|
|
void* pad = nullptr; // SDL_GameController* (opaque here)
|
|
int pad_index = -1; // requested index; -1 = first
|
|
double next_pad_open_ms = 0.0;
|
|
double last_tick_ms = 0.0;
|
|
float rate_i[5] = {}; // rate-integrator per axis
|
|
int last_sent[5] = {};
|
|
bool last_sent_valid[5] = {};
|
|
std::map<int,int> holdCounts; // held count per RIO address
|
|
std::mutex panelMx; // guards panelQ (VPX thread <-> emu)
|
|
std::vector<std::pair<int,bool>> panelQ; // pending panel clicks (addr, down)
|
|
std::set<int> heldKeys; // scancodes routed to the panel
|
|
std::set<int> toggled; // latched (toggle) addresses
|
|
uint16_t prevPadButtons = 0;
|
|
bool captureKeys = true; // toggle keys flip panel <-> DOS
|
|
int toggleKeys[3] = { 0, 0, 0 }; // scancodes; defaults Pause+ScrollLock,
|
|
// slot 2 = togglekey:<name> conf arg
|
|
unsigned int boundModMask = 0; // modifier bits bound as inputs
|
|
// (exempt from chord passthrough)
|
|
|
|
bool logv = false; // VWE_RIO_LOG stderr trace
|
|
void logf(const char* fmt, ...);
|
|
};
|
|
|
|
#endif // DOSBOX_SERIALRIO_H
|