using CommonHelpers; namespace PerformanceOverlay { internal enum OverlayControlResult { NotACommand, HandledAndExit, ContinueStartup } internal enum OverlayControlCommandType { SetMode, CycleMode, ToggleVisibility, Show, Hide } internal readonly struct OverlayControlCommand { public OverlayControlCommandType CommandType { get; } public OverlayMode Mode { get; } private OverlayControlCommand(OverlayControlCommandType commandType, OverlayMode mode = OverlayMode.FPS) { CommandType = commandType; Mode = mode; } public static OverlayControlCommand SetMode(OverlayMode mode) { return new OverlayControlCommand(OverlayControlCommandType.SetMode, mode); } public static OverlayControlCommand CycleMode() { return new OverlayControlCommand(OverlayControlCommandType.CycleMode); } public static OverlayControlCommand ToggleVisibility() { return new OverlayControlCommand(OverlayControlCommandType.ToggleVisibility); } public static OverlayControlCommand Show() { return new OverlayControlCommand(OverlayControlCommandType.Show); } public static OverlayControlCommand Hide() { return new OverlayControlCommand(OverlayControlCommandType.Hide); } } internal static class OverlayCommandLine { internal const string ProtocolScheme = "steamdecktools-performanceoverlay"; private const string RunOnceMutexName = "Global\\PerformanceOverlay"; public static OverlayControlResult HandleArgs(string[] args) { if (!TryParseCommand(args, out var command)) return OverlayControlResult.NotACommand; if (TryDispatchToRunningInstance(command)) return OverlayControlResult.HandledAndExit; // Avoid run-once fatal dialog if an existing instance is present but IPC failed. if (IsPerformanceOverlayInstanceRunning()) return OverlayControlResult.HandledAndExit; ApplyLocally(command); return OverlayControlResult.ContinueStartup; } private static bool TryParseCommand(string[] args, out OverlayControlCommand command) { command = default; foreach (var arg in args) { if (TryParseUriCommand(arg, out command)) return true; } return TryParseSwitchCommand(args, out command); } private static bool TryParseUriCommand(string arg, out OverlayControlCommand command) { command = default; if (!Uri.TryCreate(arg, UriKind.Absolute, out var uri)) return false; if (!uri.Scheme.Equals(ProtocolScheme, StringComparison.OrdinalIgnoreCase)) return false; var segments = new List(); if (!String.IsNullOrWhiteSpace(uri.Host)) segments.Add(uri.Host); segments.AddRange( uri.AbsolutePath .Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ); if (segments.Count == 0) return false; var head = segments[0]; if (head.Equals("cycle", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.CycleMode(); return true; } if (head.Equals("toggle", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.ToggleVisibility(); return true; } if (head.Equals("show", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.Show(); return true; } if (head.Equals("hide", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.Hide(); return true; } if ((head.Equals("mode", StringComparison.OrdinalIgnoreCase) || head.Equals("set", StringComparison.OrdinalIgnoreCase)) && segments.Count > 1 && TryParseMode(segments[1], out var uriMode)) { command = OverlayControlCommand.SetMode(uriMode); return true; } return false; } private static bool TryParseSwitchCommand(string[] args, out OverlayControlCommand command) { command = default; for (int i = 0; i < args.Length; i++) { var arg = args[i]; if (arg.Equals("--cycle", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.CycleMode(); return true; } if (arg.Equals("--toggle", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.ToggleVisibility(); return true; } if (arg.Equals("--show", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.Show(); return true; } if (arg.Equals("--hide", StringComparison.OrdinalIgnoreCase)) { command = OverlayControlCommand.Hide(); return true; } if (arg.StartsWith("--mode=", StringComparison.OrdinalIgnoreCase)) { var modeArg = arg["--mode=".Length..]; if (TryParseMode(modeArg, out var inlineMode)) { command = OverlayControlCommand.SetMode(inlineMode); return true; } } if (arg.Equals("--mode", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length && TryParseMode(args[i + 1], out var nextMode)) { command = OverlayControlCommand.SetMode(nextMode); return true; } } return false; } private static bool TryParseMode(string value, out OverlayMode mode) { if (Enum.TryParse(value, true, out mode)) return true; var normalized = value.Trim().Replace("-", "").Replace("_", "").ToLowerInvariant(); switch (normalized) { case "fpsbattery": case "fpswithbattery": mode = OverlayMode.FPSWithBattery; return true; default: return false; } } private static bool TryDispatchToRunningInstance(OverlayControlCommand command) { try { using var sharedData = SharedData.OpenExisting(); if (!sharedData.GetValue(out var value)) value = sharedData.NewValue(); ApplyToSharedState(ref value, command); return sharedData.SetValue(value); } catch (Exception ex) { Log.TraceLine("OverlayCommandLine: IPC dispatch failed: {0}", ex.Message); return false; } } private static void ApplyToSharedState(ref OverlayModeSetting value, OverlayControlCommand command) { switch (command.CommandType) { case OverlayControlCommandType.SetMode: value.Desired = command.Mode; value.DesiredEnabled = OverlayEnabled.Yes; return; case OverlayControlCommandType.CycleMode: value.Desired = NextMode(CurrentMode(value)); value.DesiredEnabled = OverlayEnabled.Yes; return; case OverlayControlCommandType.ToggleVisibility: value.DesiredEnabled = CurrentEnabled(value) == OverlayEnabled.Yes ? OverlayEnabled.No : OverlayEnabled.Yes; return; case OverlayControlCommandType.Show: value.DesiredEnabled = OverlayEnabled.Yes; return; case OverlayControlCommandType.Hide: value.DesiredEnabled = OverlayEnabled.No; return; default: return; } } private static OverlayMode CurrentMode(OverlayModeSetting value) { if (Enum.IsDefined(value.Current)) return value.Current; if (Enum.IsDefined(value.Desired)) return value.Desired; return Settings.Default.OSDMode; } private static OverlayEnabled CurrentEnabled(OverlayModeSetting value) { if (Enum.IsDefined(value.CurrentEnabled)) return value.CurrentEnabled; if (Enum.IsDefined(value.DesiredEnabled)) return value.DesiredEnabled; return Settings.Default.ShowOSD ? OverlayEnabled.Yes : OverlayEnabled.No; } private static void ApplyLocally(OverlayControlCommand command) { switch (command.CommandType) { case OverlayControlCommandType.SetMode: Settings.Default.OSDMode = command.Mode; Settings.Default.ShowOSD = true; return; case OverlayControlCommandType.CycleMode: Settings.Default.OSDMode = NextMode(Settings.Default.OSDMode); Settings.Default.ShowOSD = true; return; case OverlayControlCommandType.ToggleVisibility: Settings.Default.ShowOSD = !Settings.Default.ShowOSD; return; case OverlayControlCommandType.Show: Settings.Default.ShowOSD = true; return; case OverlayControlCommandType.Hide: Settings.Default.ShowOSD = false; return; default: return; } } private static OverlayMode NextMode(OverlayMode current) { var values = Enum.GetValues(); var currentIndex = Array.IndexOf(values, current); if (currentIndex < 0) currentIndex = 0; return values[(currentIndex + 1) % values.Length]; } private static bool IsPerformanceOverlayInstanceRunning() { try { using var mutex = Mutex.OpenExisting(RunOnceMutexName); return true; } catch (WaitHandleCannotBeOpenedException) { return false; } catch (UnauthorizedAccessException) { return true; } } } }