diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs index 389cb5d8..ef317299 100644 --- a/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs +++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs @@ -345,6 +345,46 @@ internal static extern void gdk_x11_window_set_frame_sync_enabled(IntPtr window, [DllImport(LibGdk)] internal static extern IntPtr gdk_display_get_default(); + // GObject type plumbing, used by GtkOffscreenHostWindow to register a GtkWindow subtype at runtime. + [StructLayout(LayoutKind.Sequential)] + internal struct GTypeQuery + { + public IntPtr type; + public IntPtr type_name; + public uint class_size; + public uint instance_size; + } + + [DllImport(LibGObject)] + internal static extern unsafe void g_type_query(IntPtr type, GTypeQuery* query); + [LibraryImport(LibGObject, StringMarshalling = StringMarshalling.Utf8)] + internal static partial IntPtr g_type_from_name(string name); + [DllImport(LibGObject)] + internal static extern IntPtr g_type_class_ref(IntPtr type); + [DllImport(LibGObject)] + internal static extern IntPtr g_type_class_peek(IntPtr type); + [DllImport(LibGObject)] + internal static extern IntPtr g_initially_unowned_get_type(); + [LibraryImport(LibGObject, StringMarshalling = StringMarshalling.Utf8)] + internal static partial IntPtr g_type_register_static_simple(IntPtr parentType, string typeName, uint classSize, + IntPtr classInit, uint instanceSize, IntPtr instanceInit, int flags); + [DllImport(LibGObject)] + internal static extern IntPtr g_object_new_with_properties(IntPtr type, uint nProperties, IntPtr names, IntPtr values); + + [DllImport(LibGtk)] + internal static extern IntPtr gtk_window_get_type(); + [DllImport(LibGtk)] + internal static extern IntPtr gtk_offscreen_window_get_type(); + + [DllImport(LibGdk)] + internal static extern IntPtr gdk_offscreen_window_get_surface(IntPtr window); + [DllImport(LibGdk)] + internal static extern int gdk_window_get_width(IntPtr window); + [DllImport(LibGdk)] + internal static extern int gdk_window_get_height(IntPtr window); + [DllImport(LibGdk)] + internal static extern IntPtr gdk_pixbuf_get_from_surface(IntPtr surface, int x, int y, int width, int height); + [DllImport(LibGdk)] internal static extern IntPtr gdk_display_get_default_seat(IntPtr display); [DllImport(LibGdk)] diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenHostWindow.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenHostWindow.cs new file mode 100644 index 00000000..d4ab011a --- /dev/null +++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenHostWindow.cs @@ -0,0 +1,93 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static Avalonia.Controls.Gtk.GtkInterop; + +namespace Avalonia.Controls.Gtk; + +internal static unsafe class GtkOffscreenHostWindow +{ + private const string TypeName = "AvaloniaWebViewOffscreenHostWindow"; + + private static IntPtr s_type; + private static bool s_registrationFailed; + + public static IntPtr TryCreate() + { + var type = EnsureRegistered(); + return type == IntPtr.Zero ? IntPtr.Zero : g_object_new_with_properties(type, 0, IntPtr.Zero, IntPtr.Zero); + } + + public static IntPtr GetPixbuf(IntPtr window) + { + var gdkWindow = gtk_widget_get_window(window); + if (gdkWindow == IntPtr.Zero) + return IntPtr.Zero; + + var surface = gdk_offscreen_window_get_surface(gdkWindow); + if (surface == IntPtr.Zero) + return IntPtr.Zero; + + return gdk_pixbuf_get_from_surface(surface, 0, 0, gdk_window_get_width(gdkWindow), gdk_window_get_height(gdkWindow)); + } + + private static IntPtr EnsureRegistered() + { + if (s_type != IntPtr.Zero || s_registrationFailed) + return s_type; + + try + { + var existing = g_type_from_name(TypeName); + if (existing != IntPtr.Zero) + return s_type = existing; + + g_type_class_ref(gtk_window_get_type()); + g_type_class_ref(gtk_offscreen_window_get_type()); + + GTypeQuery windowQuery; + g_type_query(gtk_window_get_type(), &windowQuery); + if (windowQuery.class_size == 0 || windowQuery.instance_size == 0) + { + s_registrationFailed = true; + return IntPtr.Zero; + } + + s_type = g_type_register_static_simple(gtk_window_get_type(), TypeName, windowQuery.class_size, + (IntPtr)(delegate* unmanaged[Cdecl])&ClassInit, windowQuery.instance_size, + IntPtr.Zero, 0); + } + catch (EntryPointNotFoundException) + { + s_type = IntPtr.Zero; + } + catch (DllNotFoundException) + { + s_type = IntPtr.Zero; + } + + s_registrationFailed = s_type == IntPtr.Zero; + return s_type; + } + + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + private static void ClassInit(IntPtr klass, IntPtr data) + { + var windowClass = (IntPtr*)g_type_class_peek(gtk_window_get_type()); + var offscreenClass = (IntPtr*)g_type_class_peek(gtk_offscreen_window_get_type()); + var ownClass = (IntPtr*)klass; + + GTypeQuery objectQuery, windowQuery; + g_type_query(g_initially_unowned_get_type(), &objectQuery); + g_type_query(gtk_window_get_type(), &windowQuery); + + var first = (int)(objectQuery.class_size / (uint)IntPtr.Size); + var end = (int)(windowQuery.class_size / (uint)IntPtr.Size); + + for (var i = first; i < end; i++) + { + if (ownClass[i] == windowClass[i] && offscreenClass[i] != windowClass[i]) + ownClass[i] = offscreenClass[i]; + } + } +} diff --git a/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenWebViewAdapter.cs b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenWebViewAdapter.cs index c124c0ac..183115aa 100644 --- a/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenWebViewAdapter.cs +++ b/src/Avalonia.Controls.WebView.Core/Gtk/GtkOffscreenWebViewAdapter.cs @@ -19,6 +19,7 @@ internal abstract unsafe class GtkOffscreenWebViewAdapter : GtkWebViewAdapter, new((delegate* unmanaged[Cdecl])&DrawCallback); private readonly bool _experimentalOffscreen; + private readonly bool _usesHostWindow; private IntPtr _windowHandle; private PixelSize _sizeRequest; private GtkSignal? _drawSignal; @@ -26,7 +27,18 @@ internal abstract unsafe class GtkOffscreenWebViewAdapter : GtkWebViewAdapter, protected GtkOffscreenWebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs args) : base(args) { _experimentalOffscreen = args.ExperimentalOffscreen; - _windowHandle = args.ExperimentalOffscreen ? gtk_offscreen_window_new() : gtk_window_new(0 /* GTK_WINDOW_TOPLEVEL */); + if (args.ExperimentalOffscreen) + { + // Not a real GtkOffscreenWindow, so WebKit lets media start. + _windowHandle = GtkOffscreenHostWindow.TryCreate(); + _usesHostWindow = _windowHandle != IntPtr.Zero; + if (!_usesHostWindow) + _windowHandle = gtk_offscreen_window_new(); + } + else + { + _windowHandle = gtk_window_new(0); + } g_object_ref_sink(_windowHandle); gtk_window_set_default_size(_windowHandle, 100, 100); @@ -62,7 +74,9 @@ public Task UpdateWriteableBitmap(PixelSize _, FrameChainBase