From 8fd85061a9bd63e2bf20651b0bcd3703a464e6ba Mon Sep 17 00:00:00 2001 From: keyldev Date: Thu, 2 Jul 2026 19:20:57 +0300 Subject: [PATCH 1/7] fix(desktop): force process exit after shutdown - returning from Main left the process alive in Task Manager on Windows 10 - graceful dispose (bounded 10s) still runs first; Environment.Exit is the backstop --- src/OpenIPC.Viewer.Desktop/Program.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.Desktop/Program.cs b/src/OpenIPC.Viewer.Desktop/Program.cs index 89eb9bf..acbeb98 100644 --- a/src/OpenIPC.Viewer.Desktop/Program.cs +++ b/src/OpenIPC.Viewer.Desktop/Program.cs @@ -24,6 +24,7 @@ public static int Main(string[] args) "Starting OpenIPC.Viewer {Version}", typeof(Program).Assembly.GetName().Version); + int exitCode; try { // Migrations + event ingestion no longer block here (they used to run @@ -31,7 +32,7 @@ public static int Main(string[] args) // showed a frozen, window-less process). The startup splash window now // runs them off the UI thread with visible progress — see // App.OnFrameworkInitializationCompleted / StartupViewModel. - return BuildAvaloniaApp() + exitCode = BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); } catch (Exception ex) @@ -62,6 +63,13 @@ public static int Main(string[] args) logger.LogWarning("Service disposal timed out during shutdown; forcing exit"); } } + + // Returning from Main is not enough: any foreground thread left behind by + // a native interop layer keeps the closed app alive in Task Manager + // (observed on Windows 10). Everything is flushed and disposed by now, so + // terminate unconditionally. + Environment.Exit(exitCode); + return exitCode; // unreachable — Environment.Exit does not return } private static AppBuilder BuildAvaloniaApp() => From 8c18310a0e72def5bcbca1073ac0e6e8f5efae56 Mon Sep 17 00:00:00 2001 From: keyldev Date: Thu, 2 Jul 2026 19:21:15 +0300 Subject: [PATCH 2/7] feat(grid): keep a 4px inset in kiosk fullscreen - full-bleed grid pressed against the physical screen edges - mobile fullscreen video stays edge-to-edge --- src/OpenIPC.Viewer.App/Views/MainView.axaml.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.App/Views/MainView.axaml.cs b/src/OpenIPC.Viewer.App/Views/MainView.axaml.cs index 7d3cf58..234645e 100644 --- a/src/OpenIPC.Viewer.App/Views/MainView.axaml.cs +++ b/src/OpenIPC.Viewer.App/Views/MainView.axaml.cs @@ -36,6 +36,9 @@ public partial class MainView : UserControl private static readonly Thickness WidePadding = new(24); private static readonly Thickness NarrowPadding = new(12); + // Desktop kiosk keeps a hairline inset so the grid doesn't press against + // the physical screen edges; mobile fullscreen video stays full-bleed. + private static readonly Thickness KioskPadding = new(4); private bool _isWideLayout = true; private bool _isFullscreen; @@ -161,7 +164,8 @@ private void UpdateChrome() { var showSidebar = _isWideLayout && !_isFullscreen; var showBottomNav = !_isWideLayout && !_isFullscreen; - var padding = _isFullscreen ? new Thickness(0) + var padding = _isFullscreen + ? (_vm?.KioskMode == true ? KioskPadding : new Thickness(0)) : _isWideLayout ? WidePadding : NarrowPadding; SetAndRaise(ShowSidebarProperty, ref _showSidebar, showSidebar); From 9f8e2c23e6c4887cfff40c5261c7f9e4ad601a73 Mon Sep 17 00:00:00 2001 From: keyldev Date: Thu, 2 Jul 2026 19:21:31 +0300 Subject: [PATCH 3/7] feat(editor): XM (Xiongmai) RTSP URI preset - one-click fill of NETSurveillance-style main/sub RTSP URLs - uses the credential fields, falls back to stock admin/empty - localizer strings for this branch (incl. upcoming tray items) --- src/OpenIPC.Viewer.App/Services/Localizer.cs | 16 ++++++++++++++++ .../ViewModels/Dialogs/CameraEditorViewModel.cs | 13 +++++++++++++ .../Views/Dialogs/CameraEditorContent.axaml | 10 +++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/OpenIPC.Viewer.App/Services/Localizer.cs b/src/OpenIPC.Viewer.App/Services/Localizer.cs index 2836175..e4d8d5b 100644 --- a/src/OpenIPC.Viewer.App/Services/Localizer.cs +++ b/src/OpenIPC.Viewer.App/Services/Localizer.cs @@ -76,6 +76,11 @@ private static LangCode DetectSystem() ["Startup.Failed"] = "Initialization failed", ["Startup.Retry"] = "Retry", ["Startup.Exit"] = "Exit", + ["Tray.Open"] = "Open viewer", + ["Tray.AddCamera"] = "Add camera…", + ["Tray.Settings"] = "Settings", + ["Tray.About"] = "About", + ["Tray.Exit"] = "Exit", ["Analytics.Overview"] = "Engine", ["Analytics.Status"] = "Status", ["Analytics.Status.NotStarted"] = "Not started", @@ -212,6 +217,7 @@ private static LangCode DetectSystem() ["Settings.Appearance"] = "Appearance", ["Settings.Appearance.Language"] = "Language", ["Settings.Appearance.ShowSplash"] = "Show animated splash screen on launch", + ["Settings.Appearance.CloseToTray"] = "Keep running in the tray when the window is closed", ["Settings.Video"] = "Video", ["Settings.Recording"] = "Recording", ["Settings.Discovery"] = "Discovery", @@ -334,6 +340,8 @@ private static LangCode DetectSystem() ["CameraEditor.Placeholder.SshPort"] = "22", ["CameraEditor.NoGroup"] = "(no group)", ["CameraEditor.Button.AutoDeriveRtsp"] = "Auto from host", + ["CameraEditor.Button.AutoDeriveXmRtsp"] = "XM preset", + ["CameraEditor.Tooltip.AutoDeriveXmRtsp"] = "Fill in the RTSP URLs used by XM/Xiongmai (NETSurveillance) cameras", ["CameraEditor.Button.TestConnection"] = "Test connection", ["CameraEditor.Status.Connecting"] = "Connecting…", ["CameraEditor.Status.OkFormat"] = "OK — {0}x{1}", @@ -539,6 +547,11 @@ private static LangCode DetectSystem() ["Startup.Failed"] = "Ошибка инициализации", ["Startup.Retry"] = "Повторить", ["Startup.Exit"] = "Выход", + ["Tray.Open"] = "Открыть", + ["Tray.AddCamera"] = "Добавить камеру…", + ["Tray.Settings"] = "Настройки", + ["Tray.About"] = "О программе", + ["Tray.Exit"] = "Выход", ["Analytics.Overview"] = "Движок", ["Analytics.Status"] = "Статус", ["Analytics.Status.NotStarted"] = "Не запущен", @@ -675,6 +688,7 @@ private static LangCode DetectSystem() ["Settings.Appearance"] = "Внешний вид", ["Settings.Appearance.Language"] = "Язык", ["Settings.Appearance.ShowSplash"] = "Показывать анимированную заставку при запуске", + ["Settings.Appearance.CloseToTray"] = "При закрытии окна оставаться в трее", ["Settings.Video"] = "Видео", ["Settings.Recording"] = "Запись", ["Settings.Discovery"] = "Поиск", @@ -797,6 +811,8 @@ private static LangCode DetectSystem() ["CameraEditor.Placeholder.SshPort"] = "22", ["CameraEditor.NoGroup"] = "(без группы)", ["CameraEditor.Button.AutoDeriveRtsp"] = "Подставить из хоста", + ["CameraEditor.Button.AutoDeriveXmRtsp"] = "Пресет XM", + ["CameraEditor.Tooltip.AutoDeriveXmRtsp"] = "Подставить RTSP-адреса камер XM/Xiongmai (NETSurveillance)", ["CameraEditor.Button.TestConnection"] = "Проверить", ["CameraEditor.Status.Connecting"] = "Подключение…", ["CameraEditor.Status.OkFormat"] = "OK — {0}x{1}", diff --git a/src/OpenIPC.Viewer.App/ViewModels/Dialogs/CameraEditorViewModel.cs b/src/OpenIPC.Viewer.App/ViewModels/Dialogs/CameraEditorViewModel.cs index 5403ce2..8dde719 100644 --- a/src/OpenIPC.Viewer.App/ViewModels/Dialogs/CameraEditorViewModel.cs +++ b/src/OpenIPC.Viewer.App/ViewModels/Dialogs/CameraEditorViewModel.cs @@ -159,6 +159,19 @@ private void AutoDeriveRtsp() RtspMainText = $"rtsp://{Host.Trim()}/"; } + // XM/Xiongmai (NETSurveillance) firmware embeds the login in the RTSP path + // and serves main/sub as stream=0/1. Uses the credential fields when set, + // falling back to the stock "admin" + empty password those cameras ship with. + [RelayCommand] + private void AutoDeriveXmRtsp() + { + if (string.IsNullOrWhiteSpace(Host)) return; + var host = Host.Trim(); + var user = string.IsNullOrWhiteSpace(Username) ? "admin" : Username.Trim(); + RtspMainText = $"rtsp://{host}:554/user={user}&password={Password}&channel=1&stream=0.sdp?real_stream"; + RtspSubText = $"rtsp://{host}:554/user={user}&password={Password}&channel=1&stream=1.sdp?real_stream"; + } + [RelayCommand(CanExecute = nameof(CanTestConnection))] private async Task TestConnectionAsync() { diff --git a/src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml b/src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml index 45abd41..f6c02c0 100644 --- a/src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml +++ b/src/OpenIPC.Viewer.App/Views/Dialogs/CameraEditorContent.axaml @@ -43,7 +43,7 @@ - +