Browse Source

Added UseWaylandWithFallback to opt-in for wayland support (#21734)

Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
pull/21750/head
Nikita Tsukanov 3 months ago
committed by GitHub
parent
commit
59355cb58d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      samples/ControlCatalog.Desktop/Program.cs
  2. 37
      src/Avalonia.Wayland/AvaloniaWaylandPlatformExtensions.cs
  3. 4
      src/Avalonia.Wayland/Server/Interop/WakeupFd.cs
  4. 52
      src/Avalonia.Wayland/Server/WaylandWorker.cs
  5. 66
      src/Avalonia.Wayland/WaylandPlatform.cs

1
samples/ControlCatalog.Desktop/Program.cs

@ -142,6 +142,7 @@ namespace ControlCatalog.Desktop
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseWaylandWithFallback()
.With(new X11PlatformOptions
{
EnableMultiTouch = true,

37
src/Avalonia.Wayland/AvaloniaWaylandPlatformExtensions.cs

@ -1,3 +1,5 @@
using System;
using Avalonia.Logging;
using Avalonia.Wayland;
// UseXxx are deliberately in global Avalonia namespace
@ -25,4 +27,39 @@ public static class AvaloniaWaylandPlatformExtensions
return builder;
}
/// <summary>
/// Configures the application to use the Wayland windowing backend when a usable Wayland
/// compositor is available, falling back to the previously configured windowing backend
/// otherwise. Call it after <c>UseX11</c> or <c>UsePlatformDetect</c>, e. g.
/// <c>.UsePlatformDetect().UseWaylandWithFallback()</c>. Does nothing on non-Linux platforms.
/// </summary>
/// <param name="builder">The application builder.</param>
/// <returns>The same <paramref name="builder"/> for chaining.</returns>
/// <exception cref="InvalidOperationException">
/// No windowing backend was configured prior to this call (on Linux).
/// </exception>
public static AppBuilder UseWaylandWithFallback(this AppBuilder builder)
{
if (!OperatingSystem.IsLinux())
return builder;
var fallback = builder.WindowingSubsystemInitializer
?? throw new InvalidOperationException(
"A fallback windowing backend must be configured before calling UseWaylandWithFallback, " +
"e.g. via UseX11 or UsePlatformDetect.");
return builder
.UseStandardRuntimePlatformSubsystem()
.UseWindowingSubsystem(() =>
{
var error = WaylandPlatform.TryInitialize(
AvaloniaLocator.Current.GetService<WaylandPlatformOptions>() ?? new WaylandPlatformOptions());
if (error != null)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Platform)?.Log(null,
"Unable to initialize the Wayland backend, falling back: {Error}", error.SourceException);
fallback();
}
});
}
}

4
src/Avalonia.Wayland/Server/Interop/WakeupFd.cs

@ -28,7 +28,7 @@ unsafe class WakeupFd : System.IDisposable
{
lock (_lock)
{
if(!_signaled)
if(_disposed || !_signaled)
return;
var readNow = read(_read, s_readBuf, 1);
Debug.Assert(readNow <= 1);
@ -40,7 +40,7 @@ unsafe class WakeupFd : System.IDisposable
{
lock (_lock)
{
if(_signaled)
if(_disposed || _signaled)
return;
byte b = 0;
write(_write, &b, 1);

52
src/Avalonia.Wayland/Server/WaylandWorker.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Logging;
@ -113,12 +114,26 @@ partial class WaylandWorker
return tcs.Task;
}
void Run(WaylandPlatformOptions options, WaylandConnection connection)
void Run(WaylandPlatformOptions options, WaylandConnection connection,
TaskCompletionSource<ExceptionDispatchInfo?>? initTcs)
{
var reconnectsDisabled = options.EnableReconnects == false || options.DisplayFd.HasValue;
while (true)
{
RunConnection(connection, options);
try
{
RunConnection(connection, options, initTcs);
}
catch (Exception e) when (initTcs?.Task.IsCompleted == false)
{
// Globals init failed on the initial connection: report to the waiting caller
// and exit without entering the reconnect loop
initTcs.TrySetResult(ExceptionDispatchInfo.Capture(e));
connection.Dispose();
_wakeupFd.Dispose();
return;
}
initTcs = null;
foreach (var obj in _persistentObjects.ToList())
obj.OnDisconnected();
Compositor.Server.ResetAllGpuResources();
@ -160,11 +175,15 @@ partial class WaylandWorker
}
}
void RunConnection(WaylandConnection connection, WaylandPlatformOptions options)
void RunConnection(WaylandConnection connection, WaylandPlatformOptions options,
TaskCompletionSource<ExceptionDispatchInfo?>? initTcs = null)
{
_thread = Thread.CurrentThread;
_connection = connection;
_globals = new WaylandGlobals(_connection, this, options, _outputsSink);
// Globals are fully constructed (required globals bound, sanity checks passed),
// the display is deemed usable
initTcs?.TrySetResult(null);
foreach (var obj in _persistentObjects)
ConnectPersistentObject(obj);
@ -229,8 +248,11 @@ partial class WaylandWorker
}
}
public static WaylandConnection? Probe(WaylandPlatformOptions options)
public static WaylandConnection? Probe(WaylandPlatformOptions options) => Probe(options, out _);
public static WaylandConnection? Probe(WaylandPlatformOptions options, out ExceptionDispatchInfo? error)
{
error = null;
try
{
var conn = options.DisplayFd is { } fd
@ -243,30 +265,38 @@ partial class WaylandWorker
catch(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "Wayland")?.Log(null, "Failed to connect to Wayland display: " + e);
error = ExceptionDispatchInfo.Capture(e);
return null;
}
}
/// <summary>
/// Starts the worker on a probed connection. When <paramref name="initTcs"/> is provided, it completes
/// with <c>null</c> once the initial <see cref="WaylandGlobals"/> construction succeeds, or with the
/// captured failure, in which case the worker thread exits without reconnecting.
/// </summary>
public void Start(WaylandPlatformOptions options, WaylandConnection probedConnection,
WaylandOutputsSinkProxy? outputsSink = null)
WaylandOutputsSinkProxy? outputsSink = null,
TaskCompletionSource<ExceptionDispatchInfo?>? initTcs = null)
{
_outputsSink = outputsSink;
StartCore(options, probedConnection, null);
StartCore(options, probedConnection, null, initTcs);
}
public void Start(WaylandPlatformOptions options, WlDisplay foreignDisplay,
WaylandOutputsSinkProxy? outputsSink = null)
{
_outputsSink = outputsSink;
StartCore(options, null, foreignDisplay);
StartCore(options, null, foreignDisplay, null);
}
void StartCore(WaylandPlatformOptions options, WaylandConnection? probedConnection, WlDisplay? foreignDisplay)
void StartCore(WaylandPlatformOptions options, WaylandConnection? probedConnection, WlDisplay? foreignDisplay,
TaskCompletionSource<ExceptionDispatchInfo?>? initTcs)
{
new Thread(() =>
{
if(probedConnection!=null)
Run(options, probedConnection);
Run(options, probedConnection, initTcs);
else if (foreignDisplay != null)
{
RunConnection(new WaylandConnection(foreignDisplay), options);

66
src/Avalonia.Wayland/WaylandPlatform.cs

@ -1,4 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Input;
@ -16,32 +19,55 @@ namespace Avalonia.Wayland;
class WaylandPlatform
{
public static void Initialize(WaylandPlatformOptions options)
public static void Initialize(WaylandPlatformOptions options) => TryInitialize(options)?.Throw();
internal static ExceptionDispatchInfo? TryInitialize(WaylandPlatformOptions options)
{
var connection = WaylandWorker.Probe(options);
// In some cases we aren't allowed to open multiple connections (e. g. WAYLAND_SOCKET env
// is used), so we can't do separate usability checks and bail before doing any initialization.
// Instead we do full startup sequence with WaylandWorker on compositor thread and wait for it
// to get to WaylandGlobals, which is the point where we can make an informed decision
// about wl_display being usable.
var connection = WaylandWorker.Probe(options, out var connectError);
if (connection == null)
throw new AvaloniaWaylandException("Unable to connect to Wayland display");
// TODO: wait for globals to be ready on the wayland thread and perform sanity checks too.
// This is needed for UsePlatformDetect() in Avalonia.Desktop: if we detect a compositor we
// can't support we should fail here so the caller can fall back to X11. Later we'll also want
// feature-detection flags (e.g. xdg-toplevel-drag-v1 for docks) so apps can request features
// that force an X11 fallback when the compositor lacks them.
return connectError?.SourceException is AvaloniaWaylandException
? connectError
: ExceptionDispatchInfo.Capture(
new AvaloniaWaylandException("Unable to connect to Wayland display",
connectError?.SourceException));
var inputDispatchQueue = new AutomaticRawEventGrouperDispatchQueue();
IDispatcherImpl dispatcherImpl = options.UseGLibMainLoop
? new WaylandGlibDispatcher(options.ExternalGLibMainLoopExceptionLogger)
: new ManagedDispatcherImpl(null);
Dispatcher.InitializeUIThreadDispatcher(dispatcherImpl);
var worker = new WaylandWorker(inputDispatchQueue);
WaylandWorker worker;
SnapshotScreensImpl screens;
TaskCompletionSource<ExceptionDispatchInfo?> initTcs;
try
{
// NOTE: This technically mutates the global state since it touches Dispatcher,
// which designates the current thread as the UI one in static ctor, but it's generally safe to do
var inputDispatchQueue = new AutomaticRawEventGrouperDispatchQueue();
var screens = new SnapshotScreensImpl();
var screensProxy = new WaylandOutputsSinkProxy(screens, WaylandMarshallers.UIThread);
worker = new WaylandWorker(inputDispatchQueue);
worker.Start(options, connection, screensProxy);
screens = new SnapshotScreensImpl();
var screensProxy = new WaylandOutputsSinkProxy(screens, WaylandMarshallers.UIThread);
initTcs = new TaskCompletionSource<ExceptionDispatchInfo?>(TaskCreationOptions.RunContinuationsAsynchronously);
worker.Start(options, connection, screensProxy, initTcs);
}
catch (Exception e)
{
connection.Dispose();
return ExceptionDispatchInfo.Capture(e);
}
if (initTcs.Task.GetAwaiter().GetResult() is { } initError)
return initError;
IDispatcherImpl dispatcherImpl = options.UseGLibMainLoop
? new WaylandGlibDispatcher(options.ExternalGLibMainLoopExceptionLogger)
: new ManagedDispatcherImpl(null);
Dispatcher.InitializeUIThreadDispatcher(dispatcherImpl);
var clipboardImpl = new WaylandClipboardImpl(worker);
var clipboard = new Input.Platform.Clipboard(clipboardImpl);
@ -61,5 +87,7 @@ class WaylandPlatform
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader())
.Bind<IScreenImpl>().ToConstant(screens);
return null;
}
}
Loading…
Cancel
Save