From 2b192e8d904ec9c6feb31b2c61cb0e7cce17c576 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 16 Apr 2026 13:13:36 +0200 Subject: [PATCH] Add crash dialog This PR adds a dialog window that is displayed if the TrayIcon process, so the cause is immediately visible. Prior to this PR, the user was required to locate the crashlog file and open this to figure out why Duplicati silently crashed. --- .../GUI/Duplicati.GUI.TrayIcon/ErrorDialog.cs | 165 ++++++++++++++++++ .../GUI/Duplicati.GUI.TrayIcon/Program.cs | 16 ++ Executables/Duplicati.GUI.TrayIcon/Program.cs | 3 +- 3 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 Duplicati/GUI/Duplicati.GUI.TrayIcon/ErrorDialog.cs diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/ErrorDialog.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ErrorDialog.cs new file mode 100644 index 000000000..05461c6e3 --- /dev/null +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/ErrorDialog.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +#nullable enable + +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Media; +using Avalonia.Layout; +using Avalonia.Themes.Fluent; +using ScrollBarVisibility = Avalonia.Controls.Primitives.ScrollBarVisibility; + +namespace Duplicati.GUI.TrayIcon +{ + /// + /// Helper class for showing error dialogs + /// + public static class ErrorDialog + { + /// + /// Shows an error dialog for the crash, being very defensive to avoid additional crashes. + /// This should only be called when the hosted server instance is crashing. + /// + /// The exception to show + public static void ShowErrorDialog(Exception ex) + { + + try + { + // Build a simple Avalonia application to show the dialog + var builder = AppBuilder + .Configure() + .UsePlatformDetect(); + + var app = builder.SetupWithoutStarting().Instance; + if (app == null) + return; + + // Apply a simple theme so controls look native + app.Styles.Add(new FluentTheme()); + + // Create the message box window + var window = new Window + { + Title = "Duplicati TrayIcon Error", + Width = 600, + Height = 400, + WindowStartupLocation = WindowStartupLocation.CenterScreen, + CanResize = true, + ShowInTaskbar = false, + Topmost = true + }; + + // Create the content + var stackPanel = new StackPanel + { + Margin = new Thickness(20), + Spacing = 10 + }; + + var titleBlock = new TextBlock + { + Text = "A fatal error occurred", + FontSize = 16, + FontWeight = FontWeight.Bold, + TextWrapping = TextWrapping.Wrap + }; + + var messageBlock = new TextBlock + { + Text = ex.Message, + TextWrapping = TextWrapping.Wrap + }; + + // Details label + var detailsLabel = new TextBlock + { + Text = "Details:", + FontWeight = FontWeight.Bold, + Margin = new Thickness(0, 10, 0, 0) + }; + + // Scrollable text area with full exception details + var exceptionText = new TextBox + { + Text = ex.ToString(), + IsReadOnly = true, + TextWrapping = TextWrapping.NoWrap, + AcceptsReturn = true, + FontFamily = new FontFamily("Consolas, Monaco, Courier New, monospace"), + FontSize = 10 + }; + + // Explicitly wrap in a ScrollViewer for proper scrolling + var scrollViewer = new ScrollViewer + { + Content = exceptionText, + HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, + VerticalScrollBarVisibility = ScrollBarVisibility.Auto, + Height = 200 + }; + + var buttonPanel = new StackPanel + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Center, + Margin = new Thickness(0, 20, 0, 0) + }; + + var okButton = new Button + { + Content = "OK", + Width = 100, + HorizontalContentAlignment = HorizontalAlignment.Center + }; + + okButton.Click += (_, _) => + { + try + { + window.Close(); + } + catch + { + // Ignore errors closing window + } + }; + buttonPanel.Children.Add(okButton); + + stackPanel.Children.Add(titleBlock); + stackPanel.Children.Add(messageBlock); + stackPanel.Children.Add(detailsLabel); + stackPanel.Children.Add(scrollViewer); + stackPanel.Children.Add(buttonPanel); + + window.Content = stackPanel; + + app.Run(window); + } + catch + { + } + } + } +} diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs index 07003294d..7be44850b 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/Program.cs @@ -98,6 +98,22 @@ namespace Duplicati.GUI.TrayIcon /// [STAThread] public static int Main(string[] _args) + { + try + { + return RunMain(_args); + } + catch (Exception ex) + { + Library.Crashlog.CrashlogHelper.LogCrashException(ex); + ErrorDialog.ShowErrorDialog(ex); + + Environment.Exit(1); + return 1; + } + } + + private static int RunMain(string[] _args) { PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.TrayIcon); var args = new List(_args); diff --git a/Executables/Duplicati.GUI.TrayIcon/Program.cs b/Executables/Duplicati.GUI.TrayIcon/Program.cs index fe55267f0..176d84c92 100644 --- a/Executables/Duplicati.GUI.TrayIcon/Program.cs +++ b/Executables/Duplicati.GUI.TrayIcon/Program.cs @@ -18,7 +18,6 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Crashlog; namespace Duplicati.GUI.TrayIcon.Net10 { @@ -26,6 +25,6 @@ namespace Duplicati.GUI.TrayIcon.Net10 public static class Program { public static int Main(string[] args) - => CrashlogHelper.WrapWithCrashLog(() => Duplicati.GUI.TrayIcon.Program.Main(args)); + => Duplicati.GUI.TrayIcon.Program.Main(args); } } \ No newline at end of file