Files
GSS2Rework/GSS2/Commands/UI.cs
T
amkovkov 3cc4d21947 feat: massive rework
1) GSS2.Core:
- strip prefixes in GSS2.Core.Analysis.AmineContent namespace class names
- add IEnumerable<double>.Variance extension
- remake analysis + update database so not needed to store all every file, calibration data also stored in database, also currently capturing images stored in system temporary directory and rewriting every time
2) GSS.UI.Core: AsyncImageRecordViewModel make zoom and pans public
3) GSS:
- remove image-storage-clear command
- remove ResultsView and ResultsViewModel, results presented in analysis
- rework calibration views
- add analysis views
- add text and number fields editors view
- add confirmation view on danger buttons
2026-05-13 12:56:32 +03:00

205 lines
8.7 KiB
C#

using System.CommandLine;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Avalonia;
using Avalonia.OpenGL.Egl;
using GSS2.Core;
using GSS2.Core.Logging;
using GSS2.Core.Analysis.AmineContent.Database.Calibration;
using GSS2.Core.Analysis.AmineContent.Database.Results;
using GSS2.Core.Hardware;
namespace GSS2.Commands;
public static class UI
{
public enum RenderingModes
{
X11,
DRM
}
public static readonly RootCommand RootCommand;
public static readonly Command Command;
public static readonly Option<bool> Fullscreen;
public static readonly Option<RenderingModes> RenderingMode;
public static readonly Option<bool> HideConsole;
static UI()
{
// Создаём команды, опции, аргументы и пр.
RootCommand = new("Запустить графическое приложение");
Command = new("ui")
{
Description = "Запустить графическое приложение"
};
Fullscreen = new("--fullscreen", ["-f"])
{
Description = "Запустить приложение в полноэкранном режиме",
};
RenderingMode = new("--mode", ["--rendering-mode", "-m"])
{
Description = "Режим рендеринга",
DefaultValueFactory = (_) => RenderingModes.X11
};
HideConsole = new("--hide-console", ["--no-console", "-c"])
{
Description = "Отключить вывод в консоль",
DefaultValueFactory = (result) => result.GetValue(RenderingMode) == RenderingModes.DRM
};
// Изменяем описания стандартных опций
Helper.TranslateDefaultOptionDescriptions(RootCommand);
// Наполняем команды
RootCommand.Add(Fullscreen);
RootCommand.Add(RenderingMode);
RootCommand.Add(HideConsole);
RootCommand.SetAction(CommandAction);
Command.Add(Fullscreen);
Command.Add(RenderingMode);
Command.Add(HideConsole);
Command.SetAction(CommandAction);
}
/// <summary>
/// Получить команду как корневую
/// </summary>
public static RootCommand GetCommand() => RootCommand;
/// <summary>
/// Зарегистрировать команду как подкоманду
/// </summary>
/// <param name="command">Команда в которой необходимо зарегистрировать подкоманду</param>
public static void RegisterCommand(Command command) => command.Add(Command);
private static void CommandAction(ParseResult result)
{
/// Получаем значения аргументов командной строки
var fullscreen = result.GetValue(Fullscreen);
var renderingMode = result.GetValue(RenderingMode);
var hideConsole = result.GetValue(HideConsole);
// Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью,
// поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом
if (hideConsole)
SilenceConsole();
// Собираем хост
var host = BuildHost();
// Запускаем хост асинхронно без блокировки потока
_ = host.RunAsync();
// Собираем графическое приложение
var ui = BuildApplication(host, fullscreen);
switch (renderingMode)
{
case RenderingModes.X11:
// Если мы запускаемся под X11,
// то очевидно у нас есть полноценный рабочий стол,
// поэтому запускаем графический интерфейс в классическом режиме с главным окном
ui.StartWithClassicDesktopLifetime(Program.Args);
break;
case RenderingModes.DRM:
// Если мы запускаемся в DRM (Direct Rendering Mode),
// то у нас нет рабочего стола и окон,
// потому что отрисовка (вроде как) идёт напрямую в кадровый буфер,
// поэтому запускаем графический интерфейс в DRM.
// Если работает X11, или любая другая графическая система,
// то запустить приложение в DRM не получится,
// потому что DRI (Direct Rendering Interface) занят этой графической системой
// TODO: Подумать над тем, чтобы брать DRI и scale для DRM рендеринга из переменных среды
// TODO: Подумать над тем, чтобы при запуске в DRM убивать приложения, которые занимают DRI
ui.StartLinuxDrm(Program.Args, "/dev/dri/card1", 1.0);
break;
}
}
private static void SilenceConsole()
{
var start = new ThreadStart(() =>
{
Console.CursorVisible = false;
while (true)
Console.ReadKey(true);
});
var thread = new Thread(start)
{
IsBackground = true
};
thread.Start();
}
private static IHost BuildHost()
{
// Создаём хост
var builder = Host.CreateApplicationBuilder();
// Добавляем конфигурацию из appsettings.json
builder.Configuration.AddAppSettingsJson();
// Конфигурируем логгер
builder.Logging.ConfigureLogging();
builder.Services.AddSingleton<LibCameraLogSink>();
// Добавляем сервисы баз данных
builder.Services.AddAmineContentCalibrationContext(builder.Configuration);
builder.Services.AddAmineContentResultsContext(builder.Configuration);
// Добавляем сервисы аппаратной части и прочее
builder.Services.AddLightsService("Hardware:Lights");
builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
builder.Services.AddIlluminatorService("Hardware:Illuminator");
builder.Services.AddCameraService("Hardware:Camera", true);
builder.Services.AddComputeResourcesService();
builder.Services.AddImageStorageService(new DirectoryInfo(builder.Configuration.GetValue<string>("ImageStorage") ?? "images"));
builder.Services.AddAmineContentImageCapturerService();
builder.Services.AddAmineContentAnalyzerService();
// Добавляем элементы интерфейса как сервисы
builder.Services.AddViewsAndModels();
// Собираем хост
var host = builder.Build();
// Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост,
// не будет создан ни одним из других сервисов, его необходимо создать превентивно
host.Services.GetRequiredService<LibCameraLogSink>();
// Применяем миграции баз данных если их схема данных изменялась
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<CalibrationContext>();
context.Database.Migrate();
}
using (var scope = host.Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ResultsContext>();
context.Database.Migrate();
}
return host;
}
private static AppBuilder BuildApplication(IHost host, bool fullscreen) =>
AppBuilder.Configure(() => new Application(host.Services, fullscreen))
.UsePlatformDetect()
.UseSkia()
.LogToTrace()
.With(new X11PlatformOptions
{
RenderingMode = [X11RenderingMode.Egl],
})
.With(new EglDisplayOptions
{
SupportsContextSharing = true
});
}