diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs b/src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs
index 8d0825c..8c540f6 100644
--- a/src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs
+++ b/src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs
@@ -66,13 +66,11 @@ static AvaloniaGtk()
public static bool HasSoup3 { get; }
///
- /// 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.
///
- public static IDisposable EnsureX11GdkBackendForGtkInit()
+ public static IDisposable PrepareGdkBackendForGtkInit(bool forceX11)
{
if (!OperatingSystem.IsLinux())
return EmptyScope.Instance;
@@ -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); }
@@ -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;
}
@@ -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
{
@@ -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);
diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs
index 59068d4..fade712 100644
--- a/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs
+++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs
@@ -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)]
diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenAvaloniaWebViewAdapter.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenAvaloniaWebViewAdapter.cs
index 196205b..c764b92 100644
--- a/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenAvaloniaWebViewAdapter.cs
+++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenAvaloniaWebViewAdapter.cs
@@ -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;
diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs
index d33e80a..a6ac807 100644
--- a/src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs
+++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs
@@ -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 CreateBuilder(
@@ -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(adapter));
diff --git a/src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs b/src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs
index a461f7d..397dac0 100644
--- a/src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs
+++ b/src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs
@@ -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);
diff --git a/src/Avalonia.Controls.WebView.Core/Platform/GtkWebViewEnvironmentRequestedEventArgs.cs b/src/Avalonia.Controls.WebView.Core/Platform/GtkWebViewEnvironmentRequestedEventArgs.cs
index 3bade75..28723d8 100644
--- a/src/Avalonia.Controls.WebView.Core/Platform/GtkWebViewEnvironmentRequestedEventArgs.cs
+++ b/src/Avalonia.Controls.WebView.Core/Platform/GtkWebViewEnvironmentRequestedEventArgs.cs
@@ -20,6 +20,15 @@ internal GtkWebViewEnvironmentRequestedEventArgs(DeferralManager deferralManager
///
public bool ExperimentalOffscreen { get; set; }
+ ///
+ /// 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.
+ ///
+ ///
+ /// Disabled by default, as it temporarily mutates the process environment. The previous value is restored once GTK is initialized.
+ ///
+ public bool ForceX11GdkBackend { get; set; }
+
///
/// 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.
///