11// Copyright (c) Max Kagamine
22// Licensed under the Apache License, Version 2.0
33
4+ using AutoAudioSwitcher . Properties ;
45using Microsoft . Extensions . Configuration ;
56using Microsoft . Extensions . DependencyInjection ;
67using Microsoft . Win32 ;
78using Serilog ;
89using Serilog . Core ;
910using Serilog . Events ;
1011using Serilog . Templates ;
12+ using System . Diagnostics ;
1113using System . Reactive ;
1214using System . Reactive . Linq ;
1315
@@ -16,7 +18,13 @@ namespace AutoAudioSwitcher;
1618internal sealed class Program
1719{
1820 private static readonly TimeSpan WindowsAutomaticDefaultDeviceChangeThreshold = TimeSpan . FromSeconds ( 2 ) ;
21+
22+ private const string LogsDirectory = "logs" ;
23+
1924 private static readonly LoggingLevelSwitch levelSwitch = new ( LogEventLevel . Error ) ;
25+ private static ServiceProvider ? provider ;
26+ private static ILogger ? logger ;
27+ private static Mutex ? singleInstanceMutex ;
2028
2129 private static ServiceProvider ConfigureServices ( )
2230 {
@@ -39,7 +47,7 @@ private static ServiceProvider ConfigureServices()
3947 . MinimumLevel . Debug ( )
4048 . WriteTo . Debug ( )
4149 . WriteTo . File (
42- path : "error .log",
50+ path : Path . Combine ( LogsDirectory , " .log") ,
4351 formatter : new ExpressionTemplate (
4452 "{@t:yyyy-MM-dd HH:mm:ss.fff zzz} [{@l:u3}] {#if SourceContext is not null}[{Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}] {#end}{@m}\n {@x}" ) ,
4553 levelSwitch : levelSwitch ,
@@ -60,14 +68,20 @@ private static ServiceProvider ConfigureServices()
6068 [ STAThread ]
6169 public static void Main ( )
6270 {
63- Mutex singleInstance = new ( true , "f09f929b-e98f-a1e9-9fb3-e383aae383b3" /* This is my favorite GUID */ , out bool createdNew ) ;
71+ singleInstanceMutex = new ( true , "f09f929b-e98f-a1e9-9fb3-e383aae383b3" /* This is my favorite GUID */ , out bool createdNew ) ;
6472 if ( ! createdNew )
6573 {
6674 return ;
6775 }
6876
77+ Environment . CurrentDirectory = AppContext . BaseDirectory ;
78+
6979 //CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = new("ja-JP");
7080
81+ Application . SetUnhandledExceptionMode ( UnhandledExceptionMode . CatchException ) ;
82+ Application . ThreadException += ( sender , e ) => HandleCrash ( e . Exception ) ;
83+ AppDomain . CurrentDomain . UnhandledException += ( sender , e ) => HandleCrash ( ( Exception ) e . ExceptionObject ) ;
84+
7185 ApplicationConfiguration . Initialize ( ) ;
7286
7387 // Automatic dark mode is restricted to Win11+ for no good reason. It uses the "AppsUseLightTheme" setting
@@ -79,18 +93,10 @@ public static void Main()
7993 Application . SetColorMode ( IsSystemDarkModeEnabled ( ) ? SystemColorMode . Dark : SystemColorMode . Classic ) ;
8094 } ;
8195
82- Environment . CurrentDirectory = AppContext . BaseDirectory ;
83- ServiceProvider provider = ConfigureServices ( ) ;
84-
85- var logger = provider . GetRequiredService < ILogger > ( ) ;
96+ provider = ConfigureServices ( ) ;
97+ logger = provider . GetRequiredService < ILogger > ( ) ;
8698 logger . Information ( "Application is starting." ) ;
8799
88- AppDomain . CurrentDomain . UnhandledException += ( object sender , UnhandledExceptionEventArgs e ) =>
89- {
90- logger . Fatal ( ( Exception ) e . ExceptionObject , "Unhandled exception." ) ;
91- provider . Dispose ( ) ;
92- } ;
93-
94100 Application . ApplicationExit += ( _ , _ ) =>
95101 {
96102 logger . Information ( "Application is exiting." ) ;
@@ -204,4 +210,56 @@ public static bool IsSystemDarkModeEnabled()
204210
205211 return systemUsesLightTheme == 0 ;
206212 }
213+
214+ private static void HandleCrash ( Exception ex )
215+ {
216+ try
217+ {
218+ logger ? . Fatal ( ex , "Unhandled exception." ) ;
219+ provider ? . Dispose ( ) ;
220+ }
221+ catch { }
222+
223+ try
224+ {
225+ TaskDialogCommandLinkButton restartButton = new ( Resources . Restart ) ;
226+ TaskDialogCommandLinkButton exitButton = new ( Resources . Exit ) ;
227+ TaskDialogCommandLinkButton logsButton = new ( Resources . OpenLogDirectory , allowCloseDialog : false ) ;
228+
229+ string str = ex . ToString ( ) ;
230+ var stackTraceIndex = str . IndexOf ( " at " , StringComparison . OrdinalIgnoreCase ) ;
231+ string text = stackTraceIndex > 0 ? str [ ..stackTraceIndex ] . TrimEnd ( ) : str ;
232+ TaskDialogExpander ? stackTrace = stackTraceIndex > 0 ? new ( str [ stackTraceIndex ..] ) : null ;
233+
234+ text = text . Replace ( "\\ " , "\\ \u200B " ) ; // Zero width space to allow paths to wrap instead of getting shortened with an ellipsis
235+
236+ TaskDialogPage taskDialog = new ( )
237+ {
238+ Heading = Resources . UnhandledException ,
239+ Text = text ,
240+ Expander = stackTrace ,
241+ SizeToContent = true ,
242+ Caption = Resources . ProgramName ,
243+ Icon = TaskDialogIcon . Error ,
244+ Buttons = logger is null ? // Don't show logs button if program crashed during init before logger setup
245+ [ restartButton , exitButton ] :
246+ [ restartButton , exitButton , logsButton ] ,
247+ } ;
248+
249+ logsButton . Click += ( _ , _ ) =>
250+ {
251+ Process . Start ( new ProcessStartInfo ( LogsDirectory ) { UseShellExecute = true } ) ;
252+ } ;
253+
254+ if ( TaskDialog . ShowDialog ( taskDialog ) == restartButton )
255+ {
256+ singleInstanceMutex ? . Dispose ( ) ;
257+ Application . Restart ( ) ;
258+ return ;
259+ }
260+ }
261+ catch { }
262+
263+ Application . Exit ( ) ;
264+ }
207265}
0 commit comments