Refactor common entrypoint code into GuiEntryPoint

This commit is contained in:
Ben Olden-Cooligan 2023-10-08 17:14:46 -07:00
parent 068cbaf24b
commit 5d2ac21185
12 changed files with 147 additions and 181 deletions

View File

@ -1,15 +1,9 @@
using Autofac;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
using NAPS2.Modules;
using NAPS2.Remoting.Worker;
using NAPS2.Scan;
using UnhandledExceptionEventArgs = Eto.UnhandledExceptionEventArgs;
using NAPS2.Modules;
namespace NAPS2.EntryPoints;
/// <summary>
/// The entry point logic for NAPS2.exe, the NAPS2 GUI.
/// The entry point logic for the Gtk NAPS2 executable.
/// </summary>
public static class GtkEntryPoint
{
@ -24,50 +18,6 @@ public static class GtkEntryPoint
return WorkerEntryPoint.Run(args.Skip(1).ToArray(), new GtkImagesModule());
}
// Initialize Autofac (the DI framework)
var container = AutoFacHelper.FromModules(
new CommonModule(), new GtkImagesModule(), new GtkModule(), new RecoveryModule(), new ContextModule());
Paths.ClearTemp();
// Set up basic application configuration
container.Resolve<CultureHelper>().SetCulturesFromConfig();
TaskScheduler.UnobservedTaskException += UnhandledTaskException;
GLib.ExceptionManager.UnhandledException += UnhandledGtkException;
Trace.Listeners.Add(new ConsoleTraceListener());
// Start a pending worker process
container.Resolve<IWorkerFactory>().Init(container.Resolve<ScanningContext>());
// Show the main form
var application = EtoPlatform.Current.CreateApplication();
application.UnhandledException += UnhandledException;
var formFactory = container.Resolve<IFormFactory>();
var desktop = formFactory.Create<DesktopForm>();
application.Run(desktop);
return 0;
}
private static void UnhandledGtkException(GLib.UnhandledExceptionArgs e)
{
if (e.IsTerminating)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.ExceptionObject as Exception ?? new Exception());
}
else
{
Log.ErrorException("An unhandled error occurred.", e.ExceptionObject as Exception ?? new Exception());
}
}
private static void UnhandledTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.Exception);
e.SetObserved();
}
private static void UnhandledException(object? sender, UnhandledExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the application to close.", e.ExceptionObject as Exception ?? new Exception());
return GuiEntryPoint.Run(args, new GtkImagesModule(), new GtkModule());
}
}

View File

@ -18,6 +18,23 @@ public class GtkEtoPlatform : EtoPlatform
public override bool IsGtk => true;
public override void InitializePlatform()
{
GLib.ExceptionManager.UnhandledException += UnhandledGtkException;
}
private static void UnhandledGtkException(GLib.UnhandledExceptionArgs e)
{
if (e.IsTerminating)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.ExceptionObject as Exception ?? new Exception());
}
else
{
Log.ErrorException("An unhandled error occurred.", e.ExceptionObject as Exception ?? new Exception());
}
}
public override Application CreateApplication()
{
var application = new Application(Platforms.Gtk);

View File

@ -1,15 +1,9 @@
using Autofac;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
using NAPS2.Modules;
using NAPS2.Remoting.Worker;
using NAPS2.Scan;
using UnhandledExceptionEventArgs = Eto.UnhandledExceptionEventArgs;
using NAPS2.Modules;
namespace NAPS2.EntryPoints;
/// <summary>
/// The entry point logic for NAPS2.exe, the NAPS2 GUI.
/// The entry point logic for the Mac NAPS2 executable.
/// </summary>
public static class MacEntryPoint
{
@ -24,55 +18,6 @@ public static class MacEntryPoint
return MacWorkerEntryPoint.Run(args.Skip(1).ToArray());
}
// We start the process as a background process (by setting LSBackgroundOnly in Info.plist) and only turn it
// into a foreground process once we know we're not in worker or console mode. This ensures workers don't have
// a chance to show in the dock.
MacProcessHelper.TransformThisProcessToForeground();
// Initialize Autofac (the DI framework)
var container = AutoFacHelper.FromModules(
new CommonModule(), new MacImagesModule(), new MacModule(), new RecoveryModule(), new ContextModule());
Paths.ClearTemp();
// Set up basic application configuration
container.Resolve<CultureHelper>().SetCulturesFromConfig();
TaskScheduler.UnobservedTaskException += UnhandledTaskException;
Trace.Listeners.Add(new ConsoleTraceListener());
Runtime.MarshalManagedException += (_, eventArgs) =>
{
Log.ErrorException("Marshalling managed exception", eventArgs.Exception);
eventArgs.ExceptionMode = MarshalManagedExceptionMode.ThrowObjectiveCException;
};
Runtime.MarshalObjectiveCException += (_, eventArgs) =>
{
Log.Error($"Marshalling ObjC exception: {eventArgs.Exception.Description}");
};
// Start a pending worker process
container.Resolve<IWorkerFactory>().Init(container.Resolve<ScanningContext>());
// Show the main form
var application = EtoPlatform.Current.CreateApplication();
application.UnhandledException += UnhandledException;
var formFactory = container.Resolve<IFormFactory>();
var desktop = formFactory.Create<DesktopForm>();
// Invoker.Current = new WinFormsInvoker(desktop.ToNative());
application.Run(desktop);
return 0;
}
private static void UnhandledTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.Exception);
e.SetObserved();
}
private static void UnhandledException(object? sender, UnhandledExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the application to close.",
e.ExceptionObject as Exception ?? new Exception());
return GuiEntryPoint.Run(args, new MacImagesModule(), new MacModule());
}
}

View File

@ -13,6 +13,24 @@ public class MacEtoPlatform : EtoPlatform
{
public override bool IsMac => true;
public override void InitializePlatform()
{
// We start the process as a background process (by setting LSBackgroundOnly in Info.plist) and only turn it
// into a foreground process once we know we're not in worker or console mode. This ensures workers don't have
// a chance to show in the dock.
MacProcessHelper.TransformThisProcessToForeground();
Runtime.MarshalManagedException += (_, eventArgs) =>
{
Log.ErrorException("Marshalling managed exception", eventArgs.Exception);
eventArgs.ExceptionMode = MarshalManagedExceptionMode.ThrowObjectiveCException;
};
Runtime.MarshalObjectiveCException += (_, eventArgs) =>
{
Log.Error($"Marshalling ObjC exception: {eventArgs.Exception.Description}");
};
}
public override Application CreateApplication()
{
return new Application(Platforms.macOS);

View File

@ -1,16 +1,4 @@
using System.Reflection;
using System.Threading;
using Autofac;
using Eto.Forms;
using Eto.WinForms.Forms;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
using NAPS2.EtoForms.WinForms;
using NAPS2.Modules;
using NAPS2.Platform.Windows;
using NAPS2.Remoting.Worker;
using NAPS2.Scan;
using wf = System.Windows.Forms;
using NAPS2.Modules;
namespace NAPS2.EntryPoints;
@ -26,53 +14,6 @@ public static class WinFormsEntryPoint
return WindowsWorkerEntryPoint.Run(args.Skip(1).ToArray());
}
// Initialize Autofac (the DI framework)
var container = AutoFacHelper.FromModules(
new CommonModule(), new GdiModule(), new WinFormsModule(), new RecoveryModule(), new ContextModule());
Paths.ClearTemp();
// Parse the command-line arguments and see if we're doing something other than displaying the main form
var lifecycle = container.Resolve<WindowsApplicationLifecycle>();
lifecycle.ParseArgs(args);
lifecycle.ExitIfRedundant();
// Start a pending worker process
container.Resolve<IWorkerFactory>().Init(container.Resolve<ScanningContext>());
// Set up basic application configuration
container.Resolve<CultureHelper>().SetCulturesFromConfig();
// TODO: Unify unhandled exception handling across platforms
wf.Application.ThreadException += UnhandledException;
TaskScheduler.UnobservedTaskException += UnhandledTaskException;
// Show the main form
var application = EtoPlatform.Current.CreateApplication();
var formFactory = container.Resolve<IFormFactory>();
var desktop = formFactory.Create<DesktopForm>();
// We manually run an application rather than using eto as that lets us change the main form
// TODO: PR for eto to handle mainform changes correctly
application.MainForm = desktop;
desktop.Show();
var appContext = new wf.ApplicationContext(desktop.ToNative());
Invoker.Current = new WinFormsInvoker(() => appContext.MainForm!);
WinFormsDesktopForm.ApplicationContext = appContext;
var setOptionsMethod =
typeof(ApplicationHandler).GetMethod("SetOptions", BindingFlags.Instance | BindingFlags.NonPublic);
setOptionsMethod!.Invoke(application.Handler, Array.Empty<object>());
wf.Application.Run(appContext);
return 0;
}
private static void UnhandledTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.Exception);
e.SetObserved();
}
private static void UnhandledException(object? sender, ThreadExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the application to close.", e.Exception);
return GuiEntryPoint.Run(args, new GdiModule(), new WinFormsModule());
}
}

View File

@ -1,8 +1,4 @@
using Autofac;
using CommandLine;
using NAPS2.Automation;
using NAPS2.Modules;
using NAPS2.Remoting.Worker;
using NAPS2.Modules;
namespace NAPS2.EntryPoints;

View File

@ -1,10 +1,13 @@
using System.Drawing.Imaging;
using System.Globalization;
using System.Reflection;
using Eto.Drawing;
using Eto.Forms;
using Eto.WinForms;
using Eto.WinForms.Forms;
using Eto.WinForms.Forms.Controls;
using NAPS2.EtoForms.Layout;
using NAPS2.EtoForms.Ui;
using NAPS2.EtoForms.Widgets;
using NAPS2.Images.Gdi;
using sd = System.Drawing;
@ -27,6 +30,21 @@ public class WinFormsEtoPlatform : EtoPlatform
return new Application(Eto.Platforms.WinForms);
}
public override void RunApplication(Application application, Form mainForm)
{
// We manually run an application rather than using eto as that lets us change the main form
// TODO: PR for eto to handle mainform changes correctly
application.MainForm = mainForm;
mainForm.Show();
var appContext = new wf.ApplicationContext(mainForm.ToNative());
Invoker.Current = new WinFormsInvoker(() => appContext.MainForm!);
WinFormsDesktopForm.ApplicationContext = appContext;
var setOptionsMethod =
typeof(ApplicationHandler).GetMethod("SetOptions", BindingFlags.Instance | BindingFlags.NonPublic);
setOptionsMethod!.Invoke(application.Handler, Array.Empty<object>());
wf.Application.Run(appContext);
}
public override IListView<T> CreateListView<T>(ListViewBehavior<T> behavior) =>
new WinFormsListView<T>(behavior);

View File

@ -3,7 +3,7 @@ using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
using NAPS2.EtoForms.WinForms;
using NAPS2.ImportExport;
using NAPS2.WinForms;
using NAPS2.Platform.Windows;
namespace NAPS2.Modules;
@ -13,6 +13,7 @@ public class WinFormsModule : GuiModule
{
base.Load(builder);
builder.RegisterType<WindowsApplicationLifecycle>().As<ApplicationLifecycle>();
builder.RegisterType<PrintDocumentPrinter>().As<IScannedImagePrinter>();
builder.RegisterType<WinFormsDarkModeProvider>().As<IDarkModeProvider>().SingleInstance();

View File

@ -8,7 +8,7 @@ namespace NAPS2.Platform.Windows;
/// <summary>
/// A class to help manage the lifecycle of the NAPS2 GUI.
/// </summary>
public class WindowsApplicationLifecycle
public class WindowsApplicationLifecycle : ApplicationLifecycle
{
private readonly StillImage _sti;
private readonly WindowsEventLogger _windowsEventLogger;
@ -28,7 +28,7 @@ public class WindowsApplicationLifecycle
/// Parses the NAPS2 GUI command-line arguments.
/// </summary>
/// <param name="args"></param>
public void ParseArgs(string[] args)
public override void ParseArgs(string[] args)
{
bool silent = args.Any(x => x.Equals("/Silent", StringComparison.InvariantCultureIgnoreCase));
bool noElevation = args.Any(x => x.Equals("/NoElevation", StringComparison.InvariantCultureIgnoreCase));
@ -150,7 +150,7 @@ public class WindowsApplicationLifecycle
/// <summary>
/// May terminate the NAPS2 GUI based on the command-line arguments and running processes, sending messages to other processes if appropriate.
/// </summary>
public void ExitIfRedundant()
public override void ExitIfRedundant()
{
if (_sti.ShouldRegister || _sti.ShouldUnregister || _shouldCreateEventSource)
{

View File

@ -0,0 +1,59 @@
using Autofac;
using NAPS2.EtoForms;
using NAPS2.EtoForms.Ui;
using NAPS2.Modules;
using NAPS2.Remoting.Worker;
using NAPS2.Scan;
namespace NAPS2.EntryPoints;
/// <summary>
/// The entry point for the main NAPS2 executable.
/// </summary>
public static class GuiEntryPoint
{
public static int Run(string[] args, Module imageModule, Module guiModule)
{
// Initialize Autofac (the DI framework)
var container = AutoFacHelper.FromModules(
new CommonModule(), imageModule, guiModule, new RecoveryModule(), new ContextModule());
Paths.ClearTemp();
// Parse the command-line arguments and see if we're doing something other than displaying the main form
var lifecycle = container.Resolve<ApplicationLifecycle>();
lifecycle.ParseArgs(args);
lifecycle.ExitIfRedundant();
EtoPlatform.Current.InitializePlatform();
// Set up basic application configuration
container.Resolve<CultureHelper>().SetCulturesFromConfig();
TaskScheduler.UnobservedTaskException += UnhandledTaskException;
Trace.Listeners.Add(new ConsoleTraceListener());
// Start a pending worker process
container.Resolve<IWorkerFactory>().Init(container.Resolve<ScanningContext>());
// Show the main form
var application = EtoPlatform.Current.CreateApplication();
application.UnhandledException += UnhandledException;
var formFactory = container.Resolve<IFormFactory>();
var desktop = formFactory.Create<DesktopForm>();
EtoPlatform.Current.RunApplication(application, desktop);
return 0;
}
private static void UnhandledTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the task to terminate.", e.Exception);
e.SetObserved();
}
private static void UnhandledException(object? sender, Eto.UnhandledExceptionEventArgs e)
{
Log.FatalException("An error occurred that caused the application to close.",
e.ExceptionObject as Exception ?? new Exception());
}
}

View File

@ -32,6 +32,15 @@ public abstract class EtoPlatform
public abstract Control AccessibleImageButton(Image image, string text, Action onClick,
int xOffset = 0, int yOffset = 0);
public virtual void InitializePlatform()
{
}
public virtual void RunApplication(Application application, Form mainForm)
{
application.Run(mainForm);
}
public virtual void SetContainerSize(Window window, Control container, Size size, int padding)
{
}

View File

@ -0,0 +1,12 @@
namespace NAPS2.Platform;
public class ApplicationLifecycle
{
public virtual void ParseArgs(string[] args)
{
}
public virtual void ExitIfRedundant()
{
}
}