Browse Source

Merge branch 'master' into master

ahopper 7 years ago
parent
commit
2882027b37

+ 1 - 1
src/Avalonia.Controls/Window.cs

@@ -358,7 +358,7 @@ namespace Avalonia.Controls
                         modal?.Dispose();
                         SetIsEnabled(affectedWindows, true);
                         activated?.Activate();
-                        result.SetResult((TResult)_dialogResult);
+                        result.SetResult((TResult)(_dialogResult ?? default(TResult)));
                     });
 
                 return result.Task;

+ 3 - 2
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@@ -26,8 +26,9 @@ namespace Avalonia.Gtk3
         {
             // This method may be called from non-UI thread, don't touch anything that calls back to GTK/GDK
             var s = _window.ClientSize;
-            var width = (int) s.Width;
-            var height = (int) s.Height;
+            var width = Math.Max(1, (int) s.Width);
+            var height = Math.Max(1, (int) s.Height);
+            
             
             if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11"))
             {

+ 6 - 0
src/Gtk/Avalonia.Gtk3/Interop/GObject.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Text;
@@ -21,7 +22,12 @@ namespace Avalonia.Gtk3.Interop
         protected override bool ReleaseHandle()
         {
             if (handle != IntPtr.Zero)
+            {
+                Debug.Assert(Native.GTypeCheckInstanceIsFundamentallyA(handle, new IntPtr(Native.G_TYPE_OBJECT)),
+                    "Handle is not a GObject");
                 Native.GObjectUnref(handle);
+            }
+
             handle = IntPtr.Zero;
             return true;
         }

+ 14 - 1
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -219,7 +219,10 @@ namespace Avalonia.Gtk3.Interop
             public delegate void gtk_widget_queue_draw_area(GtkWidget widget, int x, int y, int width, int height);
             
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
-            public delegate void gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy);
+            public delegate uint gtk_widget_add_tick_callback(GtkWidget widget, TickCallback callback, IntPtr userData, IntPtr destroy);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate uint gtk_widget_remove_tick_callback(GtkWidget widget, uint id);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate GtkImContext gtk_im_multicontext_new();
@@ -256,6 +259,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
             public delegate void gtk_window_unmaximize(GtkWindow window);
+            
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
+            public delegate void gtk_window_close(GtkWindow window);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
             public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@@ -341,6 +347,9 @@ namespace Avalonia.Gtk3.Interop
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
             public delegate ulong g_free(IntPtr data);
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
+            public delegate bool g_type_check_instance_is_fundamentally_a(IntPtr instance, IntPtr type);
             
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
             public unsafe delegate void g_slist_free(GSList* data);
@@ -427,6 +436,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.g_timeout_add GTimeoutAdd;
         public static D.g_timeout_add_full GTimeoutAddFull;
         public static D.g_free GFree;
+        public static D.g_type_check_instance_is_fundamentally_a GTypeCheckInstanceIsFundamentallyA;
         public static D.g_slist_free GSlistFree;
         public static D.g_memory_input_stream_new_from_data GMemoryInputStreamNewFromData;
         public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered;
@@ -434,6 +444,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect;
         public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea;
         public static D.gtk_widget_add_tick_callback GtkWidgetAddTickCallback;
+        public static D.gtk_widget_remove_tick_callback GtkWidgetRemoveTickCallback;
         public static D.gtk_widget_activate GtkWidgetActivate;
         public static D.gtk_clipboard_get_for_display GtkClipboardGetForDisplay;
         public static D.gtk_clipboard_request_text GtkClipboardRequestText;
@@ -456,6 +467,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.gtk_window_deiconify GtkWindowDeiconify;
         public static D.gtk_window_maximize GtkWindowMaximize;
         public static D.gtk_window_unmaximize GtkWindowUnmaximize;
+        public static D.gtk_window_close GtkWindowClose;
         public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
         public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
         public static D.gdk_event_request_motions GdkEventRequestMotions;
@@ -490,6 +502,7 @@ namespace Avalonia.Gtk3.Interop
         public static D.cairo_set_font_size CairoSetFontSize;
         public static D.cairo_move_to CairoMoveTo;
         public static D.cairo_destroy CairoDestroy;
+        public const int G_TYPE_OBJECT = 80;
     }
 
     public enum GtkWindowType

+ 18 - 4
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@@ -33,11 +33,11 @@ namespace Avalonia.Gtk3
         private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
         internal IntPtr? GdkWindowHandle;
         private bool _overrideRedirect;
+        private uint? _tickCallback;
         public WindowBaseImpl(GtkWindow gtkWidget)
         {
             
             GtkWidget = gtkWidget;
-            Disposables.Add(gtkWidget);
             _framebuffer = new FramebufferManager(this);
             _imContext = Native.GtkImMulticontextNew();
             Disposables.Add(_imContext);
@@ -62,7 +62,7 @@ namespace Avalonia.Gtk3
             {
                 Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
                 _gcHandle = GCHandle.Alloc(this);
-                Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero);
+                _tickCallback = Native.GtkWidgetAddTickCallback(GtkWidget, PinnedStaticCallback, GCHandle.ToIntPtr(_gcHandle), IntPtr.Zero);
                 
             }
         }
@@ -103,7 +103,7 @@ namespace Avalonia.Gtk3
 
         private bool OnDestroy(IntPtr gtkwidget, IntPtr userdata)
         {
-            Dispose();
+            DoDispose(true);
             return false;
         }
 
@@ -297,14 +297,28 @@ namespace Avalonia.Gtk3
         }
 
 
-        public void Dispose()
+        public void Dispose() => DoDispose(false);
+        
+        void DoDispose(bool fromDestroy)
         {
+            if (_tickCallback.HasValue)
+            {
+                if (!GtkWidget.IsClosed)
+                    Native.GtkWidgetRemoveTickCallback(GtkWidget, _tickCallback.Value);
+                _tickCallback = null;
+            }
+            
             //We are calling it here, since signal handler will be detached
             if (!GtkWidget.IsClosed)
                 Closed?.Invoke();
             foreach(var d in Disposables.AsEnumerable().Reverse())
                 d.Dispose();
             Disposables.Clear();
+            
+            if (!fromDestroy && !GtkWidget.IsClosed)
+                Native.GtkWindowClose(GtkWidget);
+            GtkWidget.Dispose();
+            
             if (_gcHandle.IsAllocated)
             {
                 _gcHandle.Free();

+ 5 - 5
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@@ -12,6 +12,7 @@ namespace Avalonia.MonoMac
         private readonly TopLevelImpl.TopLevelView _view;
         private readonly CGSize _logicalSize;
         private readonly bool _isDeferred;
+        private readonly IUnmanagedBlob _blob;
 
         [DllImport("libc")]
         static extern void memset(IntPtr p, int c, IntPtr size);
@@ -29,13 +30,13 @@ namespace Avalonia.MonoMac
             Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
             Format = PixelFormat.Rgba8888;
             var size = Height * RowBytes;
-            Address = Marshal.AllocHGlobal(size);
+            _blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
             memset(Address, 0, new IntPtr(size));
         }
         
         public void Dispose()
         {
-            if (Address == IntPtr.Zero)
+            if (_blob.IsDisposed)
                 return;
             var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
             CGImage image = null;
@@ -71,14 +72,13 @@ namespace Avalonia.MonoMac
                     else
                         _view.SetBackBufferImage(new SavedImage(image, _logicalSize));
                 }
-                Marshal.FreeHGlobal(Address);
-                Address = IntPtr.Zero;
+                _blob.Dispose();
             }
 
 
         }
 
-        public IntPtr Address { get; private set; }
+        public IntPtr Address => _blob.Address;
         public int Width { get; }
         public int Height { get; }
         public int RowBytes { get; }

+ 20 - 11
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@@ -28,11 +28,13 @@ namespace Avalonia.Shared.PlatformSupport
         class UnmanagedBlob : IUnmanagedBlob
         {
             private readonly StandardRuntimePlatform _plat;
+            private IntPtr _address;
 #if DEBUG
             private static readonly List<string> Backtraces = new List<string>();
             private static Thread GCThread;
             private readonly string _backtrace;
-
+            private readonly object _lock = new object();
+            private static readonly object _btlock = new object();
 
             class GCThreadDetector
             {
@@ -55,28 +57,35 @@ namespace Avalonia.Shared.PlatformSupport
             
             public UnmanagedBlob(StandardRuntimePlatform plat, int size)
             {
+                if (size <= 0)
+                    throw new ArgumentException("Positive number required", nameof(size));
                 _plat = plat;
-                Address = plat.Alloc(size);
+                _address = plat.Alloc(size);
                 GC.AddMemoryPressure(size);
                 Size = size;
 #if DEBUG
                 _backtrace = Environment.StackTrace;
-                Backtraces.Add(_backtrace);
+                lock (_btlock)
+                    Backtraces.Add(_backtrace);
 #endif
             }
 
             void DoDispose()
             {
-                if (!IsDisposed)
+                lock (_lock)
                 {
+                    if (!IsDisposed)
+                    {
 #if DEBUG
-                    Backtraces.Remove(_backtrace);
+                        lock (_btlock)
+                            Backtraces.Remove(_backtrace);
 #endif
-                    _plat.Free(Address, Size);
-                    GC.RemoveMemoryPressure(Size);
-                    IsDisposed = true;
-                    Address = IntPtr.Zero;
-                    Size = 0;
+                        _plat.Free(_address, Size);
+                        GC.RemoveMemoryPressure(Size);
+                        IsDisposed = true;
+                        _address = IntPtr.Zero;
+                        Size = 0;
+                    }
                 }
             }
 
@@ -102,7 +111,7 @@ namespace Avalonia.Shared.PlatformSupport
                 DoDispose();
             }
 
-            public IntPtr Address { get; private set; }
+            public IntPtr Address => IsDisposed ? throw new ObjectDisposedException("UnmanagedBlob") : _address; 
             public int Size { get; private set; }
             public bool IsDisposed { get; private set; }
         }

+ 6 - 16
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Win32
     public class WindowFramebuffer : ILockedFramebuffer
     {
         private readonly IntPtr _handle;
-        private IntPtr _pBitmap;
+        private IUnmanagedBlob _bitmapBlob;
         private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo;
 
         public WindowFramebuffer(IntPtr handle, int width, int height)
@@ -27,7 +27,7 @@ namespace Avalonia.Win32
             _bmpInfo.Init();
             _bmpInfo.biWidth = width;
             _bmpInfo.biHeight = -height;
-            _pBitmap = Marshal.AllocHGlobal(width * height * 4);
+            _bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(width * height * 4);
         }
 
         ~WindowFramebuffer()
@@ -35,7 +35,7 @@ namespace Avalonia.Win32
             Deallocate();
         }
 
-        public IntPtr Address => _pBitmap;
+        public IntPtr Address => _bitmapBlob.Address;
         public int RowBytes => Width * 4;
         public PixelFormat Format => PixelFormat.Bgra8888;
 
@@ -70,21 +70,18 @@ namespace Avalonia.Win32
         public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
             int height = -1)
         {
-            if(_pBitmap == IntPtr.Zero)
-                throw new ObjectDisposedException("Framebuffer");
             if (width == -1)
                 width = Width;
             if (height == -1)
                 height = Height;
             UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY,
-                0, (uint)Height, _pBitmap, ref _bmpInfo, 0);
+                0, (uint)Height, _bitmapBlob.Address, ref _bmpInfo, 0);
         }
 
         public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
             int height = -1)
         {
-
-            if (_pBitmap == IntPtr.Zero)
+            if (_bitmapBlob.IsDisposed)
                 throw new ObjectDisposedException("Framebuffer");
             if (hWnd == IntPtr.Zero)
                 return false;
@@ -102,13 +99,6 @@ namespace Avalonia.Win32
             DrawToWindow(_handle);
         }
 
-        public void Deallocate()
-        {
-            if (_pBitmap != IntPtr.Zero)
-            {
-                Marshal.FreeHGlobal(_pBitmap);
-                _pBitmap = IntPtr.Zero;
-            }
-        }
+        public void Deallocate() => _bitmapBlob.Dispose();
     }
 }

+ 24 - 4
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -6,6 +6,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
@@ -231,11 +232,23 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
-        private void ClearOpenWindows()
+        [Fact]
+        public async Task ShowDialog_With_ValueType_Returns_Default_When_Closed()
         {
-            // HACK: We really need a decent way to have "statics" that can be scoped to
-            // AvaloniaLocator scopes.
-            ((IList<Window>)Window.OpenWindows).Clear();
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var windowImpl = new Mock<IWindowImpl>();
+                windowImpl.SetupProperty(x => x.Closed);
+                windowImpl.Setup(x => x.Scaling).Returns(1);
+
+                var target = new Window(windowImpl.Object);
+                var task = target.ShowDialog<bool>();
+
+                windowImpl.Object.Closed();
+
+                var result = await task;
+                Assert.False(result);
+            }
         }
 
         [Fact]
@@ -321,5 +334,12 @@ namespace Avalonia.Controls.UnitTests
                 x.Scaling == 1 &&
                 x.CreateRenderer(It.IsAny<IRenderRoot>()) == renderer.Object);
         }
+
+        private void ClearOpenWindows()
+        {
+            // HACK: We really need a decent way to have "statics" that can be scoped to
+            // AvaloniaLocator scopes.
+            ((IList<Window>)Window.OpenWindows).Clear();
+        }
     }
 }