A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

284 lines
9.4 KiB

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
{
unsafe class X11PlatformThreading : IPlatformThreadingInterface
{
private readonly AvaloniaX11Platform _platform;
private readonly IntPtr _display;
public delegate void EventHandler(ref XEvent xev);
private readonly Dictionary<IntPtr, EventHandler> _eventHandlers;
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(AvaloniaX11Platform platform)
{
_platform = platform;
_display = platform.Display;
_eventHandlers = platform.Windows;
_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;
}
void CheckSignaled()
{
int buf = 0;
while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0)
{
}
DispatcherPriority prio;
lock (_lock)
{
if (!_signaled)
return;
_signaled = false;
prio = _signaledPriority;
_signaledPriority = DispatcherPriority.MinValue;
}
Signaled?.Invoke(prio);
}
unsafe void HandleX11(CancellationToken cancellationToken)
{
while (XPending(_display) != 0)
{
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if(XFilterEvent(ref xev, IntPtr.Zero))
continue;
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
try
{
if (xev.type == XEventName.GenericEvent)
{
if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
xev.GenericEventCookie.extension)
{
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(ref xev);
}
finally
{
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
XFreeEventData(_display, &xev.GenericEventCookie);
}
}
Dispatcher.UIThread.RunJobs();
}
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;
//Flush whatever requests were made to XServer
XFlush(_display);
epoll_event ev;
if (XPending(_display) == 0)
epoll_wait(_epoll, &ev, 1,
nextTick == null ? -1 : Math.Max(1, (int)(nextTick.Value - _clock.Elapsed).TotalMilliseconds));
if (cancellationToken.IsCancellationRequested)
return;
CheckSignaled();
HandleX11(cancellationToken);
}
}
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;
}
}
}