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}" />
+
+