csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
243 lines
7.7 KiB
243 lines
7.7 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
|
|
{
|
|
internal unsafe class X11PlatformThreading : IControlledDispatcherImpl, IDispatcherClock
|
|
{
|
|
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)]
|
|
private 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)]
|
|
private struct epoll_event
|
|
{
|
|
public uint events;
|
|
public epoll_data data;
|
|
}
|
|
|
|
[DllImport("libc")]
|
|
private extern static int epoll_create1(int size);
|
|
|
|
[DllImport("libc")]
|
|
private extern static int epoll_ctl(int epfd, int op, int fd, ref epoll_event __event);
|
|
|
|
[DllImport("libc")]
|
|
private extern static int epoll_wait(int epfd, epoll_event* events, int maxevents, int timeout);
|
|
|
|
[DllImport("libc")]
|
|
private extern static int pipe2(int* fds, int flags);
|
|
[DllImport("libc")]
|
|
private extern static IntPtr write(int fd, void* buf, IntPtr count);
|
|
|
|
[DllImport("libc")]
|
|
private extern static IntPtr read(int fd, void* buf, IntPtr count);
|
|
|
|
private enum EventCodes
|
|
{
|
|
X11 = 1,
|
|
Signal =2
|
|
}
|
|
|
|
private int _sigread, _sigwrite;
|
|
private object _lock = new object();
|
|
private bool _signaled;
|
|
private bool _wakeupRequested;
|
|
private long? _nextTimer;
|
|
private int _epoll;
|
|
private Stopwatch _clock = Stopwatch.StartNew();
|
|
|
|
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");
|
|
}
|
|
|
|
private void CheckSignaled()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if (!_signaled)
|
|
return;
|
|
_signaled = false;
|
|
}
|
|
|
|
Signaled?.Invoke();
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RunLoop(CancellationToken cancellationToken)
|
|
{
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
var now = _clock.ElapsedMilliseconds;
|
|
if (_nextTimer.HasValue && now > _nextTimer.Value)
|
|
{
|
|
Timer?.Invoke();
|
|
}
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return;
|
|
|
|
//Flush whatever requests were made to XServer
|
|
XFlush(_display);
|
|
epoll_event ev;
|
|
if (XPending(_display) == 0)
|
|
{
|
|
now = _clock.ElapsedMilliseconds;
|
|
if (_nextTimer < now)
|
|
continue;
|
|
|
|
var timeout = _nextTimer == null ? (int)-1 : Math.Max(1, _nextTimer.Value - now);
|
|
epoll_wait(_epoll, &ev, 1, (int)Math.Min(int.MaxValue, timeout));
|
|
|
|
// Drain the signaled pipe
|
|
int buf = 0;
|
|
while (read(_sigread, &buf, new IntPtr(4)).ToInt64() > 0)
|
|
{
|
|
}
|
|
|
|
lock (_lock)
|
|
_wakeupRequested = false;
|
|
}
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
return;
|
|
CheckSignaled();
|
|
HandleX11(cancellationToken);
|
|
while (_platform.EventGrouperDispatchQueue.HasJobs)
|
|
{
|
|
CheckSignaled();
|
|
_platform.EventGrouperDispatchQueue.DispatchNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Wakeup()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if(_wakeupRequested)
|
|
return;
|
|
_wakeupRequested = true;
|
|
int buf = 0;
|
|
write(_sigwrite, &buf, new IntPtr(1));
|
|
}
|
|
}
|
|
|
|
public void Signal()
|
|
{
|
|
lock (_lock)
|
|
{
|
|
if(_signaled)
|
|
return;
|
|
_signaled = true;
|
|
Wakeup();
|
|
}
|
|
}
|
|
|
|
public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _mainThread;
|
|
|
|
public event Action Signaled;
|
|
public event Action Timer;
|
|
|
|
public void UpdateTimer(int? dueTimeInTicks)
|
|
{
|
|
_nextTimer = dueTimeInTicks;
|
|
if (_nextTimer != null)
|
|
Wakeup();
|
|
}
|
|
|
|
|
|
public int TickCount => (int)_clock.ElapsedMilliseconds;
|
|
public bool CanQueryPendingInput => true;
|
|
|
|
public bool HasPendingInput => _platform.EventGrouperDispatchQueue.HasJobs || XPending(_display) != 0;
|
|
}
|
|
}
|
|
|