diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index f2634865b9..4abdce2657 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -18,7 +18,7 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. AppBuilder.Configure() - .UseDirect2D1() + .UseSkia() .UseGtk3() .Start(); } diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index ab52c8f9c4..06fc40458e 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -41,13 +41,16 @@ + + + diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs new file mode 100644 index 0000000000..c2685e8791 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; + +namespace Avalonia.Gtk3 +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly TopLevelImpl _window; + private ImageSurfaceFramebuffer _fb; + public FramebufferManager(TopLevelImpl window) + { + _window = window; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + + public ILockedFramebuffer Lock() + { + if(_window.CurrentCairoContext == IntPtr.Zero) + throw new InvalidOperationException("Window is not in drawing state"); + + var ctx = _window.CurrentCairoContext; + var width = (int) _window.ClientSize.Width; + var height = (int) _window.ClientSize.Height; + if (_fb == null || _fb.Width != width || + _fb.Height != height) + { + _fb?.Dispose(); + _fb = new ImageSurfaceFramebuffer(width, height); + } + _fb.Prepare(ctx); + return _fb; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index faf1983250..625cd7096a 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -104,7 +104,11 @@ namespace Avalonia.Gtk3 public static class Gtk3AppBuilderExtensions { - public static T UseGtk3(this AppBuilderBase builder) where T : AppBuilderBase, new() - => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + public static T UseGtk3(this AppBuilderBase builder, ICustomGtk3NativeLibraryResolver resolver = null) + where T : AppBuilderBase, new() + { + Resolver.Custom = resolver; + return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + } } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs new file mode 100644 index 0000000000..e955f82764 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + + +namespace Avalonia.Gtk3 +{ + class ImageSurfaceFramebuffer : ILockedFramebuffer + { + private IntPtr _context; + private IntPtr _surface; + + public ImageSurfaceFramebuffer(int width, int height) + { + _surface = Native.CairoImageSurfaceCreate(1, width, height); + Width = width; + Height = height; + Address = Native.CairoImageSurfaceGetData(_surface); + RowBytes = Native.CairoImageSurfaceGetStride(_surface); + Native.CairoSurfaceFlush(_surface); + } + + public void Prepare(IntPtr context) + { + _context = context; + } + + public void Deallocate() + { + Native.CairoSurfaceDestroy(_surface); + _surface = IntPtr.Zero; + } + + public void Dispose() + { + if(_context == IntPtr.Zero || _surface == IntPtr.Zero) + return; + Native.CairoSurfaceMarkDirty(_surface); + Native.CairoSetSourceSurface(_context, _surface, 0, 0); + Native.CairoPaint(_context); + _context = IntPtr.Zero; + + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format + { + get + { + if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + + + diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs index 93ba7cc195..afe526e2c3 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; /* @@ -11,7 +12,7 @@ namespace Avalonia.Gtk3.Interop { internal interface IDynLoader { - IntPtr LoadLibrary(string basePath, string dll); + IntPtr LoadLibrary(string dll); IntPtr GetProcAddress(IntPtr dll, string proc); } @@ -21,7 +22,6 @@ namespace Avalonia.Gtk3.Interop // ReSharper disable InconsistentNaming static class LinuxImports { - [DllImport("libdl.so.2")] private static extern IntPtr dlopen(string path, int flags); @@ -36,7 +36,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".so.0"; } } static class OsXImports @@ -57,7 +56,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".dylib"; //TODO } } @@ -66,8 +64,6 @@ namespace Avalonia.Gtk3.Interop [DllImport("libc")] static extern int uname(IntPtr buf); - - static UnixLoader() { var buffer = Marshal.AllocHGlobal(0x1000); @@ -83,20 +79,12 @@ namespace Avalonia.Gtk3.Interop private static Func DlOpen; private static Func DlSym; private static Func DlError; - private static string Postfix; // ReSharper restore InconsistentNaming - static string DlErrorString() - { - - return Marshal.PtrToStringAnsi(DlError()); - } + static string DlErrorString() => Marshal.PtrToStringAnsi(DlError()); - public IntPtr LoadLibrary(string basePath, string dll) + public IntPtr LoadLibrary(string dll) { - dll += Postfix; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = DlOpen(dll, 1); if (handle == IntPtr.Zero) throw new NativeException(DlErrorString()); @@ -120,15 +108,14 @@ namespace Avalonia.Gtk3.Interop [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpszLib); - IntPtr IDynLoader.LoadLibrary(string basePath, string dll) + IntPtr IDynLoader.LoadLibrary(string dll) { - dll += "-0.dll"; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = LoadLibrary(dll); - if (handle == IntPtr.Zero) - throw new NativeException("Error " + Marshal.GetLastWin32Error()); - return handle; + if (handle != IntPtr.Zero) + return handle; + var err = Marshal.GetLastWin32Error(); + + throw new NativeException("Error loading " + dll + " error " + err); } IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs new file mode 100644 index 0000000000..2f88b09896 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Gtk3.Interop; + +namespace Avalonia.Gtk3 +{ + public interface ICustomGtk3NativeLibraryResolver + { + string GetName(GtkDll dll); + string BasePath { get; } + bool TrySystemFirst { get; } + string Lookup(GtkDll dll); + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d9cf9fff05..4ed7474fb1 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -80,6 +80,33 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y); + + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate IntPtr cairo_image_surface_create(int format, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate IntPtr cairo_image_surface_get_data(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate int cairo_image_surface_get_stride(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_mark_dirty(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_flush(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_destroy(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_set_source_surface(IntPtr cr, IntPtr surface, double x, double y); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_paint(IntPtr context); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); @@ -130,7 +157,14 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_screen_get_width GdkScreenGetWidth; public static D.gdk_window_get_origin GdkWindowGetOrigin; - + public static D.cairo_image_surface_create CairoImageSurfaceCreate; + public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; + public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; + public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; + public static D.cairo_surface_flush CairoSurfaceFlush; + public static D.cairo_surface_destroy CairoSurfaceDestroy; + public static D.cairo_set_source_surface CairoSetSourceSurface; + public static D.cairo_paint CairoPaint; } public enum GtkWindowType diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 15a2f90ade..a965453841 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Avalonia.Platform; namespace Avalonia.Gtk3.Interop { @@ -22,64 +24,108 @@ namespace Avalonia.Gtk3.Interop } } - internal enum GtkDll + public enum GtkDll { Gdk, Gtk, Glib, Gio, - Gobject + Gobject, + Cairo } static class Resolver { - [DllImport("kernel32.dll")] - static extern int GetVersion(); + private static Lazy Platform = + new Lazy( + () => AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem); - static bool IsWin32() + public static ICustomGtk3NativeLibraryResolver Custom { get; set; } + + + static string FormatName(string name, int version = 0) + { + if (Platform.Value == OperatingSystemType.WinNT) + return "lib" + name + "-" + version + ".dll"; + if (Platform.Value == OperatingSystemType.Linux) + return "lib" + name + ".so" + "." + version; + if (Platform.Value == OperatingSystemType.OSX) + return "lib" + name + "." + version + ".dylib"; + throw new Exception("Unknown platform, use custom name resolver"); + } + + + + static string GetDllName(GtkDll dll) { + var name = Custom?.GetName(dll); + if (name != null) + return name; + + switch (dll) + { + case GtkDll.Cairo: + return FormatName("cairo", 2); + case GtkDll.Gdk: + return FormatName("gdk-3"); + case GtkDll.Glib: + return FormatName("glib-2.0"); + case GtkDll.Gio: + return FormatName("gio-2.0"); + case GtkDll.Gtk: + return FormatName("gtk-3"); + case GtkDll.Gobject: + return FormatName("gobject-2.0"); + default: + throw new ArgumentException("Unknown lib: " + dll); + } + } + + static IntPtr LoadDll(IDynLoader loader, GtkDll dll) + { + + var exceptions = new List(); + + var name = GetDllName(dll); + if (Custom?.TrySystemFirst != false) + { + try + { + return loader.LoadLibrary(name); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + var path = Custom?.Lookup(dll); + if (path == null && Custom?.BasePath != null) + path = Path.Combine(Custom.BasePath, name); + try { - GetVersion(); - return true; + return loader.LoadLibrary(path); } - catch + catch (Exception e) { - return false; + exceptions.Add(e); } + throw new AggregateException("Unable to load " + dll, exceptions); } - - public static void Resolve(string basePath = null) { - var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader(); - + var loader = Platform.Value == OperatingSystemType.WinNT ? (IDynLoader)new Win32Loader() : new UnixLoader(); - var gdk = loader.LoadLibrary(basePath, "libgdk-3"); - var gtk = loader.LoadLibrary(basePath, "libgtk-3"); - var gio = loader.LoadLibrary(basePath, "libgio-2.0"); - var glib = loader.LoadLibrary(basePath, "libglib-2.0"); - var gobject = loader.LoadLibrary(basePath, "libgobject-2.0"); + var dlls = Enum.GetValues(typeof(GtkDll)).Cast().ToDictionary(x => x, x => LoadDll(loader, x)); + foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) { var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); if(import == null) continue; - IntPtr lib; - if (import.Dll == GtkDll.Gtk) - lib = gtk; - else if (import.Dll == GtkDll.Gdk) - lib = gdk; - else if (import.Dll == GtkDll.Gio) - lib = gio; - else if (import.Dll == GtkDll.Glib) - lib = glib; - else if (import.Dll == GtkDll.Gobject) - lib = gobject; - else - throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType); - + IntPtr lib = dlls[import.Dll]; + var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name); fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); } @@ -90,7 +136,7 @@ namespace Avalonia.Gtk3.Interop try { Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal - .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + .GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name), typeof(Native.D.gdk_get_native_handle)); break; } catch { } @@ -103,3 +149,4 @@ namespace Avalonia.Gtk3.Interop } } + diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index fc0893bdb8..4d5509f4f7 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -13,11 +13,13 @@ namespace Avalonia.Gtk3 { protected readonly IntPtr GtkWidget; private IInputRoot _inputRoot; + private readonly FramebufferManager _framebuffer; protected readonly List _disposables = new List(); public TopLevelImpl(IntPtr gtkWidget) { GtkWidget = gtkWidget; + _framebuffer = new FramebufferManager(this); Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue); Native.GtkWidgetRealize(gtkWidget); Connect("draw", OnDraw); @@ -126,9 +128,13 @@ namespace Avalonia.Gtk3 void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); + internal IntPtr CurrentCairoContext { get; private set; } + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { + CurrentCairoContext = cairocontext; Paint?.Invoke(new Rect(ClientSize)); + CurrentCairoContext = IntPtr.Zero; return true; } @@ -234,6 +240,6 @@ namespace Avalonia.Gtk3 } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); - public IEnumerable Surfaces => new object[] {Handle}; + public IEnumerable Surfaces => new object[] {Handle, _framebuffer}; } }