Pārlūkot izejas kodu

[X11] Per-monitor DPI support

Nikita Tsukanov 6 gadi atpakaļ
vecāks
revīzija
a1ad8f0bea

+ 1 - 1
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; }

+ 1 - 0
src/Avalonia.X11/Avalonia.X11.csproj

@@ -8,6 +8,7 @@
     <ItemGroup>
         <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
         <ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
+        <ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
     </ItemGroup>
 
 </Project>

+ 5 - 3
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);

+ 4 - 2
src/Avalonia.X11/X11FramebufferSurface.cs

@@ -8,11 +8,13 @@ namespace Avalonia.X11
     {
         private readonly IntPtr _display;
         private readonly IntPtr _xid;
+        private readonly Func<double> _scaling;
 
-        public X11FramebufferSurface(IntPtr display, IntPtr xid)
+        public X11FramebufferSurface(IntPtr display, IntPtr xid, Func<double> 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());
         }
     }
 }

+ 5 - 1
src/Avalonia.X11/X11Platform.cs

@@ -21,6 +21,8 @@ namespace Avalonia.X11
         public Dictionary<IntPtr, Action<XEvent>> Windows = new Dictionary<IntPtr, Action<XEvent>>();
         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<ISystemDialogImpl>().ToConstant(new SystemDialogsStub())
                 .Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub())
                 .Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog());
-            X11Screens.Init(this);
+            
+            X11Screens = Avalonia.X11.X11Screens.Init(this);
+            Screens = new X11Screens(X11Screens);
             if (Info.XInputVersion != null)
             {
                 var xi2 = new XI2Manager();

+ 5 - 6
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<IX11Screens>().ToConstant(impl);
-            AvaloniaLocator.CurrentMutable.Bind<IScreenImpl>().ToConstant(new X11Screens(impl));
+
+            return impl;
 
         }
 
@@ -207,7 +206,7 @@ namespace Avalonia.X11
                     //Ignore
                 }
 
-                return null;  
+                return rv;  
             }
 
 

+ 93 - 39
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<X11Window> _transientChildren = new HashSet<X11Window>();
         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<IWindowingPlatformGlFeature>();
             var surfaces = new List<object>
             {
-                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<object> Surfaces { get; }
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
@@ -209,6 +223,11 @@ namespace Avalonia.X11
             new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
 
         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<IScreenImpl>();
-        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,

+ 15 - 0
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);