Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[X11] Use Xft.dpi and QT_*** environment variable to get screen scaling #12880

Merged
merged 1 commit into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions src/Avalonia.X11/Screens/X11Screen.Providers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@

#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using static Avalonia.X11.XLib;
namespace Avalonia.X11.Screens;

internal partial class X11Screens
{
internal class X11Screen
{
public bool IsPrimary { get; }
public string Name { get; set; }
public PixelRect Bounds { 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
private const int EDIDStructureLength = 32;

public event Action Changed;

public Randr15ScreensImpl(AvaloniaX11Platform platform)
{
_x11 = platform.Info;
_window = CreateEventWindow(platform, OnEvent);
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify);
}

private void OnEvent(ref XEvent ev)
{
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify)
{
_cache = null;
Changed?.Invoke();
}
}

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

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 bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
Size? pSize = null;

for (int o = 0; o < mon.NOutput; o++)
{
var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]);
if (outputSize != null)
{
pSize = outputSize;
break;
}
}

screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize);
}

XFree(new IntPtr(monitors));
_cache = UpdateWorkArea(_x11, screens);
return screens;
}
}
}

private class FallbackScreensImpl : IX11RawScreenInfoProvider
{
private readonly X11Info _info;
public event Action? Changed;

public FallbackScreensImpl(AvaloniaX11Platform platform)
{
_info = platform.Info;
if (UpdateRootWindowGeometry())
platform.Globals.RootGeometryChangedChanged += () => UpdateRootWindowGeometry();
}

bool UpdateRootWindowGeometry()
{
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[] Screens { get; private set; } = new[]
{ new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null) };
}
}
Loading