diff --git a/src/OpenIPC.Viewer.App/Services/Localizer.cs b/src/OpenIPC.Viewer.App/Services/Localizer.cs index 19ff488..b0c5403 100644 --- a/src/OpenIPC.Viewer.App/Services/Localizer.cs +++ b/src/OpenIPC.Viewer.App/Services/Localizer.cs @@ -255,6 +255,7 @@ private static LangCode DetectSystem() ["Settings.Advanced.VerboseLogging"] = "Verbose logging (debug level)", ["Settings.Advanced.RawConfigEditor"] = "Allow risky device tools (raw config editing, file manager)", + ["Settings.Advanced.SingleInstance"] = "Single instance only (a repeat launch focuses the running window)", ["Settings.Advanced.OpenAppData"] = "Open app data folder", ["Settings.About.Repository"] = "GitHub repository →", @@ -728,6 +729,7 @@ private static LangCode DetectSystem() ["Settings.Advanced.VerboseLogging"] = "Подробные логи (debug)", ["Settings.Advanced.RawConfigEditor"] = "Разрешить рискованные инструменты (raw-конфиг, файловый менеджер)", + ["Settings.Advanced.SingleInstance"] = "Запрещать запуск второй копии (повторный запуск откроет это окно)", ["Settings.Advanced.OpenAppData"] = "Открыть папку данных", ["Settings.About.Repository"] = "Репозиторий на GitHub →", diff --git a/src/OpenIPC.Viewer.App/Services/UserSettings.cs b/src/OpenIPC.Viewer.App/Services/UserSettings.cs index 9320bf7..f0edcad 100644 --- a/src/OpenIPC.Viewer.App/Services/UserSettings.cs +++ b/src/OpenIPC.Viewer.App/Services/UserSettings.cs @@ -94,7 +94,11 @@ public sealed record UserSettings( int GridStillsIntervalSeconds = 10, // Desktop only: closing the main window hides it to the tray icon instead // of quitting (live streams are released while hidden). Off = close quits. - bool CloseToTray = false) + bool CloseToTray = false, + // Desktop only: refuse to start a second copy of the app — a repeat launch + // brings the already-running window to the foreground instead (see + // Desktop/SingleInstanceGuard). Off = any number of copies may run. + bool SingleInstance = false) { public static UserSettings Default => new(); } diff --git a/src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs b/src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs index 2e2ec98..1589191 100644 --- a/src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs +++ b/src/OpenIPC.Viewer.App/ViewModels/SettingsPageViewModel.cs @@ -46,6 +46,7 @@ public sealed partial class SettingsPageViewModel : ViewModelBase [ObservableProperty] private string _language = "system"; [ObservableProperty] private bool _showSplash = true; [ObservableProperty] private bool _closeToTray; + [ObservableProperty] private bool _singleInstance; // Gates the desktop-only toggles (tray) off the shared settings page. public bool IsDesktopPlatform { get; } = @@ -216,6 +217,7 @@ private void Load() Language = s.Language; ShowSplash = s.ShowSplash; CloseToTray = s.CloseToTray; + SingleInstance = s.SingleInstance; SshStrictHostKey = s.SshStrictHostKey; SshDefaultPort = s.SshDefaultPort; SshTerminalFontSize = s.SshTerminalFontSize; @@ -248,6 +250,7 @@ private void Load() partial void OnLanguageChanged(string value) => Persist(); partial void OnShowSplashChanged(bool value) => Persist(); partial void OnCloseToTrayChanged(bool value) => Persist(); + partial void OnSingleInstanceChanged(bool value) => Persist(); partial void OnSshStrictHostKeyChanged(bool value) => Persist(); partial void OnSshDefaultPortChanged(int value) => Persist(); partial void OnSshTerminalFontSizeChanged(int value) => Persist(); @@ -279,6 +282,7 @@ private void Persist() Language = Language, ShowSplash = ShowSplash, CloseToTray = CloseToTray, + SingleInstance = SingleInstance, SshStrictHostKey = SshStrictHostKey, SshDefaultPort = SshDefaultPort, SshTerminalFontSize = SshTerminalFontSize, diff --git a/src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml b/src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml index 6de5509..fea5c58 100644 --- a/src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml +++ b/src/OpenIPC.Viewer.App/Views/Pages/SettingsPage.axaml @@ -417,6 +417,11 @@ Content="{Binding [Settings.Advanced.RawConfigEditor], Source={x:Static svc:Localizer.Instance}}" Foreground="{StaticResource TextPrimaryBrush}" /> + +