From 63751032ce613a641a6c58de7ce8a89c0eb2661c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 15:20:52 +0100 Subject: [PATCH 1/8] Merge pull request #8264 from AvaloniaUI/fixes/osx-invalidate-shadow-onshow osx: ensure shadow is invalidated on show. --- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 289694a9b87..3c4e1e314b6 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -98,6 +98,8 @@ } UpdateStyle(); + + [Window invalidateShadow]; if (ShouldTakeFocusOnShow() && activate) { [Window orderFront:Window]; From 5d360fa655a7804b0660b1f3dd621435c8b642f3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 15:40:46 +0100 Subject: [PATCH 2/8] Merge pull request #8265 from AvaloniaUI/fixes/osx-invalidate-shadow-always whenever we become key... dispatch invalidateShadow --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index d673523983a..03bbed819a3 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -283,6 +283,14 @@ -(void)becomeKeyWindow - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification { _parent->BringToFront(); + + dispatch_async(dispatch_get_main_queue(), ^{ + @try { + [self invalidateShadow]; + } + @finally{ + } + }); } - (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification From d18f97973d042bd202ff7e49a9668cd32b056e34 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 15:48:34 +0100 Subject: [PATCH 3/8] Merge pull request #8252 from AvaloniaUI/safe-log-typed-adapter Check if BindingValue actually has a value before logging an error --- src/Avalonia.Base/Reactive/TypedBindingAdapter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs b/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs index f60ef247d59..f75917a00e3 100644 --- a/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs +++ b/src/Avalonia.Base/Reactive/TypedBindingAdapter.cs @@ -30,13 +30,15 @@ public void OnNext(BindingValue value) } catch (InvalidCastException e) { + var unwrappedValue = value.HasValue ? value.Value : null; + Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log( _target, "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", _property.Name, _property.PropertyType, - value.Value, - value.Value?.GetType()); + unwrappedValue, + unwrappedValue?.GetType()); PublishNext(BindingValue.BindingError(e)); } } From 2bd97840aeb1e8df71b5f99dee392ef704b06e0f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 15:48:59 +0100 Subject: [PATCH 4/8] Merge pull request #8253 from AvaloniaUI/stop-iscancel-on-detached Stop listening for IsCancel on button detached --- src/Avalonia.Controls/Button.cs | 7 ++ .../ButtonTests.cs | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 7e24471bde6..09370afdb77 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -222,6 +222,13 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e StopListeningForDefault(inputElement); } } + if (IsCancel) + { + if (e.Root is IInputElement inputElement) + { + StopListeningForCancel(inputElement); + } + } } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index f4acf5ea37d..2e3623cc3cf 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -309,6 +309,80 @@ public void Button_Invokes_Doesnt_Execute_When_Button_Disabled() Assert.Equal(0, raised); } + + [Fact] + public void Button_IsDefault_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var raised = 0; + var target = new Button(); + var window = new Window { Content = target }; + window.Show(); + + target.Click += (s, e) => ++raised; + + target.IsDefault = false; + window.RaiseEvent(CreateKeyDownEvent(Key.Enter)); + Assert.Equal(0, raised); + + target.IsDefault = true; + window.RaiseEvent(CreateKeyDownEvent(Key.Enter)); + Assert.Equal(1, raised); + + target.IsDefault = false; + window.RaiseEvent(CreateKeyDownEvent(Key.Enter)); + Assert.Equal(1, raised); + + target.IsDefault = true; + window.RaiseEvent(CreateKeyDownEvent(Key.Enter)); + Assert.Equal(2, raised); + + window.Content = null; + // To check if handler was raised on the button, when it's detached, we need to pass it as a source manually. + window.RaiseEvent(CreateKeyDownEvent(Key.Enter, target)); + Assert.Equal(2, raised); + } + } + + [Fact] + public void Button_IsCancel_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var raised = 0; + var target = new Button(); + var window = new Window { Content = target }; + window.Show(); + + target.Click += (s, e) => ++raised; + + target.IsCancel = false; + window.RaiseEvent(CreateKeyDownEvent(Key.Escape)); + Assert.Equal(0, raised); + + target.IsCancel = true; + window.RaiseEvent(CreateKeyDownEvent(Key.Escape)); + Assert.Equal(1, raised); + + target.IsCancel = false; + window.RaiseEvent(CreateKeyDownEvent(Key.Escape)); + Assert.Equal(1, raised); + + target.IsCancel = true; + window.RaiseEvent(CreateKeyDownEvent(Key.Escape)); + Assert.Equal(2, raised); + + window.Content = null; + window.RaiseEvent(CreateKeyDownEvent(Key.Escape, target)); + Assert.Equal(2, raised); + } + } + + private KeyEventArgs CreateKeyDownEvent(Key key, IInteractive source = null) + { + return new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = key, Source = source }; + } private class TestButton : Button, IRenderRoot { From ab0c1357c8c477716bcf1d63a707937274635d36 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 17:24:57 +0100 Subject: [PATCH 5/8] Merge pull request #8267 from AvaloniaUI/fixes/osx-cef-electron-compatibility osx: restore missing api for cef - electron compatibility --- native/Avalonia.Native/src/OSX/app.mm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 14f1f6888c1..a15d0c96013 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -82,6 +82,17 @@ - (void)sendEvent:(NSEvent *)event _isHandlingSendEvent = oldHandling; } } + +// This is needed for certain embedded controls DO NOT REMOVE.. +- (BOOL) isHandlingSendEvent +{ + return _isHandlingSendEvent; +} + +- (void)setHandlingSendEvent:(BOOL)handlingSendEvent +{ + _isHandlingSendEvent = handlingSendEvent; +} @end extern void InitializeAvnApp(IAvnApplicationEvents* events) From 591b4695c48097f8ac0b4b660f0085a5db58f005 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 19:42:31 +0100 Subject: [PATCH 6/8] tidy nswindow initialisation. --- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 1 - .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 36 ++++++------------- native/Avalonia.Native/src/OSX/WindowImpl.mm | 5 --- 3 files changed, 10 insertions(+), 32 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 787827c9b49..2baf3b09b5f 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -110,7 +110,6 @@ BEGIN_INTERFACE_MAP() private: void CreateNSWindow (bool isDialog); void CleanNSWindow (); - void InitialiseNSWindow (); NSCursor *cursor; ComPtr _glContext; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 3c4e1e314b6..7285c84bff8 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -38,7 +38,16 @@ lastMenu = nullptr; CreateNSWindow(usePanel); - InitialiseNSWindow(); + + [Window setContentView:StandardContainer]; + [Window setStyleMask:NSWindowStyleMaskBorderless]; + [Window setBackingType:NSBackingStoreBuffered]; + + [Window setContentMinSize:lastMinSize]; + [Window setContentMaxSize:lastMaxSize]; + + [Window setOpaque:false]; + [Window setHasShadow:true]; } HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { @@ -568,31 +577,6 @@ } } -void WindowBaseImpl::InitialiseNSWindow() { - if(Window != nullptr) { - [Window setContentView:StandardContainer]; - [Window setStyleMask:NSWindowStyleMaskBorderless]; - [Window setBackingType:NSBackingStoreBuffered]; - - [Window setContentSize:lastSize]; - [Window setContentMinSize:lastMinSize]; - [Window setContentMaxSize:lastMaxSize]; - - [Window setOpaque:false]; - - [Window setHasShadow:true]; - [Window invalidateShadow]; - - if (lastMenu != nullptr) { - [GetWindowProtocol() applyMenu:lastMenu]; - - if ([Window isKeyWindow]) { - [GetWindowProtocol() showWindowMenuWithAppMenu]; - } - } - } -} - id WindowBaseImpl::GetWindowProtocol() { if(Window == nullptr) { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 782c8b80dcc..cc276d19b20 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -54,11 +54,6 @@ [GetWindowProtocol() setIsExtended:true]; SetExtendClientArea(true); } - - if(_parent != nullptr) - { - SetParent(_parent); - } } HRESULT WindowImpl::Show(bool activate, bool isDialog) { From 09c9a98b9d27faf05d387d34ef4b07d21e9b0541 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 19:46:52 +0100 Subject: [PATCH 7/8] only setcontentsize if window is shown or about to show. --- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 7285c84bff8..d90e89fb466 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -98,6 +98,8 @@ START_COM_CALL; @autoreleasepool { + [Window setContentSize:lastSize]; + if(hasPosition) { SetPosition(lastPositionSet); @@ -300,8 +302,7 @@ if (!_shown) { BaseEvents->Resized(AvnSize{x, y}, reason); } - - if(Window != nullptr) { + else if(Window != nullptr) { [Window setContentSize:lastSize]; [Window invalidateShadow]; } From 4aedf52935dd6680087a318b6b48d13c57df7c08 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 3 Jun 2022 20:47:11 +0100 Subject: [PATCH 8/8] Merge pull request #8268 from AvaloniaUI/feature/x11-xsync-counter [X11] Added support for the basic version of _NET_WM_SYNC_REQUEST protocol --- src/Avalonia.X11/X11Atoms.cs | 1 + src/Avalonia.X11/X11Info.cs | 10 ++++++++ src/Avalonia.X11/X11Window.cs | 44 +++++++++++++++++++++++++++-------- src/Avalonia.X11/XLib.cs | 17 ++++++++++++++ 4 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index b59883ef94f..cfda68f9e87 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -155,6 +155,7 @@ internal class X11Atoms public readonly IntPtr _NET_FRAME_EXTENTS; public readonly IntPtr _NET_WM_PING; public readonly IntPtr _NET_WM_SYNC_REQUEST; + public readonly IntPtr _NET_WM_SYNC_REQUEST_COUNTER; public readonly IntPtr _NET_SYSTEM_TRAY_S; public readonly IntPtr _NET_SYSTEM_TRAY_ORIENTATION; public readonly IntPtr _NET_SYSTEM_TRAY_OPCODE; diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 9920907601d..13dc460f45a 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -33,6 +33,7 @@ unsafe class X11Info public IntPtr LastActivityTimestamp { get; set; } public XVisualInfo? TransparentVisualInfo { get; set; } public bool HasXim { get; set; } + public bool HasXSync { get; set; } public IntPtr DefaultFontSet { get; set; } public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) @@ -101,6 +102,15 @@ public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) { //Ignore, XI is not supported } + + try + { + HasXSync = XSyncInitialize(display, out _, out _) != Status.Success; + } + catch + { + //Ignore, XSync is not supported + } } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 520b35dd033..5e8cd36ac33 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -45,6 +45,8 @@ unsafe partial class X11Window : IWindowImpl, IPopupImpl, IXI2Client, private IntPtr _handle; private IntPtr _xic; private IntPtr _renderHandle; + private IntPtr _xSyncCounter; + private XSyncValue _xSyncValue; private bool _mapped; private bool _wasMappedAtLeastOnce = false; private double? _scalingOverride; @@ -188,6 +190,16 @@ public X11Window(AvaloniaX11Platform platform, IWindowImpl popupParent) NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle); NativeControlHost = new X11NativeControlHost(_platform, this); InitializeIme(); + + XChangeProperty(_x11.Display, _handle, _x11.Atoms.WM_PROTOCOLS, _x11.Atoms.XA_ATOM, 32, + PropertyMode.Replace, new[] { _x11.Atoms.WM_DELETE_WINDOW, _x11.Atoms._NET_WM_SYNC_REQUEST }, 2); + + if (_x11.HasXSync) + { + _xSyncCounter = XSyncCreateCounter(_x11.Display, _xSyncValue); + XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_SYNC_REQUEST_COUNTER, + _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); + } } class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo @@ -381,15 +393,7 @@ void OnEvent(ref XEvent ev) (ev.type == XEventName.VisibilityNotify && ev.VisibilityEvent.state < 2)) { - if (!_triggeredExpose) - { - _triggeredExpose = true; - Dispatcher.UIThread.Post(() => - { - _triggeredExpose = false; - DoPaint(); - }, DispatcherPriority.Render); - } + EnqueuePaint(); } else if (ev.type == XEventName.FocusIn) { @@ -501,6 +505,7 @@ void OnEvent(ref XEvent ev) if (_useRenderWindow) XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); + EnqueuePaint(); } else if (ev.type == XEventName.DestroyNotify && ev.DestroyWindowEvent.window == _handle) @@ -516,7 +521,11 @@ void OnEvent(ref XEvent ev) if (Closing?.Invoke() != true) Dispose(); } - + else if (ev.ClientMessageEvent.ptr1 == _x11.Atoms._NET_WM_SYNC_REQUEST) + { + _xSyncValue.Lo = new UIntPtr(ev.ClientMessageEvent.ptr3.ToPointer()).ToUInt32(); + _xSyncValue.Hi = ev.ClientMessageEvent.ptr4.ToInt32(); + } } } else if (ev.type == XEventName.KeyPress || ev.type == XEventName.KeyRelease) @@ -728,9 +737,24 @@ void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods) ScheduleInput(mev, ref ev); } + void EnqueuePaint() + { + if (!_triggeredExpose) + { + _triggeredExpose = true; + Dispatcher.UIThread.Post(() => + { + _triggeredExpose = false; + DoPaint(); + }, DispatcherPriority.Render); + } + } + void DoPaint() { Paint?.Invoke(new Rect()); + if (_xSyncCounter != IntPtr.Zero) + XSyncSetCounter(_x11.Display, _xSyncCounter, _xSyncValue); } public void Invalidate(Rect rect) diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index e2b370821ff..464ec4f1c89 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -542,6 +542,18 @@ public static extern bool XQueryExtension(IntPtr display, [MarshalAs(UnmanagedTy public static extern int XRRQueryExtension (IntPtr dpy, out int event_base_return, out int error_base_return); + + [DllImport(libX11Ext)] + public static extern Status XSyncInitialize(IntPtr dpy, out int event_base_return, out int error_base_return); + + [DllImport(libX11Ext)] + public static extern IntPtr XSyncCreateCounter(IntPtr dpy, XSyncValue initialValue); + + [DllImport(libX11Ext)] + public static extern int XSyncDestroyCounter(IntPtr dpy, IntPtr counter); + + [DllImport(libX11Ext)] + public static extern int XSyncSetCounter(IntPtr dpy, IntPtr counter, XSyncValue value); [DllImport(libX11Randr)] public static extern int XRRQueryVersion(IntPtr dpy, @@ -627,6 +639,11 @@ public struct XGeometry public int bw; public int d; } + + public struct XSyncValue { + public int Hi; + public uint Lo; + } public static bool XGetGeometry(IntPtr display, IntPtr window, out XGeometry geo) {