diff --git a/Avalonia.sln b/Avalonia.sln
index f71a94888d..388dbb6e6a 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -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}
diff --git a/samples/PlatformSanityChecks/App.xaml b/samples/PlatformSanityChecks/App.xaml
new file mode 100644
index 0000000000..25bab6ae35
--- /dev/null
+++ b/samples/PlatformSanityChecks/App.xaml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/samples/PlatformSanityChecks/App.xaml.cs b/samples/PlatformSanityChecks/App.xaml.cs
new file mode 100644
index 0000000000..508fc1e34b
--- /dev/null
+++ b/samples/PlatformSanityChecks/App.xaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Markup.Xaml;
+
+namespace PlatformSanityChecks
+{
+ public class App : Application
+ {
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj
new file mode 100644
index 0000000000..00b5b10106
--- /dev/null
+++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj
@@ -0,0 +1,24 @@
+
+
+
+ Exe
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/PlatformSanityChecks/Program.cs b/samples/PlatformSanityChecks/Program.cs
new file mode 100644
index 0000000000..8a3aa82981
--- /dev/null
+++ b/samples/PlatformSanityChecks/Program.cs
@@ -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().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 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();
+
+ }
+
+ }
+}
diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj
new file mode 100644
index 0000000000..a035febe5b
--- /dev/null
+++ b/src/Avalonia.X11/Avalonia.X11.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
diff --git a/src/Avalonia.X11/X11Exception.cs b/src/Avalonia.X11/X11Exception.cs
new file mode 100644
index 0000000000..2ac5a31d9b
--- /dev/null
+++ b/src/Avalonia.X11/X11Exception.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Avalonia.X11
+{
+ public class X11Exception : Exception
+ {
+ public X11Exception(string message) : base(message)
+ {
+
+ }
+ }
+}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
new file mode 100644
index 0000000000..c266433eef
--- /dev/null
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -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().ToConstant(new X11PlatformThreading(Display));
+
+ }
+
+ public IntPtr DeferredDisplay { get; set; }
+ public IntPtr Display { get; set; }
+ }
+}
+
+namespace Avalonia
+{
+ public static class AvaloniaX11PlatformExtensions
+ {
+ public static T UseX11(this T builder) where T : AppBuilderBase, new()
+ {
+ builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize());
+ return builder;
+ }
+ }
+
+}
diff --git a/src/Avalonia.X11/X11PlatformThreading.cs b/src/Avalonia.X11/X11PlatformThreading.cs
new file mode 100644
index 0000000000..18b08085e3
--- /dev/null
+++ b/src/Avalonia.X11/X11PlatformThreading.cs
@@ -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 _timers = new List();
+
+ 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();
+ 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 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;
+ }
+ }
+}
diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs
new file mode 100644
index 0000000000..88d36091ca
--- /dev/null
+++ b/src/Avalonia.X11/XLib.cs
@@ -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];
+ }
+ }
+}