Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,11 @@ static AvaloniaGtk()
public static bool HasSoup3 { get; }

/// <summary>
/// Ensures GDK_BACKEND=x11 is in effect for the duration of the bridge call that
/// initializes GTK in Avalonia.X11.NativeDialogs.Gtk. The X11 GTK adapters in this
/// package use X11/GDK-X11-only entry points and require the X11 GDK backend; under
/// a Wayland session GDK_BACKEND is typically pre-set to "wayland" by the desktop,
/// which would otherwise cause `gtk_init` to fail with "Unable to initialize GTK".
/// Checks that GTK can initialize with the x11 GDK backend, which the GTK adapters require.
/// GDK_BACKEND replaces the backend list, while Avalonia only filters it with
/// gdk_set_allowed_backends("x11"), so any other explicit value makes gtk_init fail.
/// </summary>
public static IDisposable EnsureX11GdkBackendForGtkInit()
public static IDisposable PrepareGdkBackendForGtkInit(bool forceX11)
{
if (!OperatingSystem.IsLinux())
return EmptyScope.Instance;
Expand All @@ -81,11 +79,16 @@ public static IDisposable EnsureX11GdkBackendForGtkInit()
{
if (s_overrideCount == 0)
{
// Unset is fine: GDK then falls back to the backends Avalonia allowed, i.e. x11 only.
var current = Environment.GetEnvironmentVariable("GDK_BACKEND");
if (string.Equals(current, "x11", StringComparison.Ordinal))
if (current is not { Length: > 0 } || string.Equals(current, "x11", StringComparison.Ordinal))
return EmptyScope.Instance;
if (current is { Length: > 0 } && !string.Equals(current, "wayland", StringComparison.Ordinal))

if (!forceX11)
{
ReportUnsupportedBackend(current);
return EmptyScope.Instance;
}

int rc;
try { rc = LibC.setenv("GDK_BACKEND", "x11", 1); }
Expand All @@ -94,9 +97,10 @@ public static IDisposable EnsureX11GdkBackendForGtkInit()
if (rc != 0)
{
Logger.TryGet(LogEventLevel.Warning, "WebView")?.Log(null,
"libc setenv(GDK_BACKEND, x11) returned {Rc}; gtk_init may still pick Wayland", rc);
"libc setenv(GDK_BACKEND, x11) returned {Rc}; gtk_init may still fail", rc);
return EmptyScope.Instance;
}
// Keep the managed cache in step, it doesn't share storage with libc's environ.
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
s_savedBackend = current;
}
Expand All @@ -105,9 +109,22 @@ public static IDisposable EnsureX11GdkBackendForGtkInit()
return new RestoreGdkBackendScope();
}

private static void ReportUnsupportedBackend(string current)
{
if (s_reportedUnsupportedBackend)
return;
s_reportedUnsupportedBackend = true;

Logger.TryGet(LogEventLevel.Error, "WebView")?.Log(null,
"GDK_BACKEND is set to {Backend}, but the GTK web view requires the x11 GDK backend, " +
"so GTK initialization is expected to fail. Either run with GDK_BACKEND=x11, or set " +
"ForceX11GdkBackend on GtkWebViewEnvironmentRequestedEventArgs.", current);
}

private static readonly object s_overrideLock = new();
private static int s_overrideCount;
private static string? s_savedBackend;
private static bool s_reportedUnsupportedBackend;

private sealed class EmptyScope : IDisposable
{
Expand All @@ -126,15 +143,13 @@ public void Dispose()
{
if (--s_overrideCount > 0)
return;
// Only ever set when GDK_BACKEND already had a value, so there is nothing to unset.
var previous = s_savedBackend;
s_savedBackend = null;
try
{
if (previous is null)
LibC.unsetenv("GDK_BACKEND");
else
LibC.setenv("GDK_BACKEND", previous, 1);
}
if (previous is null)
return;

try { LibC.setenv("GDK_BACKEND", previous, 1); }
catch (DllNotFoundException) { }
catch (EntryPointNotFoundException) { }
Environment.SetEnvironmentVariable("GDK_BACKEND", previous);
Expand Down
4 changes: 4 additions & 0 deletions src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ internal static partial IntPtr webkit_website_data_manager_new(
[DllImport(LibGdk)]
internal static extern IntPtr gdk_x11_window_get_xid(IntPtr window);

[DllImport(LibGdk)]
internal static extern void gdk_x11_window_set_frame_sync_enabled(IntPtr window,
[MarshalAs(UnmanagedType.Bool)] bool frameSyncEnabled);

[DllImport(LibGdk)]
internal static extern IntPtr gdk_display_get_default();
[DllImport(LibGdk)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ private GtkOffscreenAvaloniaWebViewAdapter(GtkWebViewEnvironmentRequestedEventAr
// detached, so handing out a cached instance leaves a dead web view after a re-attach.
WebViewAdapter.OffscreenWebViewAdapterBuilder builder = async parent =>
{
using var backendScope = EnsureX11GdkBackendForGtkInit();
using var backendScope = PrepareGdkBackendForGtkInit(environmentArgs.ForceX11GdkBackend);
var adapter = await RunOnGlibThreadAsync(() => new GtkOffscreenAvaloniaWebViewAdapter(environmentArgs));
adapter.Parent = parent;
return adapter;
Expand Down
11 changes: 9 additions & 2 deletions src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ private GtkX11WebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs environment
gtk_container_add(_windowHandle, WebViewHandle);
gtk_widget_show_all(WebViewHandle);
gtk_widget_realize(_windowHandle);
_x11Window = gdk_x11_window_get_xid(gtk_widget_get_window(_windowHandle));

var gdkWindow = gtk_widget_get_window(_windowHandle);
_x11Window = gdk_x11_window_get_xid(gdkWindow);

// Reparenting this window into the Avalonia window takes it away from the window manager,
// so the _NET_WM_FRAME_DRAWN replies GDK throttles drawing on never arrive.
// Without this GTK paints one frame and then waits forever.
gdk_x11_window_set_frame_sync_enabled(gdkWindow, false);
}

public static Task<WebViewAdapter.NativeWebViewAdapterBuilder> CreateBuilder(
Expand All @@ -32,7 +39,7 @@ private GtkX11WebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs environment
WebViewAdapter.NativeWebViewAdapterBuilder builder = (parent, _) =>
{
WebViewDispatcher.VerifyAccess();
using var backendScope = EnsureX11GdkBackendForGtkInit();
using var backendScope = PrepareGdkBackendForGtkInit(environmentArgs.ForceX11GdkBackend);
var adapter = RunOnGlibThread(() => new GtkX11WebViewAdapter(environmentArgs));
adapter.SetParent(parent);
return new WebViewAdapter.AdapterWrapper(adapter, Task.FromResult<IWebViewAdapter>(adapter));
Expand Down
6 changes: 0 additions & 6 deletions src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ internal static unsafe partial class LibC
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
public static partial int setenv(string name, string value, int overwrite);

[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
public static partial int unsetenv(string name);

[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
public static partial nint getenv(string name);

[LibraryImport("libc")]
public static partial int poll(GPollFD* fds, nint nfds, int timeout);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ internal GtkWebViewEnvironmentRequestedEventArgs(DeferralManager deferralManager
/// </summary>
public bool ExperimentalOffscreen { get; set; }

/// <summary>
/// Gets or sets a value indicating whether GDK_BACKEND should be forced to "x11" while GTK is initialized.
/// The GTK adapters require the x11 GDK backend, and a Wayland desktop usually pre-sets GDK_BACKEND=wayland, which makes gtk_init fail.
/// </summary>
/// <remarks>
/// Disabled by default, as it temporarily mutates the process environment. The previous value is restored once GTK is initialized.
/// </remarks>
public bool ForceX11GdkBackend { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the webview should use an ephemeral data manager, handling all website data as non-persistent and not writing anything to client storage.
/// </summary>
Expand Down
Loading