Skip to content

Commit 6d8c9cc

Browse files
committed
Make GDK_BACKEND=x11 enforcement optional with ForceX11GdkBackend
1 parent a727b6e commit 6d8c9cc

5 files changed

Lines changed: 42 additions & 24 deletions

File tree

src/Avalonia.Controls.WebView.Core/Gtk/AvaloniaGtk.cs

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,11 @@ static AvaloniaGtk()
6666
public static bool HasSoup3 { get; }
6767

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

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

112+
private static void ReportUnsupportedBackend(string current)
113+
{
114+
if (s_reportedUnsupportedBackend)
115+
return;
116+
s_reportedUnsupportedBackend = true;
117+
118+
Logger.TryGet(LogEventLevel.Error, "WebView")?.Log(null,
119+
"GDK_BACKEND is set to {Backend}, but the GTK web view requires the x11 GDK backend, " +
120+
"so GTK initialization is expected to fail. Either run with GDK_BACKEND=x11, or set " +
121+
"ForceX11GdkBackend on GtkWebViewEnvironmentRequestedEventArgs.", current);
122+
}
123+
108124
private static readonly object s_overrideLock = new();
109125
private static int s_overrideCount;
110126
private static string? s_savedBackend;
127+
private static bool s_reportedUnsupportedBackend;
111128

112129
private sealed class EmptyScope : IDisposable
113130
{
@@ -126,15 +143,13 @@ public void Dispose()
126143
{
127144
if (--s_overrideCount > 0)
128145
return;
146+
// Only ever set when GDK_BACKEND already had a value, so there is nothing to unset.
129147
var previous = s_savedBackend;
130148
s_savedBackend = null;
131-
try
132-
{
133-
if (previous is null)
134-
LibC.unsetenv("GDK_BACKEND");
135-
else
136-
LibC.setenv("GDK_BACKEND", previous, 1);
137-
}
149+
if (previous is null)
150+
return;
151+
152+
try { LibC.setenv("GDK_BACKEND", previous, 1); }
138153
catch (DllNotFoundException) { }
139154
catch (EntryPointNotFoundException) { }
140155
Environment.SetEnvironmentVariable("GDK_BACKEND", previous);

src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenAvaloniaWebViewAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ private GtkOffscreenAvaloniaWebViewAdapter(GtkWebViewEnvironmentRequestedEventAr
3838
// detached, so handing out a cached instance leaves a dead web view after a re-attach.
3939
WebViewAdapter.OffscreenWebViewAdapterBuilder builder = async parent =>
4040
{
41-
using var backendScope = EnsureX11GdkBackendForGtkInit();
41+
using var backendScope = PrepareGdkBackendForGtkInit(environmentArgs.ForceX11GdkBackend);
4242
var adapter = await RunOnGlibThreadAsync(() => new GtkOffscreenAvaloniaWebViewAdapter(environmentArgs));
4343
adapter.Parent = parent;
4444
return adapter;

src/Avalonia.Controls.WebView.Core/Gtk/GtkX11WebViewAdapter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ private GtkX11WebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs environment
3939
WebViewAdapter.NativeWebViewAdapterBuilder builder = (parent, _) =>
4040
{
4141
WebViewDispatcher.VerifyAccess();
42-
using var backendScope = EnsureX11GdkBackendForGtkInit();
42+
using var backendScope = PrepareGdkBackendForGtkInit(environmentArgs.ForceX11GdkBackend);
4343
var adapter = RunOnGlibThread(() => new GtkX11WebViewAdapter(environmentArgs));
4444
adapter.SetParent(parent);
4545
return new WebViewAdapter.AdapterWrapper(adapter, Task.FromResult<IWebViewAdapter>(adapter));

src/Avalonia.Controls.WebView.Core/Linux/Interop/LibC.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ internal static unsafe partial class LibC
1010
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
1111
public static partial int setenv(string name, string value, int overwrite);
1212

13-
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
14-
public static partial int unsetenv(string name);
15-
16-
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
17-
public static partial nint getenv(string name);
18-
1913
[LibraryImport("libc")]
2014
public static partial int poll(GPollFD* fds, nint nfds, int timeout);
2115

src/Avalonia.Controls.WebView.Core/Platform/GtkWebViewEnvironmentRequestedEventArgs.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ internal GtkWebViewEnvironmentRequestedEventArgs(DeferralManager deferralManager
2020
/// </summary>
2121
public bool ExperimentalOffscreen { get; set; }
2222

23+
/// <summary>
24+
/// Gets or sets a value indicating whether GDK_BACKEND should be forced to "x11" while GTK is initialized.
25+
/// The GTK adapters require the x11 GDK backend, and a Wayland desktop usually pre-sets GDK_BACKEND=wayland, which makes gtk_init fail.
26+
/// </summary>
27+
/// <remarks>
28+
/// Disabled by default, as it temporarily mutates the process environment. The previous value is restored once GTK is initialized.
29+
/// </remarks>
30+
public bool ForceX11GdkBackend { get; set; }
31+
2332
/// <summary>
2433
/// 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.
2534
/// </summary>

0 commit comments

Comments
 (0)