Wire the Core pieces into a runnable tray app with per-game profiles and the three-state serial-yield auto-switch: - Profiles/: RioProfile + AppConfig model; ConfigStore (System.Text.Json, round-tripped); RioIniImporter ports the legacy RIO.ini (button table, invert flags, plasma greeting); AutoSwitchResolver + AutoSwitchWatcher resolve the foreground executable into Yield (native game) / Activate (profile) / Idle, with native always winning and change-only notifications. IForegroundProcessProvider abstracts the OS. - RioRuntime assembles a profile's live pipeline: serial ButtonPressed/Released + KeyPressed/Released → InputRouter (via RioAddress); AnalogReply → AxisCalibrator → the six joystick axes; RIO commands → calibration resets + version/check requests + lamp re-init. SerialLampSink sends lamp feedback over the link; NullJoystickSink is a placeholder until the Phase 1 HID feeder exists. - RioJoy.Tray: NotifyIcon menu mirroring the legacy console menu (axis resets, version/status, raw-axes & poll-rate toggles, quit) + profile selection (auto vs. manual) + "start with Windows"; RioCoordinator owns the serial acquire/release tied to the watcher (native-game COM-port yield). OS adapters: ForegroundProcessProvider (Win32 foreground PID→exe) and AutoStartManager (HKCU Run key). - tests: 18 new xUnit tests (123 total) for config round-trip, ini import, the three-state resolver + watcher, and RioRuntime end-to-end over the fake transport (button→joystick, keypad-offset→keyboard, analog→six axes). The joystick output stays a no-op until the Phase 1 driver; on-cabinet verification of the acquire/release lifecycle remains. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.6 KiB
C#
48 lines
1.6 KiB
C#
using RioJoy.Core.Mapping;
|
|
|
|
namespace RioJoy.Core.Tests.Mapping;
|
|
|
|
/// <summary>
|
|
/// Records every routed output as an ordered, human-readable log so tests can
|
|
/// assert both <i>what</i> happened and <i>in what order</i>.
|
|
/// </summary>
|
|
internal sealed class RecordingSink : IInputSink, IJoystickSink, ILampSink, IRioCommandSink
|
|
{
|
|
private readonly List<string> _log = new();
|
|
|
|
/// <summary>Direct access for single-threaded tests.</summary>
|
|
public List<string> Log
|
|
{
|
|
get { lock (_log) return _log; }
|
|
}
|
|
|
|
/// <summary>Thread-safe copy for assertions while the link thread writes.</summary>
|
|
public string[] Snapshot()
|
|
{
|
|
lock (_log) return _log.ToArray();
|
|
}
|
|
|
|
private void Add(string entry)
|
|
{
|
|
lock (_log) _log.Add(entry);
|
|
}
|
|
|
|
public void KeyDown(byte virtualKey, bool extended) => Add($"KeyDown(0x{virtualKey:X2},ext={extended})");
|
|
|
|
public void KeyUp(byte virtualKey, bool extended) => Add($"KeyUp(0x{virtualKey:X2},ext={extended})");
|
|
|
|
public void MouseMove(int dx, int dy) => Add($"MouseMove({dx},{dy})");
|
|
|
|
public void MouseButton(MouseButton button, bool down) => Add($"MouseButton({button},{down})");
|
|
|
|
public void SetButton(int button, bool pressed) => Add($"Joy({button},{pressed})");
|
|
|
|
public void SetHat(RioHat position) => Add($"Hat({position})");
|
|
|
|
public void SetAxis(RioJoy.Core.Calibration.JoyAxis axis, int value) => Add($"Axis({axis},{value})");
|
|
|
|
public void SetLamp(int address, byte lampState) => Add($"Lamp(0x{address:X2},0x{lampState:X2})");
|
|
|
|
public void Execute(RioCommandCode command) => Add($"Cmd({command})");
|
|
}
|