Browse Source

refactor x11 screen (#16705)

release/11.2.0-beta1
Emmanuel Hansen 2 years ago
committed by GitHub
parent
commit
49c7d25dea
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 247
      src/Avalonia.X11/Screens/X11Screen.Providers.cs
  2. 4
      src/Avalonia.X11/Screens/X11Screens.Scaling.cs
  3. 96
      src/Avalonia.X11/Screens/X11Screens.cs

247
src/Avalonia.X11/Screens/X11Screen.Providers.cs

@ -1,8 +1,6 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Platform; using Avalonia.Platform;
@ -11,79 +9,57 @@ namespace Avalonia.X11.Screens;
internal partial class X11Screens internal partial class X11Screens
{ {
internal class X11Screen internal unsafe class X11Screen(MonitorInfo info, X11Info x11, IScalingProvider? scalingProvider, int id) : PlatformScreen(new PlatformHandle(info.Name, "XRandRMonitorName"))
{ {
public bool IsPrimary { get; set; }
public string Name { get; set; }
public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; } public Size? PhysicalSize { get; set; }
public PixelRect WorkingArea { get; set; }
public X11Screen(
PixelRect bounds,
bool isPrimary,
string name,
Size? physicalSize)
{
IsPrimary = isPrimary;
Name = name;
Bounds = bounds;
PhysicalSize = physicalSize;
}
}
internal interface IX11RawScreenInfoProvider
{
X11Screen[] Screens { get; }
event Action? Changed;
}
private class Randr15ScreensImpl : IX11RawScreenInfoProvider
{
private X11Screen[]? _cache;
private readonly X11Info _x11;
private readonly IntPtr _window;
// Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4 // Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
private const int EDIDStructureLength = 32; private const int EDIDStructureLength = 32;
public event Action? Changed; public virtual void Refresh()
public Randr15ScreensImpl(AvaloniaX11Platform platform)
{ {
_x11 = platform.Info; if (scalingProvider == null)
_window = CreateEventWindow(platform, OnEvent); return;
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
}
private void OnEvent(ref XEvent ev) var namePtr = XGetAtomName(x11.Display, info.Name);
{ var name = Marshal.PtrToStringAnsi(namePtr);
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) XFree(namePtr);
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++)
{ {
_cache = null; var outputSize = GetPhysicalMonitorSizeFromEDID(info.Outputs[o]);
Changed?.Invoke(); if (outputSize != null)
{
pSize = outputSize;
break;
}
} }
PhysicalSize = pSize;
UpdateWorkArea();
Scaling = scalingProvider.GetScaling(this, id);
} }
private unsafe Size? GetPhysicalMonitorSizeFromEDID(IntPtr rrOutput) private unsafe Size? GetPhysicalMonitorSizeFromEDID(IntPtr rrOutput)
{ {
if (rrOutput == IntPtr.Zero) if (rrOutput == IntPtr.Zero || x11 == null)
return null; return null;
var properties = XRRListOutputProperties(_x11.Display, rrOutput, out int propertyCount); var properties = XRRListOutputProperties(x11.Display, rrOutput, out int propertyCount);
var hasEDID = false; var hasEDID = false;
for (var pc = 0; pc < propertyCount; pc++) for (var pc = 0; pc < propertyCount; pc++)
{ {
if (properties[pc] == _x11.Atoms.EDID) if (properties[pc] == x11.Atoms.EDID)
hasEDID = true; hasEDID = true;
} }
if (!hasEDID) if (!hasEDID)
return null; return null;
XRRGetOutputProperty(_x11.Display, rrOutput, _x11.Atoms.EDID, 0, EDIDStructureLength, false, false, XRRGetOutputProperty(x11.Display, rrOutput, x11.Atoms.EDID, 0, EDIDStructureLength, false, false,
_x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _, x11.Atoms.AnyPropertyType, out IntPtr actualType, out int actualFormat, out int bytesAfter, out _,
out IntPtr prop); out IntPtr prop);
if (actualType != _x11.Atoms.XA_INTEGER) if (actualType != x11.Atoms.XA_INTEGER)
return null; return null;
if (actualFormat != 8) // Expecting an byte array if (actualFormat != 8) // Expecting an byte array
return null; return null;
@ -101,7 +77,104 @@ internal partial class X11Screens
return new Size(width * 10, height * 10); return new Size(width * 10, height * 10);
} }
public unsafe X11Screen[] Screens protected unsafe void UpdateWorkArea()
{
var rect = default(PixelRect);
//Fallback value
rect = rect.Union(Bounds);
WorkingArea = Bounds;
var res = XGetWindowProperty(x11.Display,
x11.RootWindow,
x11.Atoms._NET_WORKAREA,
IntPtr.Zero,
new IntPtr(128),
false,
x11.Atoms.AnyPropertyType,
out var type,
out var format,
out var count,
out var bytesAfter,
out var prop);
if (res != (int)Status.Success || type == IntPtr.Zero ||
format == 0 || bytesAfter.ToInt64() != 0 || count.ToInt64() % 4 != 0)
return;
var pwa = (IntPtr*)prop;
var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
WorkingArea = Bounds.Intersect(wa);
if (WorkingArea.Width <= 0 || WorkingArea.Height <= 0)
WorkingArea = Bounds;
XFree(prop);
}
}
internal class FallBackScreen : X11Screen
{
public FallBackScreen(PixelRect pixelRect, X11Info x11) : base(default, x11, null, 0)
{
Bounds = pixelRect;
DisplayName = "Default";
IsPrimary = true;
PhysicalSize = pixelRect.Size.ToSize(Scaling);
UpdateWorkArea();
}
public override void Refresh()
{
}
}
internal interface IX11RawScreenInfoProvider
{
nint[] ScreenKeys { get; }
event Action? Changed;
X11Screen? CreateScreenFromKey(nint key);
}
internal unsafe struct MonitorInfo
{
public IntPtr Name;
public bool IsPrimary;
public int X;
public int Y;
public int Width;
public int Height;
public IntPtr[] Outputs;
}
private class Randr15ScreensImpl : IX11RawScreenInfoProvider
{
private MonitorInfo[]? _cache;
private readonly X11Info _x11;
private readonly IntPtr _window;
private readonly IScalingProvider _scalingProvider;
public nint[] ScreenKeys => MonitorInfos.Select(x => x.Name).ToArray();
public event Action? Changed;
public Randr15ScreensImpl(AvaloniaX11Platform platform)
{
_x11 = platform.Info;
_window = CreateEventWindow(platform, OnEvent);
_scalingProvider = GetScalingProvider(platform);
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
if (_scalingProvider is IScalingProviderWithChanges scalingWithChanges)
scalingWithChanges.SettingsChanged += () => Changed?.Invoke();
}
private void OnEvent(ref XEvent ev)
{
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify)
{
_cache = null;
Changed?.Invoke();
}
}
private unsafe MonitorInfo[] MonitorInfos
{ {
get get
{ {
@ -109,39 +182,56 @@ internal partial class X11Screens
return _cache; return _cache;
var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count); var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count);
var screens = new X11Screen[count]; var screens = new MonitorInfo[count];
for (var c = 0; c < count; c++) for (var c = 0; c < count; c++)
{ {
var mon = monitors[c]; var mon = monitors[c];
var namePtr = XGetAtomName(_x11.Display, mon.Name); var outputs = new nint[mon.NOutput];
var name = Marshal.PtrToStringAnsi(namePtr);
XFree(namePtr);
var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
Size? pSize = null;
for (int o = 0; o < mon.NOutput; o++) for (int i = 0; i < outputs.Length; i++)
{ {
var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]); outputs[i] = mon.Outputs[i];
if (outputSize != null)
{
pSize = outputSize;
break;
}
} }
screens[c] = new X11Screen(bounds, mon.Primary != 0, name ?? string.Empty, pSize); screens[c] = new MonitorInfo()
{
Name = mon.Name,
IsPrimary = mon.Primary != 0,
X = mon.X,
Y = mon.Y,
Width = mon.Width,
Height = mon.Height,
Outputs = outputs
};
} }
XFree(new IntPtr(monitors)); XFree(new IntPtr(monitors));
_cache = UpdateWorkArea(_x11, screens);
return screens; return screens;
} }
} }
public X11Screen? CreateScreenFromKey(nint key)
{
var info = MonitorInfos.Where(x => x.Name == key).FirstOrDefault();
var infos = MonitorInfos;
for (var i = 0; i < infos.Length; i++)
{
if (infos[i].Name == key)
{
return new X11Screen(info, _x11, _scalingProvider, i);
}
}
return null;
}
} }
private class FallbackScreensImpl : IX11RawScreenInfoProvider private class FallbackScreensImpl : IX11RawScreenInfoProvider
{ {
private readonly X11Info _info; private readonly X11Info _info;
private XGeometry _geo;
public event Action? Changed public event Action? Changed
{ {
@ -156,22 +246,13 @@ internal partial class X11Screens
platform.Globals.RootGeometryChangedChanged += () => UpdateRootWindowGeometry(); platform.Globals.RootGeometryChangedChanged += () => UpdateRootWindowGeometry();
} }
bool UpdateRootWindowGeometry() private bool UpdateRootWindowGeometry() => XGetGeometry(_info.Display, _info.RootWindow, out _geo);
{
var res = XGetGeometry(_info.Display, _info.RootWindow, out var geo);
if(res)
{
Screens = UpdateWorkArea(_info,
new[]
{
new X11Screen(new PixelRect(0, 0, geo.width, geo.height), true, "Default", null)
});
}
return res; public X11Screen? CreateScreenFromKey(nint key)
{
return new FallBackScreen(new PixelRect(0, 0, _geo.width, _geo.height), _info);
} }
public X11Screen[] Screens { get; private set; } = new[] public nint[] ScreenKeys => new[] { IntPtr.Zero };
{ new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null) };
} }
} }

4
src/Avalonia.X11/Screens/X11Screens.Scaling.cs

@ -8,7 +8,7 @@ namespace Avalonia.X11.Screens;
internal partial class X11Screens internal partial class X11Screens
{ {
interface IScalingProvider internal interface IScalingProvider
{ {
double GetScaling(X11Screen screen, int index); double GetScaling(X11Screen screen, int index);
} }
@ -137,7 +137,7 @@ internal partial class X11Screens
return _indexedConfig[index]; return _indexedConfig[index];
return 1; return 1;
} }
if (_namedConfig?.TryGetValue(screen.Name, out var scaling) == true) if (_namedConfig?.TryGetValue(screen.DisplayName!, out var scaling) == true)
return scaling; return scaling;
return 1; return 1;

96
src/Avalonia.X11/Screens/X11Screens.cs

@ -5,14 +5,14 @@ using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using static Avalonia.X11.Screens.X11Screens;
using static Avalonia.X11.XLib; using static Avalonia.X11.XLib;
namespace Avalonia.X11.Screens namespace Avalonia.X11.Screens
{ {
internal partial class X11Screens : IScreenImpl internal partial class X11Screens : ScreensBase<nint, X11Screen>
{ {
private IX11RawScreenInfoProvider _impl; private readonly IX11RawScreenInfoProvider _impl;
private IScalingProvider _scaling;
public X11Screens(AvaloniaX11Platform platform) public X11Screens(AvaloniaX11Platform platform)
{ {
@ -21,96 +21,14 @@ namespace Avalonia.X11.Screens
? new Randr15ScreensImpl(platform) ? new Randr15ScreensImpl(platform)
: (IX11RawScreenInfoProvider)new FallbackScreensImpl(platform); : (IX11RawScreenInfoProvider)new FallbackScreensImpl(platform);
_impl.Changed += () => Changed?.Invoke(); _impl.Changed += () => Changed?.Invoke();
_scaling = GetScalingProvider(platform);
if (_scaling is IScalingProviderWithChanges scalingWithChanges)
scalingWithChanges.SettingsChanged += () => Changed?.Invoke();
} }
private static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) protected override int GetScreenCount() => _impl.ScreenKeys.Length;
{
var rect = default(PixelRect);
foreach (var s in screens)
{
rect = rect.Union(s.Bounds);
//Fallback value
s.WorkingArea = s.Bounds;
}
var res = XGetWindowProperty(info.Display,
info.RootWindow,
info.Atoms._NET_WORKAREA,
IntPtr.Zero,
new IntPtr(128),
false,
info.Atoms.AnyPropertyType,
out var type,
out var format,
out var count,
out var bytesAfter,
out var prop);
if (res != (int)Status.Success || type == IntPtr.Zero ||
format == 0 || bytesAfter.ToInt64() != 0 || count.ToInt64() % 4 != 0)
return screens;
var pwa = (IntPtr*)prop;
var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
foreach (var s in screens)
{
s.WorkingArea = s.Bounds.Intersect(wa);
if (s.WorkingArea.Width <= 0 || s.WorkingArea.Height <= 0)
s.WorkingArea = s.Bounds;
}
XFree(prop);
return screens;
}
public Screen ScreenFromTopLevel(ITopLevelImpl topLevel)
{
if (topLevel is IWindowImpl window)
{
return ScreenFromWindow(window);
}
return null;
}
public Screen ScreenFromPoint(PixelPoint point) protected override IReadOnlyList<nint> GetAllScreenKeys() => _impl.ScreenKeys;
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Task<bool> RequestScreenDetails() => Task.FromResult(true);
public Screen ScreenFromWindow(IWindowBaseImpl window) protected override X11Screen CreateScreenFromKey(nint key) => _impl.CreateScreenFromKey(key);
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens
{
get
{
var rawScreens = _impl.Screens;
if (!rawScreens.Any(s => s.IsPrimary) && rawScreens.Length > 0)
rawScreens[0].IsPrimary = true;
return rawScreens.Select((s, i) =>
new Screen(_scaling.GetScaling(s, i), s.Bounds, s.WorkingArea, s.IsPrimary))
.ToArray();
}
}
public Action Changed { get; set; } protected override void ScreenChanged(X11Screen screen) => screen.Refresh();
} }
} }

Loading…
Cancel
Save