9 changed files with 351 additions and 14 deletions
@ -0,0 +1,254 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Platform; |
||||
|
using static Avalonia.X11.XLib; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
class X11Screens : IScreenImpl |
||||
|
{ |
||||
|
private IX11Screens _impl; |
||||
|
|
||||
|
private X11Screens(IX11Screens impl) |
||||
|
{ |
||||
|
_impl = impl; |
||||
|
} |
||||
|
|
||||
|
static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) |
||||
|
{ |
||||
|
var rect = default(Rect); |
||||
|
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 Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); |
||||
|
|
||||
|
|
||||
|
foreach (var s in screens) |
||||
|
s.WorkingArea = s.Bounds.Intersect(wa); |
||||
|
|
||||
|
XFree(prop); |
||||
|
return screens; |
||||
|
} |
||||
|
|
||||
|
class Randr15ScreensImpl : IX11Screens |
||||
|
{ |
||||
|
private readonly X11ScreensUserSettings _settings; |
||||
|
private X11Screen[] _cache; |
||||
|
private X11Info _x11; |
||||
|
private IntPtr _window; |
||||
|
|
||||
|
public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings) |
||||
|
{ |
||||
|
_settings = settings; |
||||
|
_x11 = platform.Info; |
||||
|
_window = XCreateSimpleWindow(_x11.Display, _x11.DefaultRootWindow, 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); |
||||
|
platform.Windows[_window] = OnEvent; |
||||
|
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify); |
||||
|
} |
||||
|
|
||||
|
private void OnEvent(XEvent ev) |
||||
|
{ |
||||
|
// Invalidate cache on RRScreenChangeNotify
|
||||
|
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) |
||||
|
_cache = null; |
||||
|
} |
||||
|
|
||||
|
public unsafe X11Screen[] Screens |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (_cache != null) |
||||
|
return _cache; |
||||
|
var monitors = XRRGetMonitors(_x11.Display, _window, true, out var count); |
||||
|
|
||||
|
var screens = new X11Screen[count]; |
||||
|
for (var c = 0; c < count; c++) |
||||
|
{ |
||||
|
var mon = monitors[c]; |
||||
|
var namePtr = XGetAtomName(_x11.Display, mon.Name); |
||||
|
var name = Marshal.PtrToStringAnsi(namePtr); |
||||
|
XFree(namePtr); |
||||
|
|
||||
|
var density = 1d; |
||||
|
if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) |
||||
|
{ |
||||
|
if (mon.MWidth == 0) |
||||
|
density = 1; |
||||
|
else |
||||
|
density = X11Screen.GuessPixelDensity(mon.Width, mon.MWidth); |
||||
|
} |
||||
|
|
||||
|
density *= _settings.GlobalScaleFactor; |
||||
|
|
||||
|
var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height); |
||||
|
screens[0] = new X11Screen(bounds, |
||||
|
mon.Primary != 0, |
||||
|
name, |
||||
|
(mon.MWidth == 0 || mon.MHeight == 0) ? (Size?)null : new Size(mon.MWidth, mon.MHeight), |
||||
|
density); |
||||
|
} |
||||
|
|
||||
|
XFree(new IntPtr(monitors)); |
||||
|
_cache = UpdateWorkArea(_x11, screens); |
||||
|
return screens; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class FallbackScreensImpl : IX11Screens |
||||
|
{ |
||||
|
public FallbackScreensImpl(X11Info info, X11ScreensUserSettings settings) |
||||
|
{ |
||||
|
if (XGetGeometry(info.Display, info.RootWindow, out var geo)) |
||||
|
{ |
||||
|
|
||||
|
Screens = UpdateWorkArea(info, |
||||
|
new[] |
||||
|
{ |
||||
|
new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null, |
||||
|
settings.GlobalScaleFactor) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; |
||||
|
} |
||||
|
|
||||
|
public X11Screen[] Screens { get; } |
||||
|
} |
||||
|
|
||||
|
public static void 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)); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
public int ScreenCount => _impl.Screens.Length; |
||||
|
|
||||
|
public Screen[] AllScreens => |
||||
|
_impl.Screens.Select(s => new Screen(s.Bounds, s.WorkingArea, s.Primary)).ToArray(); |
||||
|
} |
||||
|
|
||||
|
interface IX11Screens |
||||
|
{ |
||||
|
X11Screen[] Screens { get; } |
||||
|
} |
||||
|
|
||||
|
class X11ScreensUserSettings |
||||
|
{ |
||||
|
public double GlobalScaleFactor { get; set; } = 1; |
||||
|
public Dictionary<string, double> NamedScaleFactors { get; set; } |
||||
|
|
||||
|
static double? TryParse(string s) |
||||
|
{ |
||||
|
if (s == null) |
||||
|
return null; |
||||
|
if (double.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out var rv)) |
||||
|
return rv; |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static X11ScreensUserSettings DetectEnvironment() |
||||
|
{ |
||||
|
var globalFactor = Environment.GetEnvironmentVariable("AVALONIA_GLOBAL_SCALE_FACTOR"); |
||||
|
var screenFactors = Environment.GetEnvironmentVariable("AVALONIA_SCREEN_SCALE_FACTORS"); |
||||
|
if (globalFactor == null && screenFactors == null) |
||||
|
return null; |
||||
|
|
||||
|
var rv = new X11ScreensUserSettings |
||||
|
{ |
||||
|
GlobalScaleFactor = TryParse(globalFactor) ?? 1 |
||||
|
}; |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
if (!string.IsNullOrWhiteSpace(screenFactors)) |
||||
|
{ |
||||
|
rv.NamedScaleFactors = screenFactors.Split(';').Where(x => !string.IsNullOrWhiteSpace(x)) |
||||
|
.Select(x => x.Split('=')).ToDictionary(x => x[0], |
||||
|
x => double.Parse(x[1], CultureInfo.InvariantCulture)); |
||||
|
} |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
//Ignore
|
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static X11ScreensUserSettings Detect() |
||||
|
{ |
||||
|
return DetectEnvironment() ?? new X11ScreensUserSettings(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class X11Screen |
||||
|
{ |
||||
|
public bool Primary { get; } |
||||
|
public string Name { get; set; } |
||||
|
public Rect Bounds { get; set; } |
||||
|
public Size? PhysicalSize { get; set; } |
||||
|
public double PixelDensity { get; set; } |
||||
|
public Rect WorkingArea { get; set; } |
||||
|
|
||||
|
public X11Screen(Rect bounds, bool primary, |
||||
|
string name, Size? physicalSize, double? pixelDensity) |
||||
|
{ |
||||
|
Primary = primary; |
||||
|
Name = name; |
||||
|
Bounds = bounds; |
||||
|
if (physicalSize == null && pixelDensity == null) |
||||
|
{ |
||||
|
PixelDensity = 1; |
||||
|
} |
||||
|
else if (pixelDensity == null) |
||||
|
{ |
||||
|
PixelDensity = GuessPixelDensity(bounds.Width, physicalSize.Value.Width); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PixelDensity = pixelDensity.Value; |
||||
|
PhysicalSize = physicalSize; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static double GuessPixelDensity(double pixelWidth, double mmWidth) |
||||
|
=> Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96)); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue