forked from amkovkov/GranuSightSoftware2
1) remove unused components: camera view (due it causes segmentation faults), compute resources and all related, illuminator controller (due in useless without camera view), image storage service, temperature and humidity service 2) database schema - reduce tables references, features storing in records themselves in compressed form, add records creation and editing date and time, add separator comment column 3) analysis - rework of pipeline and ui, now database storing only raw data and all display values calculated from it 4) lights service - add reconnection if disconnected 5) add width and height command line arguments 6) fix some typos and other issues
226 lines
9.5 KiB
C#
226 lines
9.5 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;
|
|
|
|
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;
|
|
public static readonly Option<bool> Mock;
|
|
public static readonly Option<int> Width;
|
|
public static readonly Option<int> Height;
|
|
|
|
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
|
|
};
|
|
Mock = new("--mock")
|
|
{
|
|
Description = "Запуск в фиктивном режиме без инициализации и использования периферии",
|
|
DefaultValueFactory = (result) => false
|
|
};
|
|
Width = new("--width")
|
|
{
|
|
Description = "Ширина окна, если меньше или равна 0, то устанавливается ширина по-умолчанию",
|
|
DefaultValueFactory = (result) => 0
|
|
};
|
|
Height = new("--height")
|
|
{
|
|
Description = "Высота окна, если меньше или равна 0, то устанавливается высота по-умолчанию",
|
|
DefaultValueFactory = (result) => 0
|
|
};
|
|
|
|
// Изменяем описания стандартных опций
|
|
Helper.TranslateDefaultOptionDescriptions(RootCommand);
|
|
|
|
// Наполняем команды
|
|
RootCommand.Add(Fullscreen);
|
|
RootCommand.Add(RenderingMode);
|
|
RootCommand.Add(HideConsole);
|
|
RootCommand.Add(Mock);
|
|
RootCommand.Add(Width);
|
|
RootCommand.Add(Height);
|
|
RootCommand.SetAction(CommandAction);
|
|
|
|
Command.Add(Fullscreen);
|
|
Command.Add(RenderingMode);
|
|
Command.Add(HideConsole);
|
|
Command.Add(Mock);
|
|
Command.Add(Width);
|
|
Command.Add(Height);
|
|
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);
|
|
var mock = result.GetValue(Mock);
|
|
var width = result.GetValue(Width);
|
|
var height = result.GetValue(Height);
|
|
|
|
// Если выбран режим рендеринга DRM, то приложение будет рисоваться вместе с консолью,
|
|
// поэтому если не перехватить консоль, то вывод в неё будет рисоваться вместе с интерфейсом
|
|
if (hideConsole)
|
|
SilenceConsole();
|
|
|
|
// Собираем хост
|
|
var host = BuildHost(mock);
|
|
|
|
// Запускаем хост асинхронно без блокировки потока
|
|
_ = host.RunAsync();
|
|
|
|
// Собираем графическое приложение
|
|
var ui = BuildApplication(host, fullscreen, width, height);
|
|
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(bool mock)
|
|
{
|
|
// Создаём хост
|
|
var builder = Host.CreateApplicationBuilder();
|
|
|
|
// Добавляем конфигурацию из appsettings.json
|
|
builder.Configuration.AddAppSettingsJson();
|
|
|
|
// Конфигурируем логгер
|
|
builder.Logging.ConfigureLogging();
|
|
builder.Services.AddSingleton<LibCameraLogSink>();
|
|
|
|
// Добавляем сервисы баз данных
|
|
builder.Services.AddAmineContentContext(builder.Configuration);
|
|
|
|
// Добавляем сервисы аппаратной части и прочее
|
|
builder.Services.AddLightsService("Hardware:Lights");
|
|
if (!mock)
|
|
{
|
|
// builder.Services.AddTemperatureHumidityService("Hardware:TemperatureHumidity");
|
|
builder.Services.AddIlluminatorService("Hardware:Illuminator");
|
|
builder.Services.AddCameraService("Hardware:Camera", true);
|
|
}
|
|
builder.Services.AddAmineContentImageCapturerService(mock);
|
|
builder.Services.AddAmineContentAnalyzerService();
|
|
|
|
// Добавляем элементы интерфейса как сервисы
|
|
builder.Services.AddViewsAndModels();
|
|
|
|
// Собираем хост
|
|
var host = builder.Build();
|
|
|
|
// Поскольку LibCameraLogSink, который перенаправляет логи из libcamera в хост,
|
|
// не будет создан ни одним из других сервисов, его необходимо создать превентивно
|
|
if (!mock)
|
|
host.Services.GetRequiredService<LibCameraLogSink>();
|
|
|
|
// Применяем миграции баз данных если их схема данных изменялась
|
|
using (var scope = host.Services.CreateScope())
|
|
{
|
|
var context = scope.ServiceProvider.GetRequiredService<Context>();
|
|
context.Database.Migrate();
|
|
}
|
|
|
|
return host;
|
|
}
|
|
|
|
private static AppBuilder BuildApplication(IHost host, bool fullscreen, int width, int height) =>
|
|
AppBuilder.Configure(() => new Application(host.Services, fullscreen, width, height))
|
|
.UsePlatformDetect()
|
|
.UseSkia()
|
|
.LogToTrace()
|
|
.With(new X11PlatformOptions
|
|
{
|
|
RenderingMode = [X11RenderingMode.Egl],
|
|
})
|
|
.With(new EglDisplayOptions
|
|
{
|
|
SupportsContextSharing = true
|
|
});
|
|
}
|