Files
Nitrox/Nitrox.Launcher/App.axaml.cs
2025-07-06 00:23:46 +02:00

220 lines
7.6 KiB
C#

using System;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Threading;
using ConsoleAppFramework;
using Microsoft.Extensions.DependencyInjection;
using Nitrox.Launcher.Models.Utils;
using Nitrox.Launcher.Models.Validators;
using Nitrox.Launcher.ViewModels;
using Nitrox.Launcher.Views;
using NitroxModel.Helper;
using NitroxModel.Logger;
using NitroxModel.Platforms.OS.Shared;
namespace Nitrox.Launcher;
public class App : Application
{
internal const string CRASH_REPORT_FILE_NAME = "Nitrox.Launcher-crash.txt";
internal static Func<Window> StartupWindowFactory;
internal static InstantLaunchData InstantLaunch;
internal static bool IsCrashReport;
/// <summary>
/// If true, allows duplicate instances of the app to be active.
/// </summary>
internal static bool AllowInstances;
internal static X11RenderingMode? PreferredRenderingMode;
public Window AppWindow
{
set
{
switch (ApplicationLifetime)
{
case IClassicDesktopStyleApplicationLifetime desktop:
desktop.MainWindow = value;
break;
case ISingleViewApplicationLifetime singleViewPlatform:
singleViewPlatform.MainView = value;
break;
case null when Design.IsDesignMode:
Log.Info("Running in design previewer!");
break;
default:
throw new NotSupportedException($"Current platform '{ApplicationLifetime?.GetType().Name}' is not supported by {nameof(Nitrox)}.{nameof(Launcher)}");
}
}
}
public static AppBuilder Create()
{
CultureManager.ConfigureCultureInfo();
Log.Setup();
// Handle command line arguments.
ConsoleApp.ConsoleAppBuilder cliParser = ConsoleApp.Create();
cliParser.Add("", (bool crashReport, X11RenderingMode? rendering = null, bool allowInstances = false) =>
{
IsCrashReport = crashReport;
PreferredRenderingMode = rendering;
AllowInstances = allowInstances;
if (IsCrashReport)
{
string executableRootPath = Path.GetDirectoryName(Environment.ProcessPath ?? NitroxUser.ExecutableRootPath);
if (executableRootPath != null)
{
string crashReportContent = File.ReadAllText(Path.Combine(executableRootPath, CRASH_REPORT_FILE_NAME));
StartupWindowFactory = () => new CrashWindow { DataContext = new CrashWindowViewModel { Title = $"Nitrox {NitroxEnvironment.Version} - Crash Report", Message = crashReportContent } };
}
}
});
cliParser.Add("instantlaunch", ([SaveName] string save, [MinLength(1)] params string[] players) =>
{
InstantLaunch = new InstantLaunchData(save, players);
});
cliParser.Run(Environment.GetCommandLineArgs().Skip(1).ToArray());
// Fallback to normal startup.
if (StartupWindowFactory == null)
{
if (!AllowInstances)
{
CheckForRunningInstance();
}
ServiceProvider services = new ServiceCollection().AddAppServices().BuildServiceProvider();
StartupWindowFactory = () => new MainWindow { DataContext = services.GetRequiredService<MainWindowViewModel>() };
}
AppBuilder builder = AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace()
.With(new SkiaOptions { UseOpacitySaveLayer = true });
builder = WithRenderingMode(builder, PreferredRenderingMode);
return builder;
static AppBuilder WithRenderingMode(AppBuilder builder, X11RenderingMode? rendering)
{
if (rendering.HasValue)
{
return builder.With(new X11PlatformOptions { RenderingMode = [rendering.Value] });
}
// The Wayland+GPU is not supported by Avalonia, but Xwayland should work.
if (Environment.GetEnvironmentVariable("WAYLAND_DISPLAY") is not null)
{
if (!ProcessEx.ProcessExists("Xwayland"))
{
return builder.With(new X11PlatformOptions { RenderingMode = [X11RenderingMode.Software] });
}
}
return builder;
}
}
public override void Initialize() => AvaloniaXamlLoader.Load(this);
public override void OnFrameworkInitializationCompleted()
{
FixAvaloniaPlugins();
ApplyAppDefaults();
Dispatcher.UIThread.UnhandledException += (_, eventArgs) => HandleUnhandledException(eventArgs.Exception);
AppWindow = StartupWindowFactory();
base.OnFrameworkInitializationCompleted();
}
internal static void HandleUnhandledException(Exception ex)
{
if (IsCrashReport)
{
Log.Error(ex, "Error while trying to show crash report");
return;
}
Log.Error(ex, "!!!Nitrox Launcher Crash!!!");
// Write crash report if we're not reporting one right now.
try
{
string executableFilePath = NitroxUser.ExecutableFilePath ?? Environment.ProcessPath;
string executableRoot = Path.GetDirectoryName(executableFilePath);
if (executableFilePath != null && executableRoot != null)
{
string crashReportFile = Path.Combine(executableRoot, CRASH_REPORT_FILE_NAME);
File.WriteAllText(crashReportFile, ex.ToString());
ProcessUtils.StartSelf("--crash-report");
}
else
{
Log.Error(ex, "Unable to get executable file path for writing crash report.");
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
Environment.Exit(1);
}
private static void CheckForRunningInstance()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return;
}
try
{
using ProcessEx process = ProcessEx.GetFirstProcess("Nitrox.Launcher", process => process.Id != Environment.ProcessId && process.IsRunning);
if (process is not null)
{
process.SetForegroundWindowAndRestore();
Environment.Exit(0);
}
}
catch (Exception)
{
// Ignore
}
}
/// <summary>
/// Disables Avalonia plugins which are replaced by MVVM Toolkit.
/// </summary>
private void FixAvaloniaPlugins()
{
for (int i = BindingPlugins.DataValidators.Count - 1; i >= 0; i--)
{
if (BindingPlugins.DataValidators[i] is DataAnnotationsValidationPlugin)
{
BindingPlugins.DataValidators.RemoveAt(i);
}
}
}
private void ApplyAppDefaults() => RequestedThemeVariant = ThemeVariant.Dark;
internal class InstantLaunchData
{
public string SaveName { get; private set; }
public string[] PlayerNames { get; private set; }
public InstantLaunchData(string saveName, string[] playerNames)
{
SaveName = saveName;
PlayerNames = playerNames;
}
}
}