diff --git a/src/Avalonia.X11/X11Atoms.cs b/src/Avalonia.X11/X11Atoms.cs index 32f1822e51..137757b9cf 100644 --- a/src/Avalonia.X11/X11Atoms.cs +++ b/src/Avalonia.X11/X11Atoms.cs @@ -114,6 +114,8 @@ namespace Avalonia.X11 public readonly IntPtr XA_WM_CLASS = (IntPtr)67; public readonly IntPtr XA_WM_TRANSIENT_FOR = (IntPtr)68; + public readonly IntPtr RR_PROPERTY_RANDR_EDID = (IntPtr)82; + public readonly IntPtr WM_PROTOCOLS; public readonly IntPtr WM_DELETE_WINDOW; public readonly IntPtr WM_TAKE_FOCUS; diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index af6d2674b6..b51497fb13 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -66,7 +66,8 @@ namespace Avalonia.X11 private X11Screen[] _cache; private X11Info _x11; private IntPtr _window; - + const int EDIDStructureLength = 32; // Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4 + public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings) { _settings = settings; @@ -82,6 +83,38 @@ namespace Avalonia.X11 _cache = null; } + private unsafe Size? GetPhysicalMonitorSizeFromEDID(IntPtr rrOutput) + { + if(rrOutput == IntPtr.Zero) + return null; + var properties = XRRListOutputProperties(_x11.Display,rrOutput, out int propertyCount); + var hasEDID = false; + for(var pc = 0; pc < propertyCount; pc++) + { + if(properties[pc] == _x11.Atoms.RR_PROPERTY_RANDR_EDID) + hasEDID = true; + } + if(!hasEDID) + return null; + XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.RR_PROPERTY_RANDR_EDID, 0, EDIDStructureLength, false, false, _x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _, out IntPtr prop); + if(actualType != _x11.Atoms.XA_INTEGER) + return null; + if(actualFormat != 8) // Expecting an byte array + return null; + + var edid = new byte[bytesAfter]; + Marshal.Copy(prop,edid,0,bytesAfter); + XFree(prop); + XFree(new IntPtr(properties)); + if(edid.Length < 22) + return null; + var width = edid[21]; // 0x15 1 Max. Horizontal Image Size cm. + var height = edid[22]; // 0x16 1 Max. Vertical Image Size cm. + if(width == 0 && height == 0) + return null; + return new Size(width * 10, height * 10); + } + public unsafe X11Screen[] Screens { get @@ -97,24 +130,28 @@ namespace Avalonia.X11 var namePtr = XGetAtomName(_x11.Display, mon.Name); var name = Marshal.PtrToStringAnsi(namePtr); XFree(namePtr); - - var density = 1d; + var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height); + Size? pSize = null; + double density = 0; if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) { - if (mon.MWidth == 0) - density = 1; - else - density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth); + for(int o = 0; o < mon.NOutput; o++) + { + var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]); + var outputDensity = 1d; + if(outputSize != null) + outputDensity = X11Screen.GuessPixelDensity(bounds, outputSize.Value); + if(density == 0 || density > outputDensity) + { + density = outputDensity; + pSize = outputSize; + } + } } - + if(density == 0) + density = 1; density *= _settings.GlobalScaleFactor; - - var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height); - screens[c] = new X11Screen(bounds, - mon.Primary != 0, - name, - (mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight), - density); + screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize, density); } XFree(new IntPtr(monitors)); @@ -163,7 +200,6 @@ namespace Avalonia.X11 } - public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => @@ -229,6 +265,7 @@ namespace Avalonia.X11 class X11Screen { private const int FullHDWidth = 1920; + private const int FullHDHeight = 1080; public bool Primary { get; } public string Name { get; set; } public PixelRect Bounds { get; set; } @@ -248,7 +285,7 @@ namespace Avalonia.X11 } else if (pixelDensity == null) { - PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width); + PixelDensity = GuessPixelDensity(bounds, physicalSize.Value); } else { @@ -257,7 +294,26 @@ namespace Avalonia.X11 } } - public static double GuessPixelDensity(double pixelWidth, double mmWidth) - => pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); + public static double GuessPixelDensity(PixelRect pixel, Size physical) + { + var calculatedDensity = 1d; + if(physical.Width > 0) + calculatedDensity = pixel.Width <= FullHDWidth ? 1 : Math.Max(1, pixel.Width / physical.Width * 25.4 / 96); + else if(physical.Height > 0) + calculatedDensity = pixel.Height <= FullHDHeight ? 1 : Math.Max(1, pixel.Height / physical.Height * 25.4 / 96); + + if(calculatedDensity > 3) + return 1; + else + { + var sanePixelDensities = new double[] { 1, 1.25, 1.50, 1.75, 2 }; + foreach(var saneDensity in sanePixelDensities) + { + if(calculatedDensity <= saneDensity + 0.20) + return saneDensity; + } + return sanePixelDensities.Last(); + } + } } } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 0ae5c16aef..ae4954a2d5 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1870,7 +1870,7 @@ namespace Avalonia.X11 { public const string XNFontSet = "fontSet"; } - struct XRRMonitorInfo { + unsafe struct XRRMonitorInfo { public IntPtr Name; public int Primary; public int Automatic; @@ -1881,6 +1881,6 @@ namespace Avalonia.X11 { public int Height; public int MWidth; public int MHeight; - public IntPtr Outputs; + public IntPtr* Outputs; } } diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 85aa4862b7..bec94e63c9 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -500,6 +500,13 @@ namespace Avalonia.X11 [DllImport(libX11Randr)] public static extern XRRMonitorInfo* XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors); + + [DllImport(libX11Randr)] + public static extern IntPtr* XRRListOutputProperties(IntPtr dpy, IntPtr output, out int count); + + [DllImport(libX11Randr)] + public static extern int XRRGetOutputProperty(IntPtr dpy, IntPtr output, IntPtr atom, int offset, int length, bool _delete, bool pending, IntPtr req_type, out IntPtr actual_type, out int actual_format, out int nitems, out long bytes_after, out IntPtr prop); + [DllImport(libX11Randr)] public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);