using System.CommandLine; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Avalonia; using Avalonia.OpenGL.Egl; using GSS2.Core.Logging; using GSS2.Core; using GSS2.Core.Analysis.AmineContent.Database.Calibration; using GSS2.Core.Analysis.AmineContent.Database.Results; 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 Fullscreen; public static readonly Option RenderingMode; public static readonly Option 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); } /// /// Получить команду как корневую /// public static RootCommand GetCommand() => RootCommand; /// /// Зарегистрировать команду как подкоманду /// /// Команда в которой необходимо зарегистрировать подкоманду 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(); // Добавляем сервисы баз данных 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("images")); // Добавляем элементы интерфейса как сервисы builder.Services.AddViewsAndModels(); // Собираем хост var host = builder.Build(); // Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост, // не будет создан ни одним из других сервисов, его необходимо создать превентивно host.Services.GetRequiredService(); // Применяем миграции баз данных если их схема данных изменялась using (var scope = host.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); } using (var scope = host.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService(); 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 }); }