Browse Source

[X11] Get screen info (including scale factor) via RANDR 1.5

pull/2011/head
Nikita Tsukanov 8 years ago
parent
commit
08b50a6830
  1. 2
      samples/ControlCatalog.NetCore/Program.cs
  2. 2
      src/Avalonia.X11/Avalonia.X11.csproj
  3. 8
      src/Avalonia.X11/Stubs.cs
  4. 23
      src/Avalonia.X11/X11Info.cs
  5. 1
      src/Avalonia.X11/X11Platform.cs
  6. 254
      src/Avalonia.X11/X11Screens.cs
  7. 52
      src/Avalonia.X11/X11Structs.cs
  8. 4
      src/Avalonia.X11/X11Window.cs
  9. 19
      src/Avalonia.X11/XLib.cs

2
samples/ControlCatalog.NetCore/Program.cs

@ -43,7 +43,7 @@ namespace ControlCatalog.NetCore
/// This method is needed for IDE previewer infrastructure
/// </summary>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>().UsePlatformDetect().UseSkia().UseReactiveUI();
=> AppBuilder.Configure<App>().UsePlatformDetect().UseSkia().UseReactiveUI().UseX11();
static void ConsoleSilencer()
{

2
src/Avalonia.X11/Avalonia.X11.csproj

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>

8
src/Avalonia.X11/Stubs.cs

@ -63,12 +63,4 @@ namespace Avalonia.X11
return new FakeIcon(ms.ToArray());
}
}
class ScreenStub : IScreenImpl
{
public int ScreenCount { get; } = 1;
public Screen[] AllScreens { get; } =
{new Screen(new Rect(0, 0, 1920, 1280), new Rect(0, 0, 1920, 1280), true)};
}
}

23
src/Avalonia.X11/X11Info.cs

@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Avalonia.X11
@ -17,6 +18,12 @@ namespace Avalonia.X11
public IntPtr DefaultCursor { get; }
public X11Atoms Atoms { get; }
public IntPtr Xim { get; }
public int RandrEventBase { get; }
public int RandrErrorBase { get; }
public Version RandrVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
@ -33,6 +40,22 @@ namespace Avalonia.X11
//TODO: Open an actual XIM once we get support for preedit in our textbox
XSetLocaleModifiers("@im=none");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
try
{
if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0)
{
RandrEventBase = randrEventBase;
RandrErrorBase = randrErrorBase;
if (XRRQueryVersion(display, out var major, out var minor) != 0)
RandrVersion = new Version(major, minor);
}
}
catch
{
//Ignore, randr is not supported
}
}
}
}

1
src/Avalonia.X11/X11Platform.cs

@ -41,6 +41,7 @@ namespace Avalonia.X11
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogsStub())
.Bind<IPlatformIconLoader>().ToConstant(new IconLoaderStub());
X11Screens.Init(this);
EglGlPlatformFeature.TryInitialize();
}

254
src/Avalonia.X11/X11Screens.cs

@ -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));
}
}

52
src/Avalonia.X11/X11Structs.cs

@ -849,7 +849,43 @@ namespace Avalonia.X11 {
OwnerGrabButtonMask = 1<<24
}
internal enum GrabMode {
[Flags]
internal enum RandrEventMask
{
RRScreenChangeNotify = 1 << 0,
/* V1.2 additions */
RRCrtcChangeNotifyMask = 1 << 1,
RROutputChangeNotifyMask = 1 << 2,
RROutputPropertyNotifyMask = 1 << 3,
/* V1.4 additions */
RRProviderChangeNotifyMask = 1 << 4,
RRProviderPropertyNotifyMask = 1 << 5,
RRResourceChangeNotifyMask = 1 << 6,
/* V1.6 additions */
RRLeaseNotifyMask = 1 << 7
}
internal enum RandrEvent
{
RRScreenChangeNotify = 0,
/* V1.2 additions */
RRNotify = 1
}
internal enum RandrRotate
{
/* used in the rotation field; rotation and reflection in 0.1 proto. */
RR_Rotate_0 = 1,
RR_Rotate_90 = 2,
RR_Rotate_180 = 4,
RR_Rotate_270 = 8
}
internal enum GrabMode {
GrabModeSync = 0,
GrabModeAsync = 1
}
@ -1812,4 +1848,18 @@ namespace Avalonia.X11 {
public const string XNSpotLocation = "spotLocation";
public const string XNFontSet = "fontSet";
}
struct XRRMonitorInfo {
public IntPtr Name;
public int Primary;
public int Automatic;
public int NOutput;
public int X;
public int Y;
public int Width;
public int Height;
public int MWidth;
public int MHeight;
public IntPtr Outputs;
}
}

4
src/Avalonia.X11/X11Window.cs

@ -636,8 +636,8 @@ namespace Avalonia.X11
}
}
public IScreenImpl Screen { get; } = new ScreenStub();
public IScreenImpl Screen { get; } = AvaloniaLocator.CurrentMutable.GetService<IScreenImpl>();
public Size MaxClientSize { get; } = new Size(1920, 1280);

19
src/Avalonia.X11/XLib.cs

@ -14,7 +14,8 @@ namespace Avalonia.X11
{
internal unsafe static class XLib
{
const string libX11 = "X11";
const string libX11 = "libX11.so.6";
const string libX11Randr = "libXrandr.so.2";
[DllImport(libX11)]
public static extern IntPtr XOpenDisplay(IntPtr display);
@ -451,6 +452,22 @@ namespace Avalonia.X11
[DllImport (libX11)]
public static extern void XDestroyIC (IntPtr xic);
[DllImport(libX11Randr)]
public static extern int XRRQueryExtension (IntPtr dpy,
out int event_base_return,
out int error_base_return);
[DllImport(libX11Randr)]
public static extern int XRRQueryVersion(IntPtr dpy,
out int major_version_return,
out int minor_version_return);
[DllImport(libX11Randr)]
public static extern XRRMonitorInfo*
XRRGetMonitors(IntPtr dpy, IntPtr window, bool get_active, out int nmonitors);
[DllImport(libX11Randr)]
public static extern void XRRSelectInput(IntPtr dpy, IntPtr window, RandrEventMask mask);
public struct XGeometry
{
public IntPtr root;

Loading…
Cancel
Save