diff --git a/src/10-Core/Wtq/Services/Stubs/ServiceCollectionExtensions.cs b/src/10-Core/Wtq/Services/Stubs/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..d4f679cd --- /dev/null +++ b/src/10-Core/Wtq/Services/Stubs/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Wtq.Services.Stubs; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddStubs(this IServiceCollection services) + { + Guard.Against.Null(services); + + return services + .AddSingleton() + .AddSingleton(); + } +} \ No newline at end of file diff --git a/src/10-Core/Wtq/Services/Stubs/StubWtqScreenInfoProvider.cs b/src/10-Core/Wtq/Services/Stubs/StubWtqScreenInfoProvider.cs new file mode 100644 index 00000000..f6ea4cb8 --- /dev/null +++ b/src/10-Core/Wtq/Services/Stubs/StubWtqScreenInfoProvider.cs @@ -0,0 +1,19 @@ +namespace Wtq.Services.Stubs; + +public class StubWtqScreenInfoProvider : IWtqScreenInfoProvider +{ + public Task GetPrimaryScreenRectAsync() + { + return Task.FromResult(new Rectangle(0, 0, 1920, 1080)); + } + + public Task GetScreenRectsAsync() + { + return Task.FromResult([new Rectangle(0, 0, 1920, 1080)]); + } + + public Task GetScreenWithCursorAsync() + { + return Task.FromResult(new Rectangle(0, 0, 1920, 1080)); + } +} \ No newline at end of file diff --git a/src/10-Core/Wtq/Services/Stubs/StubWtqWindowService.cs b/src/10-Core/Wtq/Services/Stubs/StubWtqWindowService.cs new file mode 100644 index 00000000..15c084be --- /dev/null +++ b/src/10-Core/Wtq/Services/Stubs/StubWtqWindowService.cs @@ -0,0 +1,29 @@ +namespace Wtq.Services.Stubs; + +public class StubWtqWindowService : IWtqWindowService +{ + public Task CreateAsync(WtqAppOptions opts, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task> FindWindowsAsync(WtqAppOptions opts, CancellationToken cancellationToken) + { + return Task.FromResult>([]); + } + + public Task GetForegroundWindowAsync(CancellationToken cancellationToken) + { + return Task.FromResult(null); + } + + public List GetWindowProperties() + { + return []; + } + + public Task> GetWindowsAsync(CancellationToken cancellationToken) + { + return Task.FromResult>([]); + } +} \ No newline at end of file diff --git a/src/10-Core/Wtq/Utils/Log.cs b/src/10-Core/Wtq/Utils/Log.cs index 6625a601..f443b06c 100644 --- a/src/10-Core/Wtq/Utils/Log.cs +++ b/src/10-Core/Wtq/Utils/Log.cs @@ -40,7 +40,7 @@ public static void Configure(string pathToLogsDir) restrictedToMinimumLevel: LogLevel) // In-app. - .WriteTo.Sink(InAppLogSink.Instance) + // .WriteTo.Sink(InAppLogSink.Instance) // Plain text. .WriteTo.File( diff --git a/src/20-Services/Wtq.Services.UI/WtqUIHostBuilder.cs b/src/20-Services/Wtq.Services.UI/WtqUIHostBuilder.cs index f63152ac..90fda352 100644 --- a/src/20-Services/Wtq.Services.UI/WtqUIHostBuilder.cs +++ b/src/20-Services/Wtq.Services.UI/WtqUIHostBuilder.cs @@ -10,7 +10,19 @@ namespace Wtq.Services.UI; public static class WtqUIHostBuilder { - public static void Run(Action services) + public static void Run(Action services, bool gui) + { + if (gui) + { + RunGui(services); + } + else + { + RunHeadless(services); + } + } + + public static void RunGui(Action services) { Guard.Against.Null(services); @@ -60,4 +72,52 @@ public static void Run(Action services) app.Run(); } + + public static void RunHeadless(Action services) + { + Guard.Against.Null(services); + + var s = new ServiceCollection(); + + using var invoker = new WtqUIInvoker(); + + s + .AddLogging() + .AddSingleton() + .AddSingleton(_ => invoker) + ; + + services(s); + + var p = s.BuildServiceProvider(); + + var lifetime = (ApplicationLifetime)p.GetRequiredService(); + + // Note that this handler needs to be called pretty early, otherwise other handles may be run first, like the AspNetCore one (if the API is enabled). + // Note that we shouldn't ignore the return value, as it can get optimized out in "Release" mode, causing the entire handler to not work (as it gets finalized immediately). + using var reg = PosixSignalRegistration.Create( + PosixSignal.SIGINT, + ctx => + { + ctx.Cancel = true; + + lifetime.StopApplication(); // "Stopping" + }); + + invoker.Action = a => a(); + + _ = new WtqHost( + lifetime, + p.GetRequiredService>(), + p.GetRequiredService(), + () => {}); + + lifetime.NotifyStarted(); + + var exit = new TaskCompletionSource(); + + lifetime.ApplicationStopped.Register(() => exit.SetResult()); + + exit.Task.GetAwaiter().GetResult(); + } } \ No newline at end of file diff --git a/src/30-Host/Wtq.Host.Base/ConfigurationExtensions.cs b/src/30-Host/Wtq.Host.Base/ConfigurationExtensions.cs index 00a1da78..df42e595 100644 --- a/src/30-Host/Wtq.Host.Base/ConfigurationExtensions.cs +++ b/src/30-Host/Wtq.Host.Base/ConfigurationExtensions.cs @@ -34,7 +34,7 @@ private static IConfiguration CreateConfigurationRoot(IPlatformService platform, var pathToWtqConf = platform.PathToWtqConf; // Write wtq.schema.json. - WtqSchema.WriteFor(pathToWtqConf); + WtqSchema.WriteFor(pathToWtqConf); // ~40MB // Load config file. var config = new ConfigurationBuilder() diff --git a/src/30-Host/Wtq.Host.Base/WtqHostBase.cs b/src/30-Host/Wtq.Host.Base/WtqHostBase.cs index 1d6c8318..11b44d0c 100644 --- a/src/30-Host/Wtq.Host.Base/WtqHostBase.cs +++ b/src/30-Host/Wtq.Host.Base/WtqHostBase.cs @@ -16,7 +16,7 @@ public async Task RunAsync(string[] args) var platform = CreatePlatformService(); // Setup logging ASAP, so we can log stuff if initialization goes awry. - Log.Configure(platform.PathToLogsDir); + Log.Configure(platform.PathToLogsDir); // ~25MB if (args.Length == 0) { @@ -75,10 +75,11 @@ private void RunApp(IPlatformService platform, string[] args) .AddConfiguration(platform, args) .AddApi() .AddUI() - .AddWtqCore(); + .AddWtqCore() + ; ConfigureServices(s); - }); + }, gui: false); } catch (Exception ex) { diff --git a/src/30-Host/Wtq.Host.Linux/Properties/launchSettings.json b/src/30-Host/Wtq.Host.Linux/Properties/launchSettings.json index 28b913eb..a9fcea1a 100644 --- a/src/30-Host/Wtq.Host.Linux/Properties/launchSettings.json +++ b/src/30-Host/Wtq.Host.Linux/Properties/launchSettings.json @@ -5,7 +5,10 @@ "environmentVariables": { "WEBKIT_DISABLE_DMABUF_RENDERER": "1", - "__WTQ_LOG_LEVEL": "VERBOSE" + "__WTQ_LOG_LEVEL": "VERBOSE", + "__DOTNET_DefaultStackSize": "100000", + "__DOTNET_GCHeapHardLimit": "0x10000000", + "__DOTNET_GCHeapCount": "2" } }, "Config From User Home": { diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ae9038a3..7ad7da3a 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -5,6 +5,8 @@ net10.0 enable + true + true