From 460a3549d5b0ec22e37ac26da186211632e67ff6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 16 Jul 2024 18:47:43 -0700 Subject: [PATCH] Fix headless screens (#16348) * Fix headless screens being null * Remove unused properties --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 1 - src/Avalonia.Controls/Screens.cs | 4 ++- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 1 - .../Avalonia.Headless/HeadlessWindowImpl.cs | 10 +++++-- .../FramebufferToplevelImpl.cs | 1 - src/Tizen/Avalonia.Tizen/TopLevelImpl.cs | 1 - src/iOS/Avalonia.iOS/AvaloniaView.cs | 1 - .../ServicesTests.cs | 29 +++++++++++++++++++ .../CompositorTestServices.cs | 1 - 9 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 tests/Avalonia.Headless.UnitTests/ServicesTests.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 83a43d883f..79052bf9bd 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -101,7 +101,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform internal InvalidationAwareSurfaceView InternalView => _view; public double DesktopScaling => RenderScaling; - public IScreenImpl? Screen { get; } public IPlatformHandle Handle => _view; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index 7892efe559..d940900e2c 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -163,7 +163,9 @@ namespace Avalonia.Controls var tl = visual.PointToScreen(visual.Bounds.TopLeft); var br = visual.PointToScreen(visual.Bounds.BottomRight); - return ScreenFromBounds(new PixelRect(tl, br)); + // Attempt to get screen from the physical position on any screen first. Fallback to the screen hosting top level. + return ScreenFromBounds(new PixelRect(tl, br)) + ?? ScreenFromTopLevel(topLevel); } else { diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index ffc78e9423..9c15d8d1a5 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -125,7 +125,6 @@ namespace Avalonia.Browser } public double DesktopScaling => RenderScaling; - public IScreenImpl? Screen { get; } public IPlatformHandle? Handle { get; } public Size ClientSize => _surface?.ClientSize ?? new Size(1, 1); public Size? FrameSize => null; diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index b5ab9bbf62..11d62d11c6 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -19,6 +19,7 @@ namespace Avalonia.Headless private static int _nextGlobalZOrder = 1; private readonly IKeyboardDevice _keyboard; + private readonly IScreenImpl _screen; private readonly Stopwatch _st = Stopwatch.StartNew(); private readonly Pointer _mousePointer; private WriteableBitmap? _lastRenderedFrame; @@ -32,6 +33,7 @@ namespace Avalonia.Headless IsPopup = isPopup; Surfaces = new object[] { this }; _keyboard = AvaloniaLocator.Current.GetRequiredService(); + _screen = new HeadlessScreensStub(); _mousePointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); MouseDevice = new MouseDevice(_mousePointer); ClientSize = new Size(1024, 768); @@ -150,7 +152,6 @@ namespace Avalonia.Headless } - public IScreenImpl Screen { get; } = new HeadlessScreensStub(); public WindowState WindowState { get; set; } public Action? WindowStateChanged { get; set; } public void SetTitle(string? title) @@ -266,11 +267,16 @@ namespace Avalonia.Headless public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); public object? TryGetFeature(Type featureType) { - if(featureType == typeof(IClipboard)) + if (featureType == typeof(IClipboard)) { return AvaloniaLocator.Current.GetRequiredService(); } + if (featureType == typeof(IScreenImpl)) + { + return _screen; + } + return null; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 7ba9d42159..2dc7b36b11 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -54,7 +54,6 @@ using Avalonia.Rendering.Composition; } public double DesktopScaling => 1; - public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; } public Size ClientSize => ScaledSize; public Size? FrameSize => null; diff --git a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs index 6425a0e9fe..862d0d7e78 100644 --- a/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs +++ b/src/Tizen/Avalonia.Tizen/TopLevelImpl.cs @@ -27,7 +27,6 @@ internal class TopLevelImpl : ITopLevelImpl } public double DesktopScaling => RenderScaling; - public IScreenImpl? Screen { get; } public IPlatformHandle? Handle { get; } public Size ClientSize => _view.ClientSize; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index cea61648dd..ff7098251b 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -222,7 +222,6 @@ namespace Avalonia.iOS } public double DesktopScaling => RenderScaling; - public IScreenImpl? Screen { get; } public IPlatformHandle? Handle { get; } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); public Size? FrameSize => null; diff --git a/tests/Avalonia.Headless.UnitTests/ServicesTests.cs b/tests/Avalonia.Headless.UnitTests/ServicesTests.cs new file mode 100644 index 0000000000..251fe86ff5 --- /dev/null +++ b/tests/Avalonia.Headless.UnitTests/ServicesTests.cs @@ -0,0 +1,29 @@ +using System; +using System.Reactive.Disposables; +using System.Threading; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.Threading; + +namespace Avalonia.Headless.UnitTests; + +public class ServicesTests +{ +#if NUNIT + [AvaloniaTest, Timeout(10000)] +#elif XUNIT + [AvaloniaFact(Timeout = 10000)] +#endif + public void Can_Access_Screens() + { + var window = new Window(); + var screens = window.Screens; + Assert.NotNull(screens); + + var currentScreenFromWindow = screens.ScreenFromWindow(window); + var currentScreenFromVisual = screens.ScreenFromVisual(window); + + Assert.True(ReferenceEquals(currentScreenFromWindow, currentScreenFromVisual)); + } +} diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 369b39374e..4898604182 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -159,7 +159,6 @@ public class CompositorTestServices : IDisposable } public double DesktopScaling => 1; - public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; } public Size ClientSize { get; } public Size? FrameSize { get; }