[X11] Platform threading interface and platform sanity checks
This commit is contained in:
@@ -188,6 +188,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.OpenGL", "src\Avalonia.OpenGL\Avalonia.OpenGL.csproj", "{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{212D02D5-C873-469A-8E78-4A6350EC4A1A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{8B5768BB-71F9-4E23-89B5-DDBA6458B856}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
|
||||
@@ -1689,6 +1693,54 @@ Global
|
||||
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{7CCAEFC4-135D-401D-BDDD-896B9B7D3569}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -1742,6 +1794,8 @@ Global
|
||||
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
|
||||
{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
|
||||
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
|
||||
{212D02D5-C873-469A-8E78-4A6350EC4A1A} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
|
||||
{8B5768BB-71F9-4E23-89B5-DDBA6458B856} = {9B9E3891-2366-4253-A952-D08BCEB71098}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user