|
|
|
@ -3,6 +3,7 @@ using System.Diagnostics; |
|
|
|
using System.Linq; |
|
|
|
using System.Runtime.InteropServices; |
|
|
|
using Avalonia.Platform; |
|
|
|
using Avalonia.Threading; |
|
|
|
using static Avalonia.X11.XLib; |
|
|
|
|
|
|
|
namespace Avalonia.X11.Screens; |
|
|
|
@ -12,8 +13,6 @@ internal partial class X11Screens |
|
|
|
internal unsafe class X11Screen(MonitorInfo info, X11Info x11, IScalingProvider? scalingProvider, int id) : PlatformScreen(new PlatformHandle(info.Name, "XRandRMonitorName")) |
|
|
|
{ |
|
|
|
public Size? PhysicalSize { get; set; } |
|
|
|
// Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
|
|
|
|
private const int EDIDStructureLength = 32; |
|
|
|
|
|
|
|
public virtual void Refresh() |
|
|
|
{ |
|
|
|
@ -26,56 +25,11 @@ internal partial class X11Screens |
|
|
|
DisplayName = name; |
|
|
|
IsPrimary = info.IsPrimary; |
|
|
|
Bounds = new PixelRect(info.X, info.Y, info.Width, info.Height); |
|
|
|
|
|
|
|
Size? pSize = null; |
|
|
|
for (int o = 0; o < info.Outputs.Length; o++) |
|
|
|
{ |
|
|
|
var outputSize = GetPhysicalMonitorSizeFromEDID(info.Outputs[o]); |
|
|
|
if (outputSize != null) |
|
|
|
{ |
|
|
|
pSize = outputSize; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
PhysicalSize = pSize; |
|
|
|
PhysicalSize = info.PhysicalSize; |
|
|
|
UpdateWorkArea(); |
|
|
|
Scaling = scalingProvider.GetScaling(this, id); |
|
|
|
} |
|
|
|
|
|
|
|
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.EDID) |
|
|
|
hasEDID = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (!hasEDID) |
|
|
|
return null; |
|
|
|
XRRGetOutputProperty(x11.Display, rrOutput, x11.Atoms.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); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected unsafe void UpdateWorkArea() |
|
|
|
{ |
|
|
|
@ -132,6 +86,11 @@ internal partial class X11Screens |
|
|
|
event Action? Changed; |
|
|
|
X11Screen CreateScreenFromKey(nint key); |
|
|
|
} |
|
|
|
|
|
|
|
internal interface IX11RawScreenInfoProviderWithRefreshRate : IX11RawScreenInfoProvider |
|
|
|
{ |
|
|
|
int MaxRefreshRate { get; } |
|
|
|
} |
|
|
|
|
|
|
|
internal unsafe struct MonitorInfo |
|
|
|
{ |
|
|
|
@ -141,10 +100,12 @@ internal partial class X11Screens |
|
|
|
public int Y; |
|
|
|
public int Width; |
|
|
|
public int Height; |
|
|
|
public IntPtr[] Outputs; |
|
|
|
public int RefreshRate; |
|
|
|
public Size? PhysicalSize; |
|
|
|
public int SharedRefreshRate; |
|
|
|
} |
|
|
|
|
|
|
|
private class Randr15ScreensImpl : IX11RawScreenInfoProvider |
|
|
|
private class Randr15ScreensImpl : IX11RawScreenInfoProviderWithRefreshRate |
|
|
|
{ |
|
|
|
private MonitorInfo[]? _cache; |
|
|
|
private readonly X11Info _x11; |
|
|
|
@ -160,17 +121,23 @@ internal partial class X11Screens |
|
|
|
_x11 = platform.Info; |
|
|
|
_window = CreateEventWindow(platform, OnEvent); |
|
|
|
_scalingProvider = GetScalingProvider(platform); |
|
|
|
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify); |
|
|
|
XRRSelectInput(_x11.Display, _window, |
|
|
|
RandrEventMask.RRScreenChangeNotify |
|
|
|
| RandrEventMask.RROutputChangeNotifyMask |
|
|
|
| RandrEventMask.RROutputPropertyNotifyMask |
|
|
|
| RandrEventMask.RRCrtcChangeNotifyMask); |
|
|
|
|
|
|
|
if (_scalingProvider is IScalingProviderWithChanges scalingWithChanges) |
|
|
|
scalingWithChanges.SettingsChanged += () => Changed?.Invoke(); |
|
|
|
} |
|
|
|
|
|
|
|
private void OnEvent(ref XEvent ev) |
|
|
|
{ |
|
|
|
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) |
|
|
|
if (((int)ev.type - _x11.RandrEventBase) is (int)RandrEvent.RRScreenChangeNotify or (int)RandrEvent.RRNotify) |
|
|
|
{ |
|
|
|
_cache = null; |
|
|
|
Changed?.Invoke(); |
|
|
|
// Delay triggering the update event
|
|
|
|
Dispatcher.UIThread.Post(() => Changed?.Invoke(), DispatcherPriority.Normal); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -181,6 +148,7 @@ internal partial class X11Screens |
|
|
|
if (_cache != null) |
|
|
|
return _cache; |
|
|
|
var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count); |
|
|
|
var resources = XRRGetScreenResources(_x11.Display, _window); |
|
|
|
|
|
|
|
var screens = new MonitorInfo[count]; |
|
|
|
for (var c = 0; c < count; c++) |
|
|
|
@ -201,13 +169,150 @@ internal partial class X11Screens |
|
|
|
Y = mon.Y, |
|
|
|
Width = mon.Width, |
|
|
|
Height = mon.Height, |
|
|
|
Outputs = outputs |
|
|
|
PhysicalSize = GetPhysicalMonitorSizeFromFirstEligibleOutput(outputs), |
|
|
|
SharedRefreshRate = GetSharedRefreshRateForOutputs(resources, outputs) |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
XFree(new IntPtr(monitors)); |
|
|
|
XRRFreeScreenResources(resources); |
|
|
|
XRRFreeMonitors(monitors); |
|
|
|
|
|
|
|
return _cache = screens; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private unsafe int GetSharedRefreshRateForOutputs(XRRScreenResources* resources, IntPtr[] outputs) |
|
|
|
{ |
|
|
|
int? minRate = null; |
|
|
|
foreach (var output in outputs) |
|
|
|
{ |
|
|
|
var rate = GetRefreshRateForOutput(resources, output); |
|
|
|
if (rate.HasValue) |
|
|
|
minRate = minRate.HasValue ? Math.Min(minRate.Value, rate.Value) : rate; |
|
|
|
} |
|
|
|
|
|
|
|
return minRate ?? 60; |
|
|
|
} |
|
|
|
|
|
|
|
private unsafe int? GetRefreshRateForOutput(XRRScreenResources* resources, IntPtr output) |
|
|
|
{ |
|
|
|
// Check if output exists in resources
|
|
|
|
var foundOutput = false; |
|
|
|
for (var c = 0; c < resources->noutput; c++) |
|
|
|
{ |
|
|
|
if (resources->outputs[c] == output) |
|
|
|
{ |
|
|
|
foundOutput = true; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!foundOutput) |
|
|
|
return null; |
|
|
|
|
|
|
|
var outputInfo = XRRGetOutputInfo(_x11.Display, resources, output); |
|
|
|
if (outputInfo == null) |
|
|
|
return null; |
|
|
|
try |
|
|
|
{ |
|
|
|
if (outputInfo->crtc == IntPtr.Zero) |
|
|
|
return null; |
|
|
|
var crtc = XRRGetCrtcInfo(_x11.Display, resources, outputInfo->crtc); |
|
|
|
if (crtc == null) |
|
|
|
return null; |
|
|
|
try |
|
|
|
{ |
|
|
|
if (crtc->mode == IntPtr.Zero) |
|
|
|
return null; |
|
|
|
for (var c = 0; c < resources->nmode; c++) |
|
|
|
{ |
|
|
|
var mode = resources->modes[c]; |
|
|
|
if (mode.id == crtc->mode) |
|
|
|
{ |
|
|
|
var multiplier = 1d; |
|
|
|
if (mode.modeFlags.HasAnyFlag(RRModeFlags.RR_Interlace)) |
|
|
|
multiplier *= 2; |
|
|
|
if (mode.modeFlags.HasAnyFlag(RRModeFlags.RR_DoubleScan)) |
|
|
|
multiplier /= 2; |
|
|
|
if (mode.hTotal == 0 || mode.vTotal == 0 || mode.dotClock == 0) |
|
|
|
return null; |
|
|
|
var hz = mode.dotClock / ((double)mode.hTotal * mode.vTotal) * multiplier; |
|
|
|
return (int)Math.Round(hz, MidpointRounding.ToEven); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
XRRFreeCrtcInfo(crtc); |
|
|
|
} |
|
|
|
} |
|
|
|
finally |
|
|
|
{ |
|
|
|
XRRFreeOutputInfo(outputInfo); |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private Size? GetPhysicalMonitorSizeFromFirstEligibleOutput(IntPtr[] outputs) |
|
|
|
{ |
|
|
|
Size? pSize = null; |
|
|
|
for (int o = 0; o < outputs.Length; o++) |
|
|
|
{ |
|
|
|
var outputSize = GetPhysicalMonitorSizeFromEDID(outputs[o]); |
|
|
|
if (outputSize != null) |
|
|
|
{ |
|
|
|
pSize = outputSize; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return pSize; |
|
|
|
} |
|
|
|
|
|
|
|
private unsafe Size? GetPhysicalMonitorSizeFromEDID(IntPtr rrOutput) |
|
|
|
{ |
|
|
|
if (rrOutput == IntPtr.Zero) |
|
|
|
return null; |
|
|
|
var properties = XRRListOutputPropertiesAsArray(_x11.Display, rrOutput); |
|
|
|
var hasEDID = false; |
|
|
|
for (var pc = 0; pc < properties.Length; pc++) |
|
|
|
{ |
|
|
|
if (properties[pc] == _x11.Atoms.EDID) |
|
|
|
hasEDID = true; |
|
|
|
} |
|
|
|
|
|
|
|
if (!hasEDID) |
|
|
|
return null; |
|
|
|
|
|
|
|
// Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
|
|
|
|
const int EDIDStructureLength = 32; |
|
|
|
XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.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); |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
return screens; |
|
|
|
public int MaxRefreshRate |
|
|
|
{ |
|
|
|
get |
|
|
|
{ |
|
|
|
var monitors = MonitorInfos; |
|
|
|
return monitors.Length == 0 ? 60 : monitors.Max(x => x.SharedRefreshRate); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|