From ed9c2cc409769dae029ee09577a8028ff3a452f7 Mon Sep 17 00:00:00 2001 From: William Tanksley Jr Date: Mon, 26 Oct 2026 19:56:01 -0700 Subject: [PATCH] Fix macOS launch crash from SkiaSharp mismatch and harden crash handler (#2115) (#2116) - Pin SkiaSharp back to 3.119.4 in LibationUiBase to match Avalonia.Skia's expected native Skia milestone and restore the sk_fontmgr_ref_default export. - Wrap BuildAvaloniaApp()?.SetupWithoutStarting() in Program.cs in try/catch to prevent InvalidOperationException ('Setup was already called on one of AppBuilder instances') when startup faults inside AppBuilder.Setup(). - Add TryShowFallbackAlert in Program.cs to output to stderr and trigger a native OS dialog (osascript display alert on macOS, MessageBoxW on Windows) when Avalonia cannot initialize its UI subsystem. --- Source/LibationAvalonia/Program.cs | 58 ++++++++++++++++++++- Source/LibationUiBase/LibationUiBase.csproj | 2 +- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/Source/LibationAvalonia/Program.cs b/Source/LibationAvalonia/Program.cs index 6610e122..952a0e92 100644 --- a/Source/LibationAvalonia/Program.cs +++ b/Source/LibationAvalonia/Program.cs @@ -146,7 +146,19 @@ static class Program } //Run setup if needed so that we can show the crash dialog - BuildAvaloniaApp()?.SetupWithoutStarting(); + try + { + BuildAvaloniaApp()?.SetupWithoutStarting(); + } + catch (Exception ex) + { + try + { + if (Configuration.Instance.SerilogInitialized) + Serilog.Log.Logger.Warning(ex, "Could not initialize Avalonia for crash dialog."); + } + catch { /* ignore */ } + } try { @@ -154,10 +166,54 @@ static class Program } catch (Exception ex) { + TryShowFallbackAlert(exception, crashLogFile, submittedToLogger); Environment.FailFast("Fatal error displaying crash message", new AggregateException(ex, exception)); } } + private static void TryShowFallbackAlert(Exception exception, string? crashLogFile, bool submittedToLogger) + { + var fatalMessage = StartupAssemblyBootstrap.GetFatalStartupMessage( + exception, + new FatalStartupMessage( + "Libation Crash", + $""" + Libation encountered a fatal error and must close. + + {DescribeCrashLog(crashLogFile, submittedToLogger)} + """)); + + try + { + Console.Error.WriteLine($"{fatalMessage.Title}: {fatalMessage.Body}"); + Console.Error.WriteLine(exception); + } + catch { /* ignore */ } + + if (Configuration.IsMacOs) + { + try + { + var escapedTitle = fatalMessage.Title.Replace("\\", "\\\\").Replace("\"", "\\\""); + var escapedMsg = $"{fatalMessage.Body}\n\n{exception.Message}".Replace("\\", "\\\\").Replace("\"", "\\\""); + var script = $"display alert \"{escapedTitle}\" message \"{escapedMsg}\" as critical buttons {{\"OK\"}} default button \"OK\""; + Process.Start(new ProcessStartInfo("osascript", ["-e", script]) { CreateNoWindow = true, UseShellExecute = false })?.WaitForExit(10000); + } + catch { /* ignore */ } + } + else if (Configuration.IsWindows) + { + try + { + MessageBoxW(IntPtr.Zero, $"{fatalMessage.Body}\r\n\r\n{exception.Message}", fatalMessage.Title, 0x10 /* MB_ICONERROR */); + } + catch { /* ignore */ } + } + } + + [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "MessageBoxW", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] + private static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type); + private static void DisplayErrorMessage(Exception exception, string? crashLogFile, bool submittedToLogger) { var dispatcher = new DispatcherFrame(); diff --git a/Source/LibationUiBase/LibationUiBase.csproj b/Source/LibationUiBase/LibationUiBase.csproj index cec61b91..c3c51eb6 100644 --- a/Source/LibationUiBase/LibationUiBase.csproj +++ b/Source/LibationUiBase/LibationUiBase.csproj @@ -12,7 +12,7 @@ - +