The WinForms focus path only sees keys while vRIO is foreground and can't tell one keyboard from another, so vRIO went deaf the moment the sim took focus. The new "Capture keyboard" picker in the Input panel taps one physical keyboard through the Win32 Raw Input API (RIDEV_INPUTSINK) and routes its keys to the panel even in the background — the input-side twin of the lamp mirror writing that keyboard's LEDs under the same condition. Raw Input observes without intercepting, so keys still reach the focused app; the picker defaults to "All keyboards (focus only)", preserving the prior focus-bound behavior, and only registers Raw Input while a specific device is selected. While capturing, the focus path stands down but still swallows bound keys so they can't click panel buttons. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
127 lines
7.0 KiB
Markdown
127 lines
7.0 KiB
Markdown
# vRIO — virtual RIO cockpit device
|
||
|
||
A software replica of the cockpit **RIO** (Remote Input/Output) board: it opens
|
||
a COM port and speaks the **device side** of the RIO serial protocol, so any
|
||
host that expects the real hardware — most importantly
|
||
[RIOJoy](../riojoy/) — can talk to it without a cockpit attached.
|
||
|
||
The window is an interactive version of the cockpit control panel that
|
||
RIOJoy's profile editor draws (the same functional layout from the original
|
||
Win32 RIO design: five MFD clusters, four board columns, two keypads, encoder
|
||
gauges). But where the editor *edits bindings*, vRIO's cells are the physical
|
||
controls:
|
||
|
||
- **Left-click** a cell — momentary button press (`ButtonPressed`/`Released`
|
||
or `KeyPressed`/`Released` on the wire). **Right-click** — latch it down.
|
||
- **Drag** the X/Y box and the Z / L / R gauges — the five analog axes,
|
||
returned by the next `AnalogReply` (14-bit signed, 7-bit-pair packed).
|
||
Gauges span each axis' realistic hardware travel, not the full 14-bit
|
||
range: Z/L/R rest at raw 0 at the gauge bottom and travel to −800
|
||
(throttle — forward travel runs negative, matching RIOJoy's ratchet) or
|
||
+500 (spring-loaded pedals), and the stick covers ±80 around center —
|
||
the windows RIOJoy's calibrator expects.
|
||
- Cells shade to the **lamp state the host commands** (`LampRequest`:
|
||
off / dim / bright, with slow/med/fast flash), so RIOJoy's press-feedback
|
||
lights the on-screen panel just like the real buttons.
|
||
- **Lamps can mirror onto an RGB keyboard** (*Mirror lamps on RGB keyboard*,
|
||
via Windows 11 Dynamic Lighting): keys bound to lamp-capable buttons glow
|
||
with the panel's palette and blink in step with the on-screen flash modes;
|
||
per-key keyboards get the full button field, zone-lit keyboards show the
|
||
strongest current lamp board-wide, and a picker narrows the mirror to one
|
||
keyboard when several are attached. For the LEDs to stay lit while the
|
||
*game* has focus, vRIO needs package identity: run `pkg\Register-vRIO.ps1`
|
||
once (Developer Mode required; pass the exe folder for a deployed copy),
|
||
launch vRIO from its **Start menu entry** (a direct exe launch runs without
|
||
identity), then drag vRIO to the top of *Settings → Personalization →
|
||
Dynamic Lighting → Background light control*.
|
||
- **Keyboard and Xbox (XInput) controller input** drive the same controls
|
||
through a bindings file (`%APPDATA%\vRIO\bindings.txt`, created with
|
||
commented defaults on first run — *Edit bindings…* opens it, *Reload
|
||
bindings* applies edits live). Keys and pad buttons press any RIO address;
|
||
pad sticks/triggers and keys drive the axes in each axis' realistic travel
|
||
window, with deflect (spring-back), rate (throttle-style, position holds),
|
||
deadzone, and invert options. The default profile makes the controller
|
||
mandatory: all five axes live on the pad (left stick / triggers / right
|
||
stick = stick / pedals / throttle) and the keyboard covers the button
|
||
field — number row + QWERTY row = the upper MFD bank, home + bottom
|
||
rows = the lower MFD bank (4-key blocks split by an unbound gap key),
|
||
F1–F6 / F7–F12 = the Secondary / Screen columns, numpad = internal
|
||
keypad (hex keys on the operators), arrows + Space = hat + main, with
|
||
ABXY / dpad / shoulders on the pad's named buttons.
|
||
- **Capture one keyboard in the background** (*Capture keyboard* picker in the
|
||
Input panel): by default keyboard input follows the focused window, so vRIO
|
||
goes deaf the moment the sim takes focus. Selecting a specific keyboard
|
||
instead taps it through the Windows **Raw Input** API (`RIDEV_INPUTSINK`), so
|
||
its keys drive the panel *while the game has focus* — the input-side twin of
|
||
the lamp mirror writing that keyboard's LEDs under the same condition. Raw
|
||
Input observes without intercepting: the keystroke still reaches the focused
|
||
app, so this is meant for a keyboard *dedicated* to the panel, not the one you
|
||
also type on. Leaving the picker on *All keyboards* keeps the plain
|
||
focus-only behavior. (No package identity needed — unlike the lamp side, this
|
||
is a plain Win32 tap.)
|
||
|
||
## Wire behavior
|
||
|
||
Protocol per `riojoy/docs/PROTOCOL.md` (9600 8N1, `[cmd][payload…][7-bit
|
||
checksum]`, ACK `0xFC` / NAK `0xFD` / RESTART `0xFE` / IDLE `0xFF`), with
|
||
device behavior grounded in the **real v4.2 firmware dump**
|
||
(`riojoy/rio-firmware/RIOv4_2-ANALYSIS.md`):
|
||
|
||
- ACKs every well-formed packet; NAKs bad-checksum packets.
|
||
- **TX is paced at the wire rate** — one byte per 10-bit frame (~1.04 ms at
|
||
9600 8N1), never closer. A virtual null-modem has no UART, so unpaced
|
||
writes would land at the host in microsecond bursts no real board could
|
||
produce; vRIO's writer thread schedules each byte against a monotonic
|
||
slot deadline instead, so e.g. the 51-byte CheckRequest response takes
|
||
the same ~53 ms it takes real hardware.
|
||
- `CheckRequest` → the real board's init handshake: `TestModeChange` **enter**,
|
||
one `BoardOk` CheckReply per board (the 11 boards from the legacy firmware's
|
||
table), then `TestModeChange` **exit**. Hosts wait (≤5 s per step) on both
|
||
test-mode packets and send nothing while test mode is active, so the exit is
|
||
mandatory. `VersionRequest` → configurable version, default **4.2**.
|
||
- `ResetRequest` re-zeroes the targeted axis (or all).
|
||
- A NAK re-sends the last event up to **4 times**, then gives up with a
|
||
RESTART byte — the real board's retry budget.
|
||
- Optional **v4.2 reply-wedge emulation** (in `VRio.Core`; the UI toggles
|
||
were removed): after retry exhaustion, analog requests are silently
|
||
dropped — still ACK'd, RX path alive — until a host `ResetRequest`,
|
||
reproducing the latch-leak fault the firmware analysis documents.
|
||
|
||
## Using it with RIOJoy on one PC
|
||
|
||
The two apps need a crossed serial link. Install a
|
||
[com0com](https://com0com.sourceforge.net/) virtual null-modem pair
|
||
(e.g. `COM5 ⇄ COM6`), then:
|
||
|
||
1. Run `VRio.App`, pick `COM5`, **Open**.
|
||
2. Point RIOJoy at `COM6`.
|
||
|
||
RIOJoy's DTR open-pulse shows up in the wire log (DSR handshake), its ~55 ms
|
||
analog polling drives the "analog polls served" counter, and every click on
|
||
the vRIO panel arrives at RIOJoy as real cockpit input. Two physical PCs with
|
||
a null-modem cable work the same way.
|
||
|
||
## Repository layout
|
||
|
||
| Path | Contents |
|
||
|------|----------|
|
||
| `src/VRio.Core` | Protocol framing/builder/parser, the `VRioDevice` state machine, serial pump, panel layout data (class library) |
|
||
| `src/VRio.App` | WinForms panel UI |
|
||
| `pkg` | Sparse-package manifest + registration script: grants VRio.App.exe the package identity Dynamic Lighting's background-control list requires |
|
||
| `tests/VRio.Core.Tests` | xUnit tests for the protocol + device engine |
|
||
|
||
## Building
|
||
|
||
Same toolchain as RIOJoy: **.NET SDK** (8.0+) with the **.NET Framework 4.8**
|
||
targeting pack; apps target net48 so deployed builds run in-box on
|
||
Windows 10/11.
|
||
|
||
```sh
|
||
dotnet build VRio.sln -c Release
|
||
dotnet test VRio.sln
|
||
```
|
||
|
||
Interop is additionally verified against RIOJoy's real `RioSerialLink`
|
||
(version/check/analog/lamp/button/keypad/reset round-trips over an in-memory
|
||
transport) — see the RIOJoy repo for the host side.
|