committed by
GitHub
55 changed files with 1678 additions and 526 deletions
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Media.Fonts; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
internal class CompositeFontFamilyKey : FontFamilyKey |
|||
{ |
|||
public CompositeFontFamilyKey(Uri source, FontFamilyKey[] keys) : base(source, null) |
|||
{ |
|||
Keys = keys; |
|||
} |
|||
|
|||
public IReadOnlyList<FontFamilyKey> Keys { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
internal readonly record struct FontSourceIdentifier |
|||
{ |
|||
public FontSourceIdentifier(string name, Uri? source) |
|||
{ |
|||
Name = name; |
|||
Source = source; |
|||
} |
|||
|
|||
public string Name { get; init; } |
|||
|
|||
public Uri? Source { get; init; } |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.IO; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
internal interface IGlyphTypeface2 : IGlyphTypeface |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// Returns the font file stream represented by the <see cref="IGlyphTypeface"/> object.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream.</param>
|
|||
/// <returns>Returns <c>true</c> if the stream can be obtained, otherwise <c>false</c>.</returns>
|
|||
bool TryGetStream([NotNullWhen(true)] out Stream? stream); |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Threading; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Rendering; |
|||
|
|||
[PrivateApi] |
|||
public sealed class ThreadProxyRenderTimer : IRenderTimer |
|||
{ |
|||
private readonly IRenderTimer _inner; |
|||
private readonly Stopwatch _stopwatch; |
|||
private readonly Thread _timerThread; |
|||
private readonly AutoResetEvent _autoResetEvent; |
|||
private Action<TimeSpan>? _tick; |
|||
private int _subscriberCount; |
|||
private bool _registered; |
|||
|
|||
public ThreadProxyRenderTimer(IRenderTimer inner, int maxStackSize = 1 * 1024 * 1024) |
|||
{ |
|||
_inner = inner; |
|||
_stopwatch = new Stopwatch(); |
|||
_autoResetEvent = new AutoResetEvent(false); |
|||
_timerThread = new Thread(RenderTimerThreadFunc, maxStackSize) { Name = "RenderTimerLoop", IsBackground = true }; |
|||
} |
|||
|
|||
public event Action<TimeSpan> Tick |
|||
{ |
|||
add |
|||
{ |
|||
_tick += value; |
|||
|
|||
if (!_registered) |
|||
{ |
|||
_registered = true; |
|||
_timerThread.Start(); |
|||
} |
|||
|
|||
if (_subscriberCount++ == 0) |
|||
{ |
|||
_inner.Tick += InnerTick; |
|||
} |
|||
} |
|||
|
|||
remove |
|||
{ |
|||
if (--_subscriberCount == 0) |
|||
{ |
|||
_inner.Tick -= InnerTick; |
|||
} |
|||
|
|||
_tick -= value; |
|||
} |
|||
} |
|||
|
|||
private void RenderTimerThreadFunc() |
|||
{ |
|||
while (_autoResetEvent.WaitOne()) |
|||
{ |
|||
_tick?.Invoke(_stopwatch.Elapsed); |
|||
} |
|||
} |
|||
|
|||
private void InnerTick(TimeSpan obj) |
|||
{ |
|||
_autoResetEvent.Set(); |
|||
} |
|||
|
|||
public bool RunsInBackground => true; |
|||
} |
|||
@ -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) }; |
|||
} |
|||
} |
|||
@ -0,0 +1,249 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.X11.Screens; |
|||
|
|||
internal partial class X11Screens |
|||
{ |
|||
interface IScalingProvider |
|||
{ |
|||
double GetScaling(X11Screen screen, int index); |
|||
} |
|||
|
|||
interface IScalingProviderWithChanges : IScalingProvider |
|||
{ |
|||
event Action SettingsChanged; |
|||
} |
|||
|
|||
class PostMultiplyScalingProvider : IScalingProvider |
|||
{ |
|||
private readonly IScalingProvider _inner; |
|||
private readonly double _factor; |
|||
|
|||
public PostMultiplyScalingProvider(IScalingProvider inner, double factor) |
|||
{ |
|||
_inner = inner; |
|||
_factor = factor; |
|||
} |
|||
|
|||
public double GetScaling(X11Screen screen, int index) => _inner.GetScaling(screen, index) * _factor; |
|||
} |
|||
|
|||
class NullScalingProvider : IScalingProvider |
|||
{ |
|||
public double GetScaling(X11Screen screen, int index) => 1; |
|||
} |
|||
|
|||
|
|||
class XrdbScalingProvider : IScalingProviderWithChanges |
|||
{ |
|||
private readonly XResources _resources; |
|||
private double _factor = 1; |
|||
|
|||
public XrdbScalingProvider(AvaloniaX11Platform platform) |
|||
{ |
|||
_resources = platform.Resources; |
|||
_resources.ResourceChanged += name => |
|||
{ |
|||
if (name == "Xft.dpi") |
|||
Update(); |
|||
}; |
|||
Update(); |
|||
} |
|||
|
|||
void Update() |
|||
{ |
|||
var factor = 1d; |
|||
var stringValue = _resources.GetResource("Xft.dpi")?.Trim(); |
|||
if (!string.IsNullOrWhiteSpace(stringValue) && double.TryParse(stringValue, NumberStyles.Any, |
|||
CultureInfo.InvariantCulture, out var parsed)) |
|||
{ |
|||
factor = parsed / 96; |
|||
} |
|||
|
|||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|||
if (_factor != factor) |
|||
{ |
|||
_factor = factor; |
|||
SettingsChanged?.Invoke(); |
|||
} |
|||
} |
|||
|
|||
public event Action? SettingsChanged; |
|||
|
|||
public double GetScaling(X11Screen screen, int index) => _factor; |
|||
} |
|||
|
|||
class PhysicalDpiScalingProvider : IScalingProvider |
|||
{ |
|||
private const int FullHDWidth = 1920; |
|||
private const int FullHDHeight = 1080; |
|||
|
|||
public double GetScaling(X11Screen screen, int index) |
|||
{ |
|||
if (screen.PhysicalSize == null) |
|||
return 1; |
|||
return GuessPixelDensity(screen.Bounds, screen.PhysicalSize.Value); |
|||
} |
|||
|
|||
double GuessPixelDensity(PixelRect pixel, Size physical) |
|||
{ |
|||
var calculatedDensity = 1d; |
|||
if (physical.Width > 0) |
|||
calculatedDensity = pixel.Width <= FullHDWidth |
|||
? 1 |
|||
: Math.Max(1, pixel.Width / physical.Width * 25.4 / 96); |
|||
else if (physical.Height > 0) |
|||
calculatedDensity = pixel.Height <= FullHDHeight |
|||
? 1 |
|||
: Math.Max(1, pixel.Height / physical.Height * 25.4 / 96); |
|||
|
|||
if (calculatedDensity > 3) |
|||
return 1; |
|||
else |
|||
{ |
|||
var sanePixelDensities = new double[] { 1, 1.25, 1.50, 1.75, 2 }; |
|||
foreach (var saneDensity in sanePixelDensities) |
|||
{ |
|||
if (calculatedDensity <= saneDensity + 0.20) |
|||
return saneDensity; |
|||
} |
|||
|
|||
return sanePixelDensities.Last(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
class UserConfiguredScalingProvider : IScalingProvider |
|||
{ |
|||
private readonly Dictionary<string, double>? _namedConfig; |
|||
private readonly List<double>? _indexedConfig; |
|||
|
|||
|
|||
public UserConfiguredScalingProvider(Dictionary<string, double>? namedConfig, List<double>? indexedConfig) |
|||
{ |
|||
_namedConfig = namedConfig; |
|||
_indexedConfig = indexedConfig; |
|||
} |
|||
|
|||
public double GetScaling(X11Screen screen, int index) |
|||
{ |
|||
if (_indexedConfig != null) |
|||
{ |
|||
if (index > 0 && index < _indexedConfig.Count) |
|||
return _indexedConfig[index]; |
|||
return 1; |
|||
} |
|||
if (_namedConfig?.TryGetValue(screen.Name, out var scaling) == true) |
|||
return scaling; |
|||
|
|||
return 1; |
|||
} |
|||
} |
|||
|
|||
class UserScalingConfiguration |
|||
{ |
|||
public Dictionary<string, double>? NamedConfig { get; set; } |
|||
public List<double>? IndexedConfig { get; set; } |
|||
} |
|||
|
|||
static (UserScalingConfiguration? config, double global, bool forceAuto)? TryGetEnvConfiguration( |
|||
string globalFactorName, string userConfigName, string[] autoNames) |
|||
{ |
|||
var globalFactorString = Environment.GetEnvironmentVariable(globalFactorName); |
|||
var screenFactorsString = Environment.GetEnvironmentVariable(userConfigName); |
|||
bool usePhysicalDpi = false; |
|||
foreach (var autoName in autoNames) |
|||
{ |
|||
var envValue = Environment.GetEnvironmentVariable(autoName); |
|||
if (envValue == "1") |
|||
usePhysicalDpi = true; |
|||
} |
|||
|
|||
double? globalFactor = null; |
|||
if (!string.IsNullOrWhiteSpace(globalFactorString) |
|||
&& double.TryParse(globalFactorString, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsed)) |
|||
globalFactor = parsed; |
|||
|
|||
UserScalingConfiguration? userConfig = null; |
|||
if (!string.IsNullOrWhiteSpace(screenFactorsString)) |
|||
{ |
|||
try |
|||
{ |
|||
var split = screenFactorsString.Split(';').Where(x => !string.IsNullOrWhiteSpace(x)).ToArray(); |
|||
if (split[0].Contains("=")) |
|||
{ |
|||
userConfig = new UserScalingConfiguration |
|||
{ |
|||
NamedConfig = split.Select(x => x.Split(new[] { '=' }, 2)) |
|||
.ToDictionary(x => x[0], x => double.Parse(x[1], CultureInfo.InvariantCulture)) |
|||
}; |
|||
} |
|||
else |
|||
{ |
|||
userConfig = new UserScalingConfiguration |
|||
{ |
|||
IndexedConfig = split.Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToList() |
|||
}; |
|||
} |
|||
} |
|||
catch |
|||
{ |
|||
Console.Error.WriteLine($"Unable to parse {userConfigName}={screenFactorsString}"); |
|||
} |
|||
} |
|||
|
|||
|
|||
if (globalFactorString == null && screenFactorsString == null && usePhysicalDpi == null) |
|||
return null; |
|||
|
|||
return (userConfig, globalFactor ?? 1, usePhysicalDpi); |
|||
} |
|||
|
|||
|
|||
static IScalingProvider GetScalingProvider(AvaloniaX11Platform platform) |
|||
{ |
|||
var envSets = new[] |
|||
{ |
|||
("AVALONIA_GLOBAL_SCALE_FACTOR", "AVALONIA_SCREEN_SCALE_FACTORS", new[] { "AVALONIA_USE_PHYSICAL_DPI" }) |
|||
}.ToList(); |
|||
|
|||
if (Environment.GetEnvironmentVariable("AVALONIA_SCREEN_SCALE_IGNORE_QT") != "1") |
|||
{ |
|||
envSets.Add(("QT_SCALE_FACTOR", "QT_SCREEN_SCALE_FACTORS", |
|||
new[] { "QT_AUTO_SCREEN_SCALE_FACTOR", "QT_USE_PHYSICAL_DPI" })); |
|||
} |
|||
|
|||
UserScalingConfiguration? config = null; |
|||
double global = 1; |
|||
bool forceAuto = false; |
|||
|
|||
|
|||
foreach (var envSet in envSets) |
|||
{ |
|||
var envConfig = TryGetEnvConfiguration(envSet.Item1, envSet.Item2, envSet.Item3); |
|||
if (envConfig != null) |
|||
{ |
|||
(config, global, forceAuto) = envConfig.Value; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
IScalingProvider provider; |
|||
if (config != null) |
|||
provider = new UserConfiguredScalingProvider(config.NamedConfig, config.IndexedConfig); |
|||
else if (forceAuto) |
|||
provider = new PhysicalDpiScalingProvider(); |
|||
else |
|||
provider = new XrdbScalingProvider(platform); |
|||
|
|||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|||
if (global != 1) |
|||
provider = new PostMultiplyScalingProvider(provider, global); |
|||
|
|||
return provider; |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
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 : IScreenImpl |
|||
{ |
|||
private IX11RawScreenInfoProvider _impl; |
|||
private IScalingProvider _scaling; |
|||
internal event Action Changed; |
|||
|
|||
public X11Screens(AvaloniaX11Platform platform) |
|||
{ |
|||
var info = platform.Info; |
|||
_impl = (info.RandrVersion != null && info.RandrVersion >= new Version(1, 5)) |
|||
? new Randr15ScreensImpl(platform) |
|||
: (IX11RawScreenInfoProvider)new FallbackScreensImpl(platform); |
|||
_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) |
|||
{ |
|||
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 ScreenFromPoint(PixelPoint point) |
|||
{ |
|||
return ScreenHelper.ScreenFromPoint(point, AllScreens); |
|||
} |
|||
|
|||
public Screen ScreenFromRect(PixelRect rect) |
|||
{ |
|||
return ScreenHelper.ScreenFromRect(rect, AllScreens); |
|||
} |
|||
|
|||
public Screen ScreenFromWindow(IWindowBaseImpl window) |
|||
{ |
|||
return ScreenHelper.ScreenFromWindow(window, AllScreens); |
|||
} |
|||
|
|||
public int ScreenCount => _impl.Screens.Length; |
|||
|
|||
public IReadOnlyList<Screen> AllScreens => |
|||
_impl.Screens.Select((s, i) => new Screen(_scaling.GetScaling(s, i), s.Bounds, s.WorkingArea, s.IsPrimary)) |
|||
.ToArray(); |
|||
} |
|||
} |
|||
@ -1,337 +0,0 @@ |
|||
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 |
|||
{ |
|||
internal class X11Screens : IScreenImpl |
|||
{ |
|||
private IX11Screens _impl; |
|||
|
|||
public X11Screens(IX11Screens impl) |
|||
{ |
|||
_impl = impl; |
|||
} |
|||
|
|||
private static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
private class Randr15ScreensImpl : IX11Screens |
|||
{ |
|||
private readonly X11ScreensUserSettings _settings; |
|||
private X11Screen[] _cache; |
|||
private X11Info _x11; |
|||
private IntPtr _window; |
|||
private const int EDIDStructureLength = 32; // Length of a EDID-Block-Length(128 bytes), XRRGetOutputProperty multiplies offset and length by 4
|
|||
|
|||
public Randr15ScreensImpl(AvaloniaX11Platform platform, X11ScreensUserSettings settings) |
|||
{ |
|||
_settings = settings; |
|||
_x11 = platform.Info; |
|||
_window = CreateEventWindow(platform, OnEvent); |
|||
XRRSelectInput(_x11.Display, _window, RandrEventMask.RRScreenChangeNotify); |
|||
} |
|||
|
|||
private void OnEvent(ref XEvent ev) |
|||
{ |
|||
// Invalidate cache on RRScreenChangeNotify
|
|||
if ((int)ev.type == _x11.RandrEventBase + (int)RandrEvent.RRScreenChangeNotify) |
|||
_cache = null; |
|||
} |
|||
|
|||
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; |
|||
double density = 0; |
|||
if (_settings.NamedScaleFactors?.TryGetValue(name, out density) != true) |
|||
{ |
|||
for(int o = 0; o < mon.NOutput; o++) |
|||
{ |
|||
var outputSize = GetPhysicalMonitorSizeFromEDID(mon.Outputs[o]); |
|||
var outputDensity = 1d; |
|||
if(outputSize != null) |
|||
outputDensity = X11Screen.GuessPixelDensity(bounds, outputSize.Value); |
|||
if(density == 0 || density > outputDensity) |
|||
{ |
|||
density = outputDensity; |
|||
pSize = outputSize; |
|||
} |
|||
} |
|||
} |
|||
if(density == 0) |
|||
density = 1; |
|||
density *= _settings.GlobalScaleFactor; |
|||
screens[c] = new X11Screen(bounds, mon.Primary != 0, name, pSize, density); |
|||
} |
|||
|
|||
XFree(new IntPtr(monitors)); |
|||
_cache = UpdateWorkArea(_x11, screens); |
|||
return screens; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private 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 PixelRect(0, 0, geo.width, geo.height), true, "Default", null, |
|||
settings.GlobalScaleFactor) |
|||
}); |
|||
} |
|||
else |
|||
{ |
|||
Screens = new[] |
|||
{ |
|||
new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, |
|||
settings.GlobalScaleFactor) |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public X11Screen[] Screens { get; } |
|||
} |
|||
|
|||
public static IX11Screens 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); |
|||
|
|||
return impl; |
|||
|
|||
} |
|||
|
|||
public Screen ScreenFromPoint(PixelPoint point) |
|||
{ |
|||
return ScreenHelper.ScreenFromPoint(point, AllScreens); |
|||
} |
|||
|
|||
public Screen ScreenFromRect(PixelRect rect) |
|||
{ |
|||
return ScreenHelper.ScreenFromRect(rect, AllScreens); |
|||
} |
|||
|
|||
public Screen ScreenFromWindow(IWindowBaseImpl window) |
|||
{ |
|||
return ScreenHelper.ScreenFromWindow(window, AllScreens); |
|||
} |
|||
|
|||
public int ScreenCount => _impl.Screens.Length; |
|||
|
|||
public IReadOnlyList<Screen> AllScreens => |
|||
_impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); |
|||
} |
|||
|
|||
internal interface IX11Screens |
|||
{ |
|||
X11Screen[] Screens { get; } |
|||
} |
|||
|
|||
internal class X11ScreensUserSettings |
|||
{ |
|||
public double GlobalScaleFactor { get; set; } = 1; |
|||
public Dictionary<string, double> NamedScaleFactors { get; set; } |
|||
|
|||
private 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 rv; |
|||
} |
|||
|
|||
|
|||
public static X11ScreensUserSettings Detect() |
|||
{ |
|||
return DetectEnvironment() ?? new X11ScreensUserSettings(); |
|||
} |
|||
} |
|||
|
|||
internal class X11Screen |
|||
{ |
|||
private const int FullHDWidth = 1920; |
|||
private const int FullHDHeight = 1080; |
|||
public bool IsPrimary { get; } |
|||
public string Name { get; set; } |
|||
public PixelRect Bounds { get; set; } |
|||
public Size? PhysicalSize { get; set; } |
|||
public double Scaling { get; set; } |
|||
public PixelRect WorkingArea { get; set; } |
|||
|
|||
public X11Screen( |
|||
PixelRect bounds, |
|||
bool isPrimary, |
|||
string name, |
|||
Size? physicalSize, |
|||
double? scaling) |
|||
{ |
|||
IsPrimary = isPrimary; |
|||
Name = name; |
|||
Bounds = bounds; |
|||
if (physicalSize == null && scaling == null) |
|||
{ |
|||
Scaling = 1; |
|||
} |
|||
else if (scaling == null) |
|||
{ |
|||
Scaling = GuessPixelDensity(bounds, physicalSize.Value); |
|||
} |
|||
else |
|||
{ |
|||
Scaling = scaling.Value; |
|||
PhysicalSize = physicalSize; |
|||
} |
|||
} |
|||
|
|||
public static double GuessPixelDensity(PixelRect pixel, Size physical) |
|||
{ |
|||
var calculatedDensity = 1d; |
|||
if(physical.Width > 0) |
|||
calculatedDensity = pixel.Width <= FullHDWidth ? 1 : Math.Max(1, pixel.Width / physical.Width * 25.4 / 96); |
|||
else if(physical.Height > 0) |
|||
calculatedDensity = pixel.Height <= FullHDHeight ? 1 : Math.Max(1, pixel.Height / physical.Height * 25.4 / 96); |
|||
|
|||
if(calculatedDensity > 3) |
|||
return 1; |
|||
else |
|||
{ |
|||
var sanePixelDensities = new double[] { 1, 1.25, 1.50, 1.75, 2 }; |
|||
foreach(var saneDensity in sanePixelDensities) |
|||
{ |
|||
if(calculatedDensity <= saneDensity + 0.20) |
|||
return saneDensity; |
|||
} |
|||
return sanePixelDensities.Last(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using static Avalonia.X11.XLib; |
|||
namespace Avalonia.X11; |
|||
|
|||
internal class XResources |
|||
{ |
|||
private Dictionary<string, string> _resources = new(); |
|||
private readonly X11Info _x11; |
|||
public event Action<string>? ResourceChanged; |
|||
|
|||
public XResources(AvaloniaX11Platform plat) |
|||
{ |
|||
_x11 = plat.Info; |
|||
plat.Globals.RootPropertyChanged += OnRootPropertyChanged; |
|||
UpdateResources(); |
|||
} |
|||
|
|||
void UpdateResources() |
|||
{ |
|||
var res = ReadResourcesString() ?? ""; |
|||
var items = res.Split('\n'); |
|||
var newResources = new Dictionary<string, string>(); |
|||
var missingResources = new HashSet<string>(_resources.Keys); |
|||
var changedResources = new HashSet<string>(); |
|||
foreach (var item in items) |
|||
{ |
|||
var sp = item.Split(new[] { ':' }, 2); |
|||
if (sp.Length < 2) |
|||
continue; |
|||
var key = sp[0]; |
|||
var value = sp[1].TrimStart(); |
|||
newResources[key] = value; |
|||
if (!missingResources.Remove(sp[0]) || _resources[key] != value) |
|||
changedResources.Add(key); |
|||
} |
|||
_resources = newResources; |
|||
foreach (var missing in missingResources) |
|||
ResourceChanged?.Invoke(missing); |
|||
foreach (var changed in changedResources) |
|||
ResourceChanged?.Invoke(changed); |
|||
} |
|||
|
|||
public string? GetResource(string key) |
|||
{ |
|||
_resources.TryGetValue(key, out var value); |
|||
return value; |
|||
} |
|||
|
|||
string ReadResourcesString() |
|||
{ |
|||
XGetWindowProperty(_x11.Display, _x11.RootWindow, _x11.Atoms.XA_RESOURCE_MANAGER, |
|||
IntPtr.Zero, new IntPtr(0x7fffffff), |
|||
false, _x11.Atoms.XA_STRING, out var actualType, out var actualFormat, |
|||
out var nitems, out _, out var prop); |
|||
try |
|||
{ |
|||
if (actualFormat != 8) |
|||
return null; |
|||
return Marshal.PtrToStringAnsi(prop, nitems.ToInt32()); |
|||
} |
|||
finally |
|||
{ |
|||
XFree(prop); |
|||
} |
|||
} |
|||
|
|||
private void OnRootPropertyChanged(IntPtr atom) |
|||
{ |
|||
if (atom == _x11.Atoms.XA_RESOURCE_MANAGER) |
|||
UpdateResources(); |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net472</TargetFramework> |
|||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> |
|||
<OutputType>Library</OutputType> |
|||
<IsPackable>false</IsPackable> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
<Configuration Condition="'$(Configuration)'==''">Debug</Configuration> |
|||
</PropertyGroup> |
|||
|
|||
<Import Project="..\..\build\Moq.props" /> |
|||
<Import Project="..\..\build\Rx.props" /> |
|||
<Import Project="..\..\build\HarfBuzzSharp.props" /> |
|||
<Import Project="..\..\build\XUnit.props" /> |
|||
<Import Project="..\..\build\SharedVersion.props" /> |
|||
<ItemGroup> |
|||
<Content Include="..\TestFiles\BuildTasks\PInvoke\bin\$(Configuration)\netstandard2.0\PInvoke.dll" Link="Assets\PInvoke.dll"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</Content> |
|||
<Content Include="..\TestFiles\BuildTasks\PInvoke\bin\$(Configuration)\netstandard2.0\PInvoke.dll.refs" Link="Assets\PInvoke.dll.refs"> |
|||
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
|||
</Content> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj" /> |
|||
<!-- Ensure PInvoke.csproj is build before Avalonia.Build.Tasks.UnitTest --> |
|||
<ProjectReference Include="..\TestFiles\BuildTasks\PInvoke\PInvoke.csproj" |
|||
SetConfiguration="Configuration=$(Configuration)" |
|||
SetTargetFramework="TargetFramework=netstandard2.0" |
|||
ReferenceOutputAssembly="false" |
|||
PrivateAssets="all" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,38 @@ |
|||
using System; |
|||
using System.IO; |
|||
using System.Reflection; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Build.Tasks.UnitTest; |
|||
|
|||
public class CompileAvaloniaXamlTaskTest |
|||
{ |
|||
|
|||
[Fact] |
|||
public void Does_Not_Fail_When_Codebehind_Contains_DllImport() |
|||
{ |
|||
using var engine = UnitTestBuildEngine.Start(); |
|||
var basePath = Path.Combine(Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath), "Assets"); |
|||
var originalAssemblyPath = Path.Combine(basePath, |
|||
"PInvoke.dll"); |
|||
var referencesPath = Path.Combine(basePath, |
|||
"PInvoke.dll.refs"); |
|||
var compiledAssemblyPath = "PInvoke.dll"; |
|||
|
|||
Assert.True(File.Exists(originalAssemblyPath), $"The original {originalAssemblyPath} don't exists."); |
|||
|
|||
new CompileAvaloniaXamlTask() |
|||
{ |
|||
AssemblyFile = originalAssemblyPath, |
|||
ReferencesFilePath = referencesPath, |
|||
OutputPath = compiledAssemblyPath, |
|||
RefAssemblyFile = null, |
|||
BuildEngine = engine, |
|||
ProjectDirectory = Directory.GetCurrentDirectory(), |
|||
VerifyIl = true |
|||
}.Execute(); |
|||
Assert.Equal(0, engine.Errors.Count); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Microsoft.Build.Framework; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Build.Tasks.UnitTest; |
|||
|
|||
/// <summary>
|
|||
/// This is fake BuildEngine using for testing build task
|
|||
/// at moment it manage only <see cref="BuildErrorEventArgs"/> and <see cref="BuildWarningEventArgs"/>
|
|||
/// other messages are ignored/>
|
|||
/// </summary>
|
|||
internal class UnitTestBuildEngine : IBuildEngine, IDisposable |
|||
{ |
|||
private readonly bool _treatWarningAsError; |
|||
private readonly bool _assertOnDispose; |
|||
private readonly List<UnitTestBuildEngineMessage> _errors = new(); |
|||
|
|||
/// <summary>
|
|||
/// Start new instance of <see cref="UnitTestBuildEngine"/>
|
|||
/// </summary>
|
|||
/// <param name="continueOnError">if it is <c>false</c> immediately assert error</param>
|
|||
/// <param name="treatWarningAsError">if it is <c>true</c> treat warning as error</param>
|
|||
/// <param name="assertOnDispose">if it is <c>true</c> assert on dispose if there are any errors.</param>
|
|||
/// <returns></returns>
|
|||
public static UnitTestBuildEngine Start(bool continueOnError = false, |
|||
bool treatWarningAsError = false, |
|||
bool assertOnDispose = false) => |
|||
new UnitTestBuildEngine(continueOnError, treatWarningAsError, assertOnDispose); |
|||
|
|||
private UnitTestBuildEngine(bool continueOnError, |
|||
bool treatWarningAsError, |
|||
bool assertOnDispose) |
|||
{ |
|||
ContinueOnError = continueOnError; |
|||
_treatWarningAsError = treatWarningAsError; |
|||
_assertOnDispose = assertOnDispose; |
|||
} |
|||
|
|||
public bool ContinueOnError { get; } |
|||
|
|||
public int LineNumberOfTaskNode { get; } |
|||
|
|||
public int ColumnNumberOfTaskNode { get; } |
|||
|
|||
public string ProjectFileOfTaskNode { get; } |
|||
|
|||
public IReadOnlyList<UnitTestBuildEngineMessage> Errors => _errors; |
|||
|
|||
public bool BuildProjectFile(string projectFileName, |
|||
string[] targetNames, |
|||
IDictionary globalProperties, |
|||
IDictionary targetOutputs) |
|||
=> throw new NotImplementedException(); |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_assertOnDispose && _errors.Count > 0) |
|||
{ |
|||
Assert.Fail("There is one o more errors."); |
|||
} |
|||
} |
|||
|
|||
|
|||
public void LogCustomEvent(CustomBuildEventArgs e) |
|||
{ |
|||
} |
|||
|
|||
public void LogMessageEvent(BuildMessageEventArgs e) |
|||
{ |
|||
} |
|||
|
|||
public void LogErrorEvent(BuildErrorEventArgs e) |
|||
{ |
|||
var message = UnitTestBuildEngineMessage.From(e); |
|||
_errors.Add(message); |
|||
if (!ContinueOnError) |
|||
{ |
|||
Assert.Fail(message.Message); |
|||
} |
|||
} |
|||
|
|||
public void LogWarningEvent(BuildWarningEventArgs e) |
|||
{ |
|||
if (_treatWarningAsError) |
|||
{ |
|||
var message = UnitTestBuildEngineMessage.From(e); |
|||
_errors.Add(message); |
|||
if (!ContinueOnError) |
|||
{ |
|||
Assert.Fail(message.Message); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using Microsoft.Build.Framework; |
|||
|
|||
namespace Avalonia.Build.Tasks.UnitTest; |
|||
|
|||
enum MessageSource |
|||
{ |
|||
Unknown, |
|||
ErrorEvent, |
|||
MessageEvent, |
|||
CustomEvent, |
|||
WarningEvent |
|||
} |
|||
|
|||
record class UnitTestBuildEngineMessage |
|||
{ |
|||
private UnitTestBuildEngineMessage(MessageSource Type, LazyFormattedBuildEventArgs Source) |
|||
{ |
|||
this.Type = Type; |
|||
this.Source = Source; |
|||
Message = Source.Message; |
|||
} |
|||
|
|||
public MessageSource Type { get; } |
|||
public LazyFormattedBuildEventArgs Source { get; } |
|||
public string Message { get; } |
|||
|
|||
public static UnitTestBuildEngineMessage From(BuildWarningEventArgs buildWarning) => |
|||
new UnitTestBuildEngineMessage(MessageSource.WarningEvent, buildWarning); |
|||
|
|||
public static UnitTestBuildEngineMessage From(BuildMessageEventArgs buildMessage) => |
|||
new UnitTestBuildEngineMessage(MessageSource.MessageEvent, buildMessage); |
|||
|
|||
public static UnitTestBuildEngineMessage From(BuildErrorEventArgs buildError) => |
|||
new UnitTestBuildEngineMessage(MessageSource.ErrorEvent, buildError); |
|||
|
|||
public static UnitTestBuildEngineMessage From(CustomBuildEventArgs customBuild) => |
|||
new UnitTestBuildEngineMessage(MessageSource.CustomEvent, customBuild); |
|||
|
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
<Application |
|||
xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
x:Class="PInvoke.App"> |
|||
<Application.Styles> |
|||
<FluentTheme /> |
|||
</Application.Styles> |
|||
</Application> |
|||
@ -0,0 +1,21 @@ |
|||
using Avalonia; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace PInvoke; |
|||
|
|||
public class App : Application |
|||
{ |
|||
public override void Initialize() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
public override void OnFrameworkInitializationCompleted() |
|||
{ |
|||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) |
|||
{ |
|||
desktopLifetime.MainWindow = new MainWindow(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
<Window xmlns="https://github.com/avaloniaui" |
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' |
|||
x:Class="PInvoke.MainWindow"> |
|||
</Window> |
|||
@ -0,0 +1,22 @@ |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace PInvoke; |
|||
|
|||
public partial class MainWindow : Window |
|||
{ |
|||
[DllImport(@"libhello")] |
|||
extern static int add(int a, int b); |
|||
|
|||
public MainWindow() |
|||
{ |
|||
InitializeComponent(); |
|||
} |
|||
|
|||
protected override void OnLoaded(RoutedEventArgs e) |
|||
{ |
|||
base.OnLoaded(e); |
|||
var x = add(1, 2); |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>WinExe</OutputType> |
|||
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> |
|||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> |
|||
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators> |
|||
<!--<AvaloniaXamlIlDebuggerLaunch>true</AvaloniaXamlIlDebuggerLaunch>--> |
|||
<EnableAvaloniaXamlCompilation>false</EnableAvaloniaXamlCompilation> |
|||
<IncludeAvaloniaGenerators>true</IncludeAvaloniaGenerators> |
|||
<IsPackable>false</IsPackable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\src\Avalonia.Fonts.Inter\Avalonia.Fonts.Inter.csproj" /> |
|||
<ProjectReference Include="..\..\..\..\src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<Import Project="..\..\..\..\build\SampleApp.props" /> |
|||
<Import Project="..\..\..\..\build\ReferenceCoreLibraries.props" /> |
|||
<Import Project="..\..\..\..\build\BuildTargets.targets" /> |
|||
<Import Project="..\..\..\..\build\SourceGenerators.props" /> |
|||
</Project> |
|||
@ -0,0 +1,14 @@ |
|||
using Avalonia; |
|||
|
|||
namespace PInvoke; |
|||
|
|||
public class Program |
|||
{ |
|||
static void Main(string[] args) => BuildAvaloniaApp() |
|||
.StartWithClassicDesktopLifetime(args); |
|||
|
|||
public static AppBuilder BuildAvaloniaApp() => |
|||
AppBuilder.Configure<App>() |
|||
.UsePlatformDetect() |
|||
.LogToTrace(); |
|||
} |
|||
Loading…
Reference in new issue