10 changed files with 656 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||
<Application xmlns="https://github.com/avaloniaui"> |
|||
<Application.Styles> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> |
|||
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/> |
|||
</Application.Styles> |
|||
</Application> |
|||
@ -0,0 +1,13 @@ |
|||
using Avalonia; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace PlatformSanityChecks |
|||
{ |
|||
public class App : Application |
|||
{ |
|||
public override void Initialize() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<OutputType>Exe</OutputType> |
|||
<TargetFramework>netcoreapp2.0</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
|
|||
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> |
|||
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> |
|||
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,140 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Reactive.Disposables; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading; |
|||
using Avalonia; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using Avalonia.X11; |
|||
|
|||
namespace PlatformSanityChecks |
|||
{ |
|||
public class Program |
|||
{ |
|||
static Thread UiThread; |
|||
|
|||
static void Main(string[] args) |
|||
{ |
|||
UiThread = Thread.CurrentThread; |
|||
AppBuilder.Configure<App>().RuntimePlatformServicesInitializer(); |
|||
var app = new App(); |
|||
|
|||
new AvaloniaX11Platform().Initialize(); |
|||
|
|||
CheckPlatformThreading(); |
|||
|
|||
|
|||
|
|||
} |
|||
|
|||
static bool CheckAccess() => UiThread == Thread.CurrentThread; |
|||
|
|||
static void VerifyAccess() |
|||
{ |
|||
if (!CheckAccess()) |
|||
Die("Call from invalid thread"); |
|||
} |
|||
|
|||
static Exception Die(string error) |
|||
{ |
|||
Console.Error.WriteLine(error); |
|||
Console.Error.WriteLine(Environment.StackTrace); |
|||
Process.GetCurrentProcess().Kill(); |
|||
throw new Exception(error); |
|||
} |
|||
|
|||
static IDisposable Enter([CallerMemberName] string caller = null) |
|||
{ |
|||
Console.WriteLine("Entering " + caller); |
|||
return Disposable.Create(() => { Console.WriteLine("Leaving " + caller); }); |
|||
} |
|||
|
|||
static void EnterLoop(Action<CancellationTokenSource> cb, [CallerMemberName] string caller = null) |
|||
{ |
|||
using (Enter(caller)) |
|||
{ |
|||
var cts = new CancellationTokenSource(); |
|||
cb(cts); |
|||
Dispatcher.UIThread.MainLoop(cts.Token); |
|||
if (!cts.IsCancellationRequested) |
|||
Die("Unexpected loop exit"); |
|||
} |
|||
} |
|||
|
|||
static void CheckTimerOrdering() => EnterLoop(cts => |
|||
{ |
|||
bool firstFired = false, secondFired = false; |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
Console.WriteLine("Second tick"); |
|||
VerifyAccess(); |
|||
if (!firstFired) |
|||
throw Die("Invalid timer ordering"); |
|||
if (secondFired) |
|||
throw Die("Invocation of finished timer"); |
|||
secondFired = true; |
|||
cts.Cancel(); |
|||
return false; |
|||
}, TimeSpan.FromSeconds(2)); |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
Console.WriteLine("First tick"); |
|||
VerifyAccess(); |
|||
if (secondFired) |
|||
throw Die("Invalid timer ordering"); |
|||
if (firstFired) |
|||
throw Die("Invocation of finished timer"); |
|||
firstFired = true; |
|||
return false; |
|||
}, TimeSpan.FromSeconds(1)); |
|||
}); |
|||
|
|||
static void CheckTimerTicking() => EnterLoop(cts => |
|||
{ |
|||
int ticks = 0; |
|||
var st = Stopwatch.StartNew(); |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
ticks++; |
|||
Console.WriteLine($"Tick {ticks} at {st.Elapsed}"); |
|||
if (ticks == 5) |
|||
{ |
|||
if (st.Elapsed.TotalSeconds < 4.5) |
|||
Die("Timer is too fast"); |
|||
if (st.Elapsed.TotalSeconds > 6) |
|||
Die("Timer is too slow"); |
|||
cts.Cancel(); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}, TimeSpan.FromSeconds(1)); |
|||
|
|||
|
|||
}); |
|||
|
|||
|
|||
static void CheckSignaling() => EnterLoop(cts => |
|||
{ |
|||
ThreadPool.QueueUserWorkItem(_ => |
|||
{ |
|||
Thread.Sleep(100); |
|||
Dispatcher.UIThread.Post(() => |
|||
{ |
|||
VerifyAccess(); |
|||
cts.Cancel(); |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
static void CheckPlatformThreading() |
|||
{ |
|||
CheckSignaling(); |
|||
CheckTimerOrdering(); |
|||
CheckTimerTicking(); |
|||
|
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,12 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.X11 |
|||
{ |
|||
public class X11Exception : Exception |
|||
{ |
|||
public X11Exception(string message) : base(message) |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Platform; |
|||
using Avalonia.X11; |
|||
using static Avalonia.X11.XLib; |
|||
namespace Avalonia.X11 |
|||
{ |
|||
public class AvaloniaX11Platform |
|||
{ |
|||
public void Initialize() |
|||
{ |
|||
Display = XOpenDisplay(IntPtr.Zero); |
|||
DeferredDisplay = XOpenDisplay(IntPtr.Zero); |
|||
if (Display == IntPtr.Zero) |
|||
throw new Exception("XOpenDisplay failed"); |
|||
|
|||
|
|||
AvaloniaLocator.CurrentMutable.BindToSelf(this) |
|||
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(Display)); |
|||
|
|||
} |
|||
|
|||
public IntPtr DeferredDisplay { get; set; } |
|||
public IntPtr Display { get; set; } |
|||
} |
|||
} |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class AvaloniaX11PlatformExtensions |
|||
{ |
|||
public static T UseX11<T>(this T builder) where T : AppBuilderBase<T>, new() |
|||
{ |
|||
builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize()); |
|||
return builder; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,251 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
using static Avalonia.X11.XLib; |
|||
|
|||
namespace Avalonia.X11 |
|||
{ |
|||
public unsafe class X11PlatformThreading : IPlatformThreadingInterface |
|||
{ |
|||
private readonly IntPtr _display; |
|||
private Thread _mainThread; |
|||
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
struct epoll_data |
|||
{ |
|||
[FieldOffset(0)] |
|||
public IntPtr ptr; |
|||
[FieldOffset(0)] |
|||
public int fd; |
|||
[FieldOffset(0)] |
|||
public uint u32; |
|||
[FieldOffset(0)] |
|||
public ulong u64; |
|||
} |
|||
|
|||
private const int EPOLLIN = 1; |
|||
private const int EPOLL_CTL_ADD = 1; |
|||
private const int O_NONBLOCK = 2048; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct epoll_event |
|||
{ |
|||
public uint events; |
|||
public epoll_data data; |
|||
} |
|||
|
|||
[DllImport("libc")] |
|||
extern static int epoll_create1(int size); |
|||
|
|||
[DllImport("libc")] |
|||
extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event); |
|||
|
|||
[DllImport("libc")] |
|||
extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout); |
|||
|
|||
[DllImport("libc")] |
|||
extern static int pipe2(int* fds, int flags); |
|||
[DllImport("libc")] |
|||
extern static IntPtr write(int fd, void* buf, IntPtr count); |
|||
|
|||
[DllImport("libc")] |
|||
extern static IntPtr read(int fd, void* buf, IntPtr count); |
|||
|
|||
enum EventCodes |
|||
{ |
|||
X11 = 1, |
|||
Signal =2 |
|||
} |
|||
|
|||
private int _sigread, _sigwrite; |
|||
private object _lock = new object(); |
|||
private bool _signaled; |
|||
private DispatcherPriority _signaledPriority; |
|||
private int _epoll; |
|||
private Stopwatch _clock = Stopwatch.StartNew(); |
|||
|
|||
class X11Timer : IDisposable |
|||
{ |
|||
private readonly X11PlatformThreading _parent; |
|||
|
|||
public X11Timer(X11PlatformThreading parent, DispatcherPriority prio, TimeSpan interval, Action tick) |
|||
{ |
|||
_parent = parent; |
|||
Priority = prio; |
|||
Tick = tick; |
|||
Interval = interval; |
|||
Reschedule(); |
|||
} |
|||
|
|||
public DispatcherPriority Priority { get; } |
|||
public TimeSpan NextTick { get; private set; } |
|||
public TimeSpan Interval { get; } |
|||
public Action Tick { get; } |
|||
public bool Disposed { get; private set; } |
|||
|
|||
public void Reschedule() |
|||
{ |
|||
NextTick = _parent._clock.Elapsed + Interval; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
Disposed = true; |
|||
lock (_parent._lock) |
|||
_parent._timers.Remove(this); |
|||
} |
|||
} |
|||
|
|||
List<X11Timer> _timers = new List<X11Timer>(); |
|||
|
|||
public X11PlatformThreading(IntPtr display) |
|||
{ |
|||
_display = display; |
|||
_mainThread = Thread.CurrentThread; |
|||
var fd = XLib.XConnectionNumber(display); |
|||
var ev = new epoll_event() |
|||
{ |
|||
events = EPOLLIN, |
|||
data = {u32 = (int)EventCodes.X11} |
|||
}; |
|||
_epoll = epoll_create1(0); |
|||
if (_epoll == -1) |
|||
throw new X11Exception("epoll_create1 failed"); |
|||
|
|||
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, ref ev) == -1) |
|||
throw new X11Exception("Unable to attach X11 connection handle to epoll"); |
|||
|
|||
var fds = stackalloc int[2]; |
|||
pipe2(fds, O_NONBLOCK); |
|||
_sigread = fds[0]; |
|||
_sigwrite = fds[1]; |
|||
|
|||
ev = new epoll_event |
|||
{ |
|||
events = EPOLLIN, |
|||
data = {u32 = (int)EventCodes.Signal} |
|||
}; |
|||
if (epoll_ctl(_epoll, EPOLL_CTL_ADD, _sigread, ref ev) == -1) |
|||
throw new X11Exception("Unable to attach signal pipe to epoll"); |
|||
} |
|||
|
|||
int TimerComparer(X11Timer t1, X11Timer t2) |
|||
{ |
|||
return t2.Priority - t1.Priority; |
|||
} |
|||
|
|||
public void RunLoop(CancellationToken cancellationToken) |
|||
{ |
|||
var readyTimers = new List<X11Timer>(); |
|||
while (!cancellationToken.IsCancellationRequested) |
|||
{ |
|||
var now = _clock.Elapsed; |
|||
TimeSpan? nextTick = null; |
|||
readyTimers.Clear(); |
|||
lock(_timers) |
|||
foreach (var t in _timers) |
|||
{ |
|||
if (nextTick == null || t.NextTick < nextTick.Value) |
|||
nextTick = t.NextTick; |
|||
if (t.NextTick < now) |
|||
readyTimers.Add(t); |
|||
} |
|||
|
|||
readyTimers.Sort(TimerComparer); |
|||
|
|||
foreach (var t in readyTimers) |
|||
{ |
|||
if (cancellationToken.IsCancellationRequested) |
|||
return; |
|||
t.Tick(); |
|||
if(!t.Disposed) |
|||
{ |
|||
t.Reschedule(); |
|||
if (nextTick == null || t.NextTick < nextTick.Value) |
|||
nextTick = t.NextTick; |
|||
} |
|||
} |
|||
|
|||
if (cancellationToken.IsCancellationRequested) |
|||
return; |
|||
epoll_event ev; |
|||
var len = epoll_wait(_epoll, &ev, 1, |
|||
nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds)); |
|||
if (cancellationToken.IsCancellationRequested) |
|||
return; |
|||
if (len == 0) |
|||
{ |
|||
// We handle timer-related stuff at the beginning of the loop
|
|||
continue; |
|||
} |
|||
else |
|||
{ |
|||
if (ev.data.u32 == (int)EventCodes.Signal) |
|||
{ |
|||
int buf = 0; |
|||
while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0) |
|||
{ |
|||
} |
|||
|
|||
DispatcherPriority prio; |
|||
lock (_lock) |
|||
{ |
|||
_signaled = false; |
|||
prio = _signaledPriority; |
|||
_signaledPriority = DispatcherPriority.MinValue; |
|||
} |
|||
Signaled?.Invoke(prio); |
|||
} |
|||
else |
|||
{ |
|||
while (XPending(_display)) |
|||
{ |
|||
if (cancellationToken.IsCancellationRequested) |
|||
return; |
|||
XNextEvent(_display, out var xev); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
public void Signal(DispatcherPriority priority) |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
if (priority > _signaledPriority) |
|||
_signaledPriority = priority; |
|||
|
|||
if(_signaled) |
|||
return; |
|||
_signaled = true; |
|||
int buf = 0; |
|||
write(_sigwrite, &buf, new IntPtr(1)); |
|||
} |
|||
} |
|||
|
|||
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread; |
|||
public event Action<DispatcherPriority?> Signaled; |
|||
|
|||
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) |
|||
{ |
|||
if (_mainThread != Thread.CurrentThread) |
|||
throw new InvalidOperationException("StartTimer can be only called from UI thread"); |
|||
if (interval <= TimeSpan.Zero) |
|||
throw new ArgumentException("Interval must be positive", nameof(interval)); |
|||
|
|||
// We assume that we are on the main thread and outside of epoll_wait, so there is no need for wakeup signal
|
|||
|
|||
var timer = new X11Timer(this, priority, interval, tick); |
|||
lock(_timers) |
|||
_timers.Add(timer); |
|||
return timer; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace Avalonia.X11 |
|||
{ |
|||
public static class XLib |
|||
{ |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XInitThreads(); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XOpenDisplay(IntPtr name); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XConnectionNumber(IntPtr display); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XLockDisplay(IntPtr display); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XUnlockDisplay(IntPtr display); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XInitImage(ref XImage image); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XDestroyImage(ref XImage image); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern IntPtr XSetErrorHandler(XErrorHandler handler); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XSync(IntPtr display, bool discard); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XNextEvent(IntPtr display, out XEvent ev); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern bool XPending(IntPtr display); |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public struct XAnyEvent |
|||
{ |
|||
|
|||
public int Type; |
|||
public ulong Serial; /* # of last request processed by server */ |
|||
public bool send_event; /* true if this came from a SendEvent request */ |
|||
public IntPtr display; /* Display the event was read from */ |
|||
public IntPtr window; /* window on which event was requested in event mask */ |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
public unsafe struct XEvent |
|||
{ |
|||
[FieldOffset(0)] public int Type; |
|||
[FieldOffset(0)] public XAnyEvent XAny; |
|||
[FieldOffset(0)] private fixed int pad[40]; |
|||
} |
|||
|
|||
public delegate int XErrorHandler(IntPtr display, ref XErrorEvent error); |
|||
|
|||
[DllImport("libX11.so.6")] |
|||
public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, |
|||
int srcx, int srcy, int destx, int desty, uint width, uint height); |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public unsafe struct XErrorEvent |
|||
{ |
|||
public int type; |
|||
public IntPtr* display; /* Display the event was read from */ |
|||
public ulong serial; /* serial number of failed request */ |
|||
public byte error_code; /* error code of failed request */ |
|||
public byte request_code; /* Major op-code of failed request */ |
|||
public byte minor_code; /* Minor op-code of failed request */ |
|||
public IntPtr resourceid; /* resource id */ |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
public unsafe struct XImage |
|||
{ |
|||
public int width, height; /* size of image */ |
|||
public int xoffset; /* number of pixels offset in X direction */ |
|||
public int format; /* XYBitmap, XYPixmap, ZPixmap */ |
|||
public IntPtr data; /* pointer to image data */ |
|||
public int byte_order; /* data byte order, LSBFirst, MSBFirst */ |
|||
public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ |
|||
public int bitmap_bit_order; /* LSBFirst, MSBFirst */ |
|||
public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ |
|||
public int depth; /* depth of image */ |
|||
public int bytes_per_line; /* accelerator to next scanline */ |
|||
public int bits_per_pixel; /* bits per pixel (ZPixmap) */ |
|||
public ulong red_mask; /* bits in z arrangement */ |
|||
public ulong green_mask; |
|||
public ulong blue_mask; |
|||
private fixed byte funcs[128]; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue