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))
{