diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index 910b0f0d52..11d9b5d2e1 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -48,14 +48,18 @@ + + + + diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 296f302556..943d83b57f 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -38,7 +38,7 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(Instance) .Bind().ToSingleton() .Bind().ToConstant(new DefaultRenderLoop(60)) - .Bind().ToConstant(new PlatformIconLoaderStub()); + .Bind().ToConstant(new PlatformIconLoader()); } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GException.cs b/src/Gtk/Avalonia.Gtk3/Interop/GException.cs new file mode 100644 index 0000000000..ab08df4ec3 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/GException.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + public class GException : Exception + { + [StructLayout(LayoutKind.Sequential)] + struct GError + { + UInt32 domain; + int code; + public IntPtr message; + }; + + static unsafe string GetError(IntPtr error) + { + if (error == IntPtr.Zero) + return "Unknown error"; + return Utf8Buffer.StringFromPtr(((GError*) error)->message); + } + + public GException(IntPtr error) : base(GetError(error)) + { + + } + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs new file mode 100644 index 0000000000..9b5ee758b9 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + class GObject : SafeHandle + { + public GObject() : base(IntPtr.Zero, true) + { + } + + public GObject(IntPtr handle, bool owned = true) : base(IntPtr.Zero, owned) + { + this.handle = handle; + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + Native.GObjectUnref(handle); + handle = IntPtr.Zero; + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } + + class GInputStream : GObject + { + + } +} + diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index f3c1c847ca..b325b46285 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -38,6 +38,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_hide(IntPtr gtkWidget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_icon(IntPtr window, Pixbuf pixbuf); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow); @@ -160,12 +163,31 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_clipboard_clear(IntPtr clipboard); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] + public delegate IntPtr gdk_pixbuf_new_from_file(Utf8Buffer filename, out IntPtr error); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] + public delegate IntPtr gdk_pixbuf_new_from_stream(GInputStream stream, IntPtr cancel, out IntPtr error); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)] + public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer, out IntPtr buffer_size, + Utf8Buffer type, IntPtr option_keys, IntPtr option_values, out IntPtr error); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate void g_object_unref(IntPtr instance); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_free(IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gio)] + public delegate GInputStream g_memory_input_stream_new_from_data(IntPtr ptr, IntPtr len, IntPtr destroyCallback); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); @@ -191,6 +213,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_application_new GtkApplicationNew; public static D.gtk_main_iteration GtkMainIteration; public static D.gtk_window_new GtkWindowNew; + public static D.gtk_window_set_icon GtkWindowSetIcon; public static D.gtk_init GtkInit; public static D.gtk_window_present GtkWindowPresent; public static D.gtk_widget_hide GtkWidgetHide; @@ -205,9 +228,12 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_window_set_default_size GtkWindowSetDefaultSize; public static D.gtk_window_get_position GtkWindowGetPosition; public static D.gtk_window_move GtkWindowMove; + public static D.g_object_unref GObjectUnref; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; + public static D.g_free GFree; + public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData; public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; public static D.gtk_widget_set_events GtkWidgetSetEvents; public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; @@ -232,6 +258,10 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag; public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag; + public static D.gdk_pixbuf_new_from_file GdkPixbufNewFromFile; + public static D.gdk_pixbuf_new_from_stream GdkPixbufNewFromStream; + public static D.gdk_pixbuf_save_to_bufferv GdkPixbufSaveToBufferv; + 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; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs b/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs new file mode 100644 index 0000000000..1a0a772522 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Pixbuf.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Platform; + +namespace Avalonia.Gtk3.Interop +{ + internal class Pixbuf : GObject, IWindowIconImpl + { + Pixbuf(IntPtr handle) : base(handle) + { + + } + + public static Pixbuf NewFromFile(string filename) + { + using (var ub = new Utf8Buffer(filename)) + { + IntPtr err; + var rv = Native.GdkPixbufNewFromFile(ub, out err); + if(rv != IntPtr.Zero) + return new Pixbuf(rv); + throw new GException(err); + } + } + + public static unsafe Pixbuf NewFromBytes(byte[] data) + { + fixed (void* bytes = data) + { + using (var stream = Native.GMemoryInputStreamNewFromData(new IntPtr(bytes), new IntPtr(data.Length), IntPtr.Zero)) + { + IntPtr err; + var rv = Native.GdkPixbufNewFromStream(stream, IntPtr.Zero, out err); + if (rv != IntPtr.Zero) + return new Pixbuf(rv); + throw new GException(err); + } + } + } + + public static Pixbuf NewFromStream(Stream s) + { + if (s is MemoryStream) + return NewFromBytes(((MemoryStream) s).ToArray()); + var ms = new MemoryStream(); + s.CopyTo(ms); + return NewFromBytes(ms.ToArray()); + } + + public void Save(Stream outputStream) + { + IntPtr buffer, bufferLen, error; + using (var png = new Utf8Buffer("png")) + if (!Native.GdkPixbufSaveToBufferv(this, out buffer, out bufferLen, png, + IntPtr.Zero, IntPtr.Zero, out error)) + throw new GException(error); + var data = new byte[bufferLen.ToInt32()]; + Marshal.Copy(buffer, data, 0, bufferLen.ToInt32()); + Native.GFree(buffer); + outputStream.Write(data, 0, data.Length); + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index a965453841..c6b6326f80 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -31,7 +31,8 @@ namespace Avalonia.Gtk3.Interop Glib, Gio, Gobject, - Cairo + Cairo, + GdkPixBuf } static class Resolver @@ -76,6 +77,8 @@ namespace Avalonia.Gtk3.Interop return FormatName("gtk-3"); case GtkDll.Gobject: return FormatName("gobject-2.0"); + case GtkDll.GdkPixBuf: + return FormatName("gdk_pixbuf-2.0"); default: throw new ArgumentException("Unknown lib: " + dll); } diff --git a/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs b/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs new file mode 100644 index 0000000000..b4e293cdff --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/PlatformIconLoader.cs @@ -0,0 +1,20 @@ +using System.IO; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + class PlatformIconLoader : IPlatformIconLoader + { + public IWindowIconImpl LoadIcon(string fileName) => Pixbuf.NewFromFile(fileName); + + public IWindowIconImpl LoadIcon(Stream stream) => Pixbuf.NewFromStream(stream); + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + return Pixbuf.NewFromBytes(ms.ToArray()); + } + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Stubs.cs b/src/Gtk/Avalonia.Gtk3/Stubs.cs index 89d9251cdd..8ebc1e9c59 100644 --- a/src/Gtk/Avalonia.Gtk3/Stubs.cs +++ b/src/Gtk/Avalonia.Gtk3/Stubs.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -26,13 +25,4 @@ namespace Avalonia.Gtk3 public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => Task.FromResult((string) null); } - - class PlatformIconLoaderStub : IPlatformIconLoader - { - public IWindowIconImpl LoadIcon(string fileName) => null; - - public IWindowIconImpl LoadIcon(Stream stream) => null; - - public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => null; - } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index b8c561c56c..25b0bd8dd3 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -22,10 +22,7 @@ namespace Avalonia.Gtk3 public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled); - public void SetIcon(IWindowIconImpl icon) - { - //STUB - } + public void SetIcon(IWindowIconImpl icon) => Native.GtkWindowSetIcon(GtkWidget, (Pixbuf) icon); public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel)) {