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>
36 lines
942 B
C#
36 lines
942 B
C#
using RioJoy.Core.Mapping;
|
|
|
|
namespace RioJoy.Core.Serial;
|
|
|
|
/// <summary>
|
|
/// <see cref="ILampSink"/> that sends lamp feedback back to the RIO as
|
|
/// <c>LampRequest</c> packets over the serial link. Writes are fire-and-forget
|
|
/// (lamp feedback is best-effort and must not block input routing).
|
|
/// </summary>
|
|
public sealed class SerialLampSink : ILampSink
|
|
{
|
|
private readonly RioSerialLink _link;
|
|
|
|
public SerialLampSink(RioSerialLink link)
|
|
{
|
|
_link = link ?? throw new ArgumentNullException(nameof(link));
|
|
}
|
|
|
|
public void SetLamp(int address, byte lampState)
|
|
{
|
|
_ = SendAsync((byte)address, lampState);
|
|
}
|
|
|
|
private async Task SendAsync(byte address, byte state)
|
|
{
|
|
try
|
|
{
|
|
await _link.SetLampAsync(address, state).ConfigureAwait(false);
|
|
}
|
|
catch
|
|
{
|
|
// Best-effort: a dropped lamp update must not crash the input path.
|
|
}
|
|
}
|
|
}
|