using Silk.NET.GLFW;
using Silk.NET.Core.Loader;
using Silk.NET.Windowing.Glfw;
namespace AcDream.App.Platform;
internal enum GraphicalDisplayProtocol
{
Unknown,
Windows,
X11,
Wayland,
Automatic,
}
///
/// Immutable process-start decision for Silk's GLFW 3.4 backend. Linux users
/// may force x11 or wayland with
/// ACDREAM_DISPLAY_PROTOCOL; otherwise GLFW selects from the available
/// native backends. The decision is applied once before any window exists.
///
internal sealed record GraphicalWindowBackendSelection(
GraphicalDisplayProtocol RequestedProtocol,
string Reason)
{
internal const string EnvironmentVariable =
"ACDREAM_DISPLAY_PROTOCOL";
internal static GraphicalWindowBackendSelection Resolve(
GraphicalHostOperatingSystem operatingSystem,
Func environment)
{
ArgumentNullException.ThrowIfNull(environment);
if (operatingSystem == GraphicalHostOperatingSystem.Windows)
{
return new(
GraphicalDisplayProtocol.Windows,
"Windows graphical host");
}
string? configured = environment(EnvironmentVariable);
if (!string.IsNullOrWhiteSpace(configured))
{
return configured.Trim().ToLowerInvariant() switch
{
"auto" => new(
GraphicalDisplayProtocol.Automatic,
$"{EnvironmentVariable}=auto"),
"x11" => new(
GraphicalDisplayProtocol.X11,
$"{EnvironmentVariable}=x11"),
"wayland" => new(
GraphicalDisplayProtocol.Wayland,
$"{EnvironmentVariable}=wayland"),
_ => throw new InvalidOperationException(
$"{EnvironmentVariable} must be auto, x11, or wayland; " +
$"received '{configured}'."),
};
}
string? sessionType = environment("XDG_SESSION_TYPE");
string? waylandDisplay = environment("WAYLAND_DISPLAY");
if (string.Equals(
sessionType,
"wayland",
StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(waylandDisplay))
{
return new(
GraphicalDisplayProtocol.Wayland,
"XDG_SESSION_TYPE=wayland and WAYLAND_DISPLAY is set");
}
string? xDisplay = environment("DISPLAY");
if (!string.IsNullOrWhiteSpace(xDisplay))
{
return new(
GraphicalDisplayProtocol.X11,
"DISPLAY is set");
}
if (!string.IsNullOrWhiteSpace(waylandDisplay))
{
return new(
GraphicalDisplayProtocol.Wayland,
"WAYLAND_DISPLAY is set");
}
return new(
GraphicalDisplayProtocol.Automatic,
"no X11 or Wayland environment was detected");
}
}
///
/// The only App-layer owner allowed to select a native GLFW platform. GLFW
/// requires the platform init hint before glfwInit; applying it after
/// is too late.
///
internal static class GraphicalWindowBackendConfigurator
{
private const int GlfwPlatformInitHint = 0x00050003;
private const int GlfwAnyPlatform = 0x00060000;
private const int GlfwWin32Platform = 0x00060001;
private const int GlfwWaylandPlatform = 0x00060003;
private const int GlfwX11Platform = 0x00060004;
private static readonly object Gate = new();
private static GraphicalDisplayProtocol? _configuredProtocol;
private static Glfw? _glfw;
internal static void Configure(
GraphicalHostPlatformServices platform)
{
ArgumentNullException.ThrowIfNull(platform);
GraphicalDisplayProtocol requested =
platform.WindowBackend.RequestedProtocol;
lock (Gate)
{
if (_configuredProtocol is { } existing)
{
if (existing != requested)
{
throw new InvalidOperationException(
"The GLFW platform is already configured as " +
$"{existing}; it cannot change to {requested} in " +
"the same process.");
}
return;
}
PreferPublishedNativeLibraries();
GlfwWindowing.Use();
// Silk's windowing backend initializes this exact singleton.
// A separate Glfw.GetApi() instance would receive the hint but
// would not own the window backend's process-global GLFW state.
Glfw glfw = GlfwProvider.UninitializedGLFW.Value;
glfw.InitHint(
(InitHint)GlfwPlatformInitHint,
requested switch
{
GraphicalDisplayProtocol.Windows =>
GlfwWin32Platform,
GraphicalDisplayProtocol.X11 =>
GlfwX11Platform,
GraphicalDisplayProtocol.Wayland =>
GlfwWaylandPlatform,
GraphicalDisplayProtocol.Automatic =>
GlfwAnyPlatform,
_ => throw new ArgumentOutOfRangeException(
nameof(requested)),
});
_glfw = glfw;
_configuredProtocol = requested;
}
}
internal static bool TryGetConfiguredApi(out Glfw? glfw)
{
lock (Gate)
{
glfw = _glfw;
return glfw is not null;
}
}
private static void PreferPublishedNativeLibraries()
{
if (PathResolver.Default is not DefaultPathResolver resolver)
{
throw new InvalidOperationException(
"Silk.NET's default native path resolver is unavailable.");
}
// Silk 2.23 names its Linux GLFW ABI libglfw.so.3.3, while the
// packaged multi-backend GLFW 3.4 asset is libglfw.so.3. Move the
// application directory ahead of the system passthrough so Silk's
// version resolver reaches the packaged .so.3 before a distribution
// GLFW 3.3. This also makes every packaged native closure deterministic
// instead of silently preferring a machine-global library.
List>> resolvers =
resolver.Resolvers;
resolvers.Remove(DefaultPathResolver.BaseDirectoryResolver);
resolvers.Insert(
0,
DefaultPathResolver.BaseDirectoryResolver);
}
}