diff --git a/src/Avalonia.Input/Raw/RawDragEvent.cs b/src/Avalonia.Input/Raw/RawDragEvent.cs index d251fe8640..208d898a8a 100644 --- a/src/Avalonia.Input/Raw/RawDragEvent.cs +++ b/src/Avalonia.Input/Raw/RawDragEvent.cs @@ -3,7 +3,7 @@ public class RawDragEvent : RawInputEventArgs { public IInputElement InputRoot { get; } - public Point Location { get; } + public Point Location { get; set; } public IDataObject Data { get; } public DragDropEffects Effects { get; set; } public RawDragEventType Type { get; } diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj index 40db6d8c62..087ba017ae 100644 --- a/src/Avalonia.X11/Avalonia.X11.csproj +++ b/src/Avalonia.X11/Avalonia.X11.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs index 8e48a4089a..d99d7970b6 100644 --- a/src/Avalonia.X11/X11Framebuffer.cs +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using Avalonia.Platform; +using SkiaSharp; using static Avalonia.X11.XLib; namespace Avalonia.X11 { @@ -9,11 +11,11 @@ namespace Avalonia.X11 private readonly IntPtr _xid; private IUnmanagedBlob _blob; - public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) + public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, double factor) { _display = display; _xid = xid; - Size = new PixelSize(width * factor, height * factor); + Size = new PixelSize(width, height); RowBytes = width * 4; Dpi = new Vector(96, 96) * factor; Format = PixelFormat.Bgra8888; @@ -34,7 +36,7 @@ namespace Avalonia.X11 image.bitmap_bit_order = 0;// LSBFirst; image.bitmap_pad = bitsPerPixel; image.depth = 24; - image.bytes_per_line = RowBytes - Size.Width * 4; + image.bytes_per_line = RowBytes; image.bits_per_pixel = bitsPerPixel; XLockDisplay(_display); XInitImage(ref image); diff --git a/src/Avalonia.X11/X11FramebufferSurface.cs b/src/Avalonia.X11/X11FramebufferSurface.cs index 05b21efb0c..6caec0a66f 100644 --- a/src/Avalonia.X11/X11FramebufferSurface.cs +++ b/src/Avalonia.X11/X11FramebufferSurface.cs @@ -8,11 +8,13 @@ namespace Avalonia.X11 { private readonly IntPtr _display; private readonly IntPtr _xid; + private readonly Func _scaling; - public X11FramebufferSurface(IntPtr display, IntPtr xid) + public X11FramebufferSurface(IntPtr display, IntPtr xid, Func scaling) { _display = display; _xid = xid; + _scaling = scaling; } public ILockedFramebuffer Lock() @@ -21,7 +23,7 @@ namespace Avalonia.X11 XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height, out var bw, out var d); XUnlockDisplay(_display); - return new X11Framebuffer(_display, _xid, width, height, 1); + return new X11Framebuffer(_display, _xid, width, height, _scaling()); } } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 675ed4bd53..efda30b2fc 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -21,6 +21,8 @@ namespace Avalonia.X11 public Dictionary> Windows = new Dictionary>(); public XI2Manager XI2; public X11Info Info { get; private set; } + public IX11Screens X11Screens { get; private set; } + public IScreenImpl Screens { get; private set; } public void Initialize() { XInitThreads(); @@ -44,7 +46,9 @@ namespace Avalonia.X11 .Bind().ToConstant(new SystemDialogsStub()) .Bind().ToConstant(new IconLoaderStub()) .Bind().ToConstant(new Gtk3ForeignX11SystemDialog()); - X11Screens.Init(this); + + X11Screens = Avalonia.X11.X11Screens.Init(this); + Screens = new X11Screens(X11Screens); if (Info.XInputVersion != null) { var xi2 = new XI2Manager(); diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index 46ad4d7320..560cc04a28 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -13,7 +13,7 @@ namespace Avalonia.X11 { private IX11Screens _impl; - private X11Screens(IX11Screens impl) + public X11Screens(IX11Screens impl) { _impl = impl; } @@ -141,16 +141,15 @@ namespace Avalonia.X11 public X11Screen[] Screens { get; } } - public static void Init(AvaloniaX11Platform platform) + public static IX11Screens Init(AvaloniaX11Platform platform) { var info = platform.Info; var settings = X11ScreensUserSettings.Detect(); var impl = (info.RandrVersion != null && info.RandrVersion >= new Version(1, 5)) ? new Randr15ScreensImpl(platform, settings) : (IX11Screens)new FallbackScreensImpl(info, settings); - - AvaloniaLocator.CurrentMutable.Bind().ToConstant(impl); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(new X11Screens(impl)); + + return impl; } @@ -207,7 +206,7 @@ namespace Avalonia.X11 //Ignore } - return null; + return rv; } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 4441006fda..b919424efe 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -27,13 +27,15 @@ namespace Avalonia.X11 private IInputRoot _inputRoot; private readonly IMouseDevice _mouse; private readonly IKeyboardDevice _keyboard; - private Point _position; + private Point? _position; + private PixelSize _realSize; private IntPtr _handle; private IntPtr _xic; private IntPtr _renderHandle; private bool _mapped; private HashSet _transientChildren = new HashSet(); private X11Window _transientParent; + public object SyncRoot { get; } = new object(); class InputEventContainer { @@ -79,7 +81,7 @@ namespace Avalonia.X11 SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr); Handle = new PlatformHandle(_handle, "XID"); - ClientSize = new Size(400, 400); + _realSize = new PixelSize(300, 200); platform.Windows[_handle] = OnEvent; XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask @@ -96,12 +98,12 @@ namespace Avalonia.X11 var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService(); var surfaces = new List { - new X11FramebufferSurface(_x11.DeferredDisplay, _handle) + new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling) }; if (feature != null) surfaces.Insert(0, new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext, - new SurfaceInfo(_x11.DeferredDisplay, _handle, _renderHandle))); + new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle))); Surfaces = surfaces.ToArray(); UpdateMotifHits(); XFlush(_x11.Display); @@ -109,11 +111,13 @@ namespace Avalonia.X11 class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo { + private readonly X11Window _window; private readonly IntPtr _display; private readonly IntPtr _parent; - public SurfaceInfo(IntPtr display, IntPtr parent, IntPtr xid) + public SurfaceInfo(X11Window window, IntPtr display, IntPtr parent, IntPtr xid) { + _window = window; _display = display; _parent = parent; Handle = xid; @@ -134,7 +138,7 @@ namespace Avalonia.X11 } } - public double Scaling { get; } = 1; + public double Scaling => _window.Scaling; } void UpdateMotifHits() @@ -188,10 +192,20 @@ namespace Avalonia.X11 XSetWMNormalHints(_x11.Display, _handle, ref hints); } - - public Size ClientSize { get; private set; } - //TODO - public double Scaling { get; } = 1; + + public Size ClientSize => new Size(_realSize.Width / Scaling, _realSize.Height / Scaling); + + public double Scaling + { + get + { + lock (SyncRoot) + return _scaling; + + } + private set => _scaling = value; + } + public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -209,6 +223,11 @@ namespace Avalonia.X11 new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); void OnEvent(XEvent ev) + { + lock (SyncRoot) + OnEventSync(ev); + } + void OnEventSync(XEvent ev) { if(XFilterEvent(ref ev, _handle)) return; @@ -269,6 +288,8 @@ namespace Avalonia.X11 } else if (ev.type == XEventName.ConfigureNotify) { + if (ev.ConfigureEvent.window != _handle) + return; var needEnqueue = (_configure == null); _configure = ev.ConfigureEvent; if (needEnqueue) @@ -278,22 +299,27 @@ namespace Avalonia.X11 return; var cev = _configure.Value; _configure = null; - var nsize = new Size(cev.width, cev.height); - XTranslateCoordinates(_x11.Display, _handle, _x11.DefaultRootWindow, 0, 0, out var xret, - out var yret, out var _); - var npos = new Point(xret, yret); - var changedSize = ClientSize != nsize; - var changedPos = npos != _position; - ClientSize = nsize; + var nsize = new PixelSize(cev.width, cev.height); + var npos = new Point(cev.x, cev.y); + var changedSize = _realSize != nsize; + var changedPos = _position == null || npos != _position; + _realSize = nsize; _position = npos; - if (changedSize) - Resized?.Invoke(nsize); + bool updatedSizeViaScaling = false; if (changedPos) + { PositionChanged?.Invoke(npos); + updatedSizeViaScaling = UpdateScaling(); + } + + if (changedSize && !updatedSizeViaScaling) + Resized?.Invoke(ClientSize); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); }, DispatcherPriority.Layout); + XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height); } - else if (ev.type == XEventName.DestroyNotify) + else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle) { Cleanup(); } @@ -338,6 +364,28 @@ namespace Avalonia.X11 } } + private bool UpdateScaling() + { + lock (SyncRoot) + { + var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) + .FirstOrDefault(m => m.Bounds.Contains(Position)); + var newScaling = monitor?.PixelDensity ?? Scaling; + if (Scaling != newScaling) + { + Console.WriteLine( + $"Updating scaling from {Scaling} to {newScaling} as a response to position change to {Position}"); + var oldScaledSize = ClientSize; + Scaling = newScaling; + ScalingChanged?.Invoke(Scaling); + Resize(oldScaledSize, true); + return true; + } + + return false; + } + } + private WindowState _lastWindowState; public WindowState WindowState { @@ -431,6 +479,7 @@ namespace Avalonia.X11 private bool _systemDecorations = true; private bool _canResize = true; private (Size minSize, Size maxSize) _minMaxSize; + private double _scaling = 1; void ScheduleInput(RawInputEventArgs args, ref XEvent xev) { @@ -440,6 +489,10 @@ namespace Avalonia.X11 public void ScheduleInput(RawInputEventArgs args) { + if (args is RawMouseEventArgs mouse) + mouse.Position = mouse.Position / Scaling; + if (args is RawDragEvent drag) + drag.Location = drag.Location / Scaling; _lastEvent = new InputEventContainer() {Event = args}; _inputQueue.Enqueue(_lastEvent); @@ -564,35 +617,35 @@ namespace Avalonia.X11 public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(Point point) => new Point(point.X - _position.X, point.Y - _position.Y); + public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); - public Point PointToScreen(Point point) => new Point(point.X + _position.X, point.Y + _position.Y); + public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y); public void SetSystemDecorations(bool enabled) { _systemDecorations = enabled; UpdateMotifHits(); } + + + public void Resize(Size clientSize) => Resize(clientSize, false); - - public void Resize(Size clientSize) + void Resize(Size clientSize, bool force) { - if (clientSize == ClientSize) + if (!force && clientSize == ClientSize) return; - var changes = new XWindowChanges - { - width = (int)clientSize.Width, - height = (int)clientSize.Height - }; - var needResize = clientSize != ClientSize; - XConfigureWindow(_x11.Display, _handle, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, - ref changes); + + var needImmediatePopupResize = clientSize != ClientSize; + + var pixelSize = new PixelSize((int)(clientSize.Width * Scaling), (int)(clientSize.Height * Scaling)); + XConfigureResizeWindow(_x11.Display, _handle, pixelSize); + XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize); XFlush(_x11.Display); - if (_popup && needResize) + if (force || (_popup && needImmediatePopupResize)) { - ClientSize = clientSize; - Resized?.Invoke(clientSize); + _realSize = pixelSize; + Resized?.Invoke(ClientSize); } } @@ -618,7 +671,7 @@ namespace Avalonia.X11 public Point Position { - get => _position; + get => _position ?? default; set { var changes = new XWindowChanges @@ -650,9 +703,10 @@ namespace Avalonia.X11 } - public IScreenImpl Screen { get; } = AvaloniaLocator.CurrentMutable.GetService(); - public Size MaxClientSize { get; } = new Size(1920, 1280); + public IScreenImpl Screen => _platform.Screens; + public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity) + .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); void SendNetWMMessage(IntPtr message_type, IntPtr l0, diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index a6c2c6ac1c..b47f79d7bd 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -115,6 +115,21 @@ namespace Avalonia.X11 public static extern uint XConfigureWindow(IntPtr display, IntPtr window, ChangeWindowFlags value_mask, ref XWindowChanges values); + public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, PixelSize size) + => XConfigureResizeWindow(display, window, size.Width, size.Height); + + public static uint XConfigureResizeWindow(IntPtr display, IntPtr window, int width, int height) + { + var changes = new XWindowChanges + { + width = width, + height = height + }; + + return XConfigureWindow(display, window, ChangeWindowFlags.CWHeight | ChangeWindowFlags.CWWidth, + ref changes); + } + [DllImport(libX11)] public static extern IntPtr XInternAtom(IntPtr display, string atom_name, bool only_if_exists);