namespace RioJoy.Core.Profiles; /// Supplies the current foreground application's executable name. public interface IForegroundProcessProvider { /// The foreground executable name (e.g. "game.exe"), or null if unknown. string? GetForegroundExecutable(); } /// /// Polls the foreground process and resolves the three-state auto-switch decision, /// raising only when the decision actually changes. /// The polling cadence is driven by the host; performs one /// resolution so the logic is deterministic and unit-testable. /// public sealed class AutoSwitchWatcher { private readonly IForegroundProcessProvider _provider; private readonly Func _configAccessor; private SwitchDecision? _last; public AutoSwitchWatcher(IForegroundProcessProvider provider, Func configAccessor) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); _configAccessor = configAccessor ?? throw new ArgumentNullException(nameof(configAccessor)); } /// Raised when the resolved decision differs from the previous one. public event Action? DecisionChanged; /// The most recently resolved decision (null until the first ). public SwitchDecision? Current => _last; /// /// Forget the last decision so the next re-raises /// even if the resolved decision is unchanged. Used /// after a manual/editor override to re-sync the coordinator with the foreground app. /// public void Reset() => _last = null; /// Resolve once; raise if it changed. Returns the decision. public SwitchDecision Poll() { string? exe = _provider.GetForegroundExecutable(); SwitchDecision decision = AutoSwitchResolver.Resolve(_configAccessor(), exe); if (_last is null || !_last.Value.Equals(decision)) { _last = decision; DecisionChanged?.Invoke(decision); } return decision; } /// Poll on until cancelled. public async Task RunAsync(TimeSpan interval, CancellationToken cancellationToken) { // net48 has no PeriodicTimer; poll on a Task.Delay loop instead. Poll(); while (!cancellationToken.IsCancellationRequested) { try { await Compat.TaskCompat.Delay(interval, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { break; } Poll(); } } }