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>
52 lines
1.8 KiB
C#
52 lines
1.8 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace RioJoy.Core.Profiles;
|
|
|
|
/// <summary>
|
|
/// Loads and saves <see cref="AppConfig"/> as JSON. Replaces the legacy
|
|
/// SimpleIni single-file config with a profile library (see docs/PLAN.md).
|
|
/// </summary>
|
|
public static class ConfigStore
|
|
{
|
|
private static readonly JsonSerializerOptions Options = new()
|
|
{
|
|
WriteIndented = true,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
|
Converters = { new JsonStringEnumConverter() },
|
|
};
|
|
|
|
public static string Serialize(AppConfig config)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(config);
|
|
return JsonSerializer.Serialize(config, Options);
|
|
}
|
|
|
|
public static AppConfig Deserialize(string json)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(json);
|
|
return JsonSerializer.Deserialize<AppConfig>(json, Options)
|
|
?? throw new JsonException("Config JSON deserialized to null.");
|
|
}
|
|
|
|
/// <summary>Save the config to <paramref name="path"/> (creating directories).</summary>
|
|
public static void Save(AppConfig config, string path)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
|
string? dir = Path.GetDirectoryName(Path.GetFullPath(path));
|
|
if (!string.IsNullOrEmpty(dir))
|
|
Directory.CreateDirectory(dir);
|
|
File.WriteAllText(path, Serialize(config));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load the config from <paramref name="path"/>, or return a fresh default
|
|
/// <see cref="AppConfig"/> if the file does not exist.
|
|
/// </summary>
|
|
public static AppConfig Load(string path)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(path);
|
|
return File.Exists(path) ? Deserialize(File.ReadAllText(path)) : new AppConfig();
|
|
}
|
|
}
|