Skip to content

Commit

Permalink
Merge pull request #5178 from robinxan/master
Browse files Browse the repository at this point in the history
More reliable pixendensitydetection under Xorg/Randr
  • Loading branch information
danwalmsley authored Jan 10, 2021
2 parents b292daa + 40a5f40 commit 13e0e30
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 21 deletions.
2 changes: 2 additions & 0 deletions src/Avalonia.X11/X11Atoms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ internal class X11Atoms
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;
Expand Down
94 changes: 75 additions & 19 deletions src/Avalonia.X11/X11Screens.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class Randr15ScreensImpl : IX11Screens
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;
Expand All @@ -82,6 +83,38 @@ private void OnEvent(XEvent ev)
_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
Expand All @@ -97,24 +130,28 @@ public unsafe X11Screen[] Screens
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));
Expand Down Expand Up @@ -163,7 +200,6 @@ public static IX11Screens Init(AvaloniaX11Platform platform)

}


public int ScreenCount => _impl.Screens.Length;

public IReadOnlyList<Screen> AllScreens =>
Expand Down Expand Up @@ -229,6 +265,7 @@ public static X11ScreensUserSettings Detect()
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; }
Expand All @@ -248,7 +285,7 @@ public X11Screen(PixelRect bounds, bool primary,
}
else if (pixelDensity == null)
{
PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width);
PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
}
else
{
Expand All @@ -257,7 +294,26 @@ public X11Screen(PixelRect bounds, bool primary,
}
}

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();
}
}
}
}
4 changes: 2 additions & 2 deletions src/Avalonia.X11/X11Structs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,7 @@ internal static class XNames
public const string XNFontSet = "fontSet";
}

struct XRRMonitorInfo {
unsafe struct XRRMonitorInfo {
public IntPtr Name;
public int Primary;
public int Automatic;
Expand All @@ -1881,6 +1881,6 @@ struct XRRMonitorInfo {
public int Height;
public int MWidth;
public int MHeight;
public IntPtr Outputs;
public IntPtr* Outputs;
}
}
7 changes: 7 additions & 0 deletions src/Avalonia.X11/XLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,13 @@ public static extern int XRRQueryVersion(IntPtr dpy,
[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);

Expand Down

0 comments on commit 13e0e30

Please sign in to comment.