浏览代码

Implemented support for running on top of fbdev and libevdev2

Nikita Tsukanov 8 年之前
父节点
当前提交
70abfca7d0

+ 46 - 1
Avalonia.sln

@@ -1,6 +1,6 @@
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26228.9
+VisualStudioVersion = 15.0.26228.4
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -185,6 +185,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6F
 		build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets
 	EndProjectSection
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}"
+EndProject
 Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@@ -2503,6 +2507,46 @@ Global
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|Mono.Build.0 = Release|Any CPU
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|x86.ActiveCfg = Release|Any CPU
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Release|x86.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.AppStore|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Mono.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Mono.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|x86.Build.0 = Debug|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhone.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Mono.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Mono.Build.0 = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.ActiveCfg = Release|Any CPU
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2561,5 +2605,6 @@ Global
 		{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A} = {74487168-7D91-487E-BF93-055F2251461E}
 		{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
+		{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 	EndGlobalSection
 EndGlobal

+ 1 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -7,6 +7,7 @@
 
   <ItemGroup>
     <ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
+    <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
 

+ 8 - 3
samples/ControlCatalog.NetCore/Program.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Linq;
 using Avalonia;
 
 namespace ControlCatalog.NetCore
@@ -7,9 +8,13 @@ namespace ControlCatalog.NetCore
     {
         static void Main(string[] args)
         {
-            AppBuilder.Configure<App>()
-                .UsePlatformDetect()
-                .Start<MainWindow>();
+            if (args.Contains("--fbdev"))
+                AppBuilder.Configure<App>()
+                    .InitializeWithLinuxFramebuffer(tl => tl.Content = new MainView());
+            else
+                AppBuilder.Configure<App>()
+                    .UsePlatformDetect()
+                    .Start<MainWindow>();
         }
     }
 }

+ 14 - 0
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFramework>netstandard1.3</TargetFramework>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    </PropertyGroup>
+    <ItemGroup>
+      <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
+      <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
+      <ProjectReference Include="..\..\Skia\Avalonia.Skia.Desktop.NetStandard\Avalonia.Skia.Desktop.NetStandard.csproj" />
+    </ItemGroup>
+</Project>

+ 89 - 0
src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs

@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class EvDevDevice
+    {
+        private static readonly Lazy<List<EvDevDevice>> AllMouseDevices = new Lazy<List<EvDevDevice>>(()
+            => OpenMouseDevices());
+
+        private static List<EvDevDevice> OpenMouseDevices()
+        {
+            var rv = new List<EvDevDevice>();
+            foreach (var dev in Directory.GetFiles("/dev/input", "event*").Select(Open))
+            {
+                if (!dev.IsMouse)
+                    NativeUnsafeMethods.close(dev.Fd);
+                else
+                    rv.Add(dev);
+            }
+            return rv;
+        }
+
+        public static IReadOnlyList<EvDevDevice> MouseDevices => AllMouseDevices.Value;
+
+        
+        public int Fd { get; }
+        private IntPtr _dev;
+        public string Name { get; }
+        public List<EvType> EventTypes { get; private set; } = new List<EvType>();
+        public input_absinfo? AbsX { get; }
+        public input_absinfo? AbsY { get; }
+
+        public EvDevDevice(int fd, IntPtr dev)
+        {
+            Fd = fd;
+            _dev = dev;
+            Name = Marshal.PtrToStringAnsi(NativeUnsafeMethods.libevdev_get_name(_dev));
+            foreach (EvType type in Enum.GetValues(typeof(EvType)))
+            {
+                if (NativeUnsafeMethods.libevdev_has_event_type(dev, type) != 0)
+                    EventTypes.Add(type);
+            }
+            var ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int) AbsAxis.ABS_X);
+            if (ptr != null)
+                AbsX = *ptr;
+            ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int)AbsAxis.ABS_Y);
+            if (ptr != null)
+                AbsY = *ptr;
+        }
+
+        public input_event? NextEvent()
+        {
+            input_event ev;
+            if (NativeUnsafeMethods.libevdev_next_event(_dev, 2, out ev) == 0)
+                return ev;
+            return null;
+        }
+
+        public bool IsMouse => EventTypes.Contains(EvType.EV_REL);
+
+        public static EvDevDevice Open(string device)
+        {
+            var fd = NativeUnsafeMethods.open(device, 2048, 0);
+            if (fd <= 0)
+                throw new Exception($"Unable to open {device} code {Marshal.GetLastWin32Error()}");
+            IntPtr dev;
+            var rc = NativeUnsafeMethods.libevdev_new_from_fd(fd, out dev);
+            if (rc < 0)
+            {
+                NativeUnsafeMethods.close(fd);
+                throw new Exception($"Unable to initialize evdev for {device} code {Marshal.GetLastWin32Error()}");
+            }
+            return new EvDevDevice(fd, dev);
+        }
+
+
+    }
+
+    public class EvDevAxisInfo
+    {
+        public int Minimum { get; set; }
+        public int Maximum { get; set; }
+    }
+}

+ 68 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -0,0 +1,68 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class FramebufferToplevelImpl : IEmbeddableWindowImpl
+    {
+        private readonly LinuxFramebuffer _fb;
+        private bool _renderQueued;
+        public IInputRoot InputRoot { get; private set; }
+
+        public FramebufferToplevelImpl(LinuxFramebuffer fb)
+        {
+            _fb = fb;
+            Invalidate(default(Rect));
+            var mice = new Mice(ClientSize.Width, ClientSize.Height);
+            mice.Start();
+            mice.Event += e => Input?.Invoke(e);
+        }
+
+        public void Dispose()
+        {
+            throw new NotSupportedException();
+        }
+
+        
+        public void Invalidate(Rect rect)
+        {
+            if(_renderQueued)
+                return;
+            _renderQueued = true;
+            Dispatcher.UIThread.InvokeAsync(() =>
+            {
+                Paint?.Invoke(new Rect(default(Point), ClientSize));
+                _renderQueued = false;
+            });
+        }
+
+        public void SetInputRoot(IInputRoot inputRoot)
+        {
+            InputRoot = inputRoot;
+        }
+
+        public Point PointToClient(Point point) => point;
+
+        public Point PointToScreen(Point point) => point;
+
+        public void SetCursor(IPlatformHandle cursor)
+        {
+        }
+
+        public Size ClientSize => _fb.PixelSize;
+        public double Scaling => 1;
+        public IEnumerable<object> Surfaces => new object[] {_fb};
+        public Action<RawInputEventArgs> Input { get; set; }
+        public Action<Rect> Paint { get; set; }
+        public Action<Size> Resized { get; set; }
+        public Action<double> ScalingChanged { get; set; }
+        public Action Closed { get; set; }
+        public event Action LostFocus;
+    }
+}

+ 138 - 0
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs

@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
+    {
+        private readonly Size _dpi;
+        private int _fd;
+        private fb_fix_screeninfo _fixedInfo;
+        private fb_var_screeninfo _varInfo;
+        private IntPtr _mappedLength;
+        private IntPtr _mappedAddress;
+
+        public LinuxFramebuffer(string fileName = null, Size? dpi = null)
+        {
+            _dpi = dpi ?? new Size(96, 96);
+            fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
+            _fd = NativeUnsafeMethods.open(fileName, 2, 0);
+            if (_fd <= 0)
+                throw new Exception("Error: " + Marshal.GetLastWin32Error());
+
+            try
+            {
+                Init();
+            }
+            catch
+            {
+                Dispose();
+                throw;
+            }
+        }
+
+        void Init()
+        {
+            fixed (void* pnfo = &_varInfo)
+            {
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                SetBpp();
+
+                _varInfo.yoffset = 100;
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
+                    _varInfo.transp = new fb_bitfield();
+
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOPUT_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+                if (_varInfo.bits_per_pixel != 32)
+                    throw new Exception("Unable to set 32-bit display mode");
+            }
+            fixed(void*pnfo = &_fixedInfo)
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo))
+                    throw new Exception("FBIOGET_FSCREENINFO error: " + Marshal.GetLastWin32Error());
+
+            _mappedLength = new IntPtr(_fixedInfo.line_length * _varInfo.yres);
+            _mappedAddress = NativeUnsafeMethods.mmap(IntPtr.Zero, _mappedLength, 3, 1, _fd, IntPtr.Zero);
+            if (_mappedAddress == new IntPtr(-1))
+                throw new Exception($"Unable to mmap {_mappedLength} bytes, error {Marshal.GetLastWin32Error()}");
+            fixed (fb_fix_screeninfo* pnfo = &_fixedInfo)
+            {
+                int idlen;
+                for (idlen = 0; idlen < 16 && pnfo->id[idlen] != 0; idlen++) ;
+                Id = Encoding.ASCII.GetString(pnfo->id, idlen);
+            }
+        }
+
+        void SetBpp()
+        {
+            _varInfo.bits_per_pixel = 32;
+            _varInfo.grayscale = 0;
+            _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
+            {
+                length = 8
+            };
+            _varInfo.green.offset = 8;
+            _varInfo.blue.offset = 16;
+            _varInfo.transp.offset = 24;
+        }
+
+        public string Id { get; private set; }
+
+        public Size PixelSize
+        {
+            get
+            {
+                fb_var_screeninfo nfo;
+                if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, &nfo))
+                    throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
+                return new Size(nfo.xres, nfo.yres);
+            }
+        }
+
+        public ILockedFramebuffer Lock()
+        {
+            if (_fd <= 0)
+                throw new ObjectDisposedException("LinuxFramebuffer");
+            return new LockedFramebuffer(_fd, _fixedInfo, _varInfo, _mappedAddress, _dpi);
+        }
+
+
+        private void ReleaseUnmanagedResources()
+        {
+            if (_mappedAddress != IntPtr.Zero)
+            {
+                NativeUnsafeMethods.munmap(_mappedAddress, _mappedLength);
+                _mappedAddress = IntPtr.Zero;
+            }
+            if(_fd == 0)
+                return;
+            NativeUnsafeMethods.close(_fd);
+            _fd = 0;
+        }
+
+        public void Dispose()
+        {
+            ReleaseUnmanagedResources();
+            GC.SuppressFinalize(this);
+        }
+
+        ~LinuxFramebuffer()
+        {
+            ReleaseUnmanagedResources();
+        }
+    }
+}

+ 75 - 0
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.LinuxFramebuffer;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Threading;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class LinuxFramebufferPlatform
+    {
+        LinuxFramebuffer _fb;
+        public static KeyboardDevice KeyboardDevice = new KeyboardDevice();
+        public static MouseDevice MouseDevice = new MouseDevice();
+        private static readonly Stopwatch St = Stopwatch.StartNew();
+        internal static uint Timestamp => (uint)St.ElapsedTicks;
+        public static FramebufferToplevelImpl TopLevel;
+        LinuxFramebufferPlatform(string fbdev = null)
+        {
+            _fb = new LinuxFramebuffer(fbdev);
+        }
+
+
+        void Initialize()
+        {
+            AvaloniaLocator.CurrentMutable
+                .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
+                .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
+                .Bind<IMouseDevice>().ToConstant(MouseDevice)
+                .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
+                .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
+                .Bind<IRenderLoop>().ToConstant(PlatformThreadingInterface.Instance);
+        }
+
+        internal static TopLevel Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()
+        {
+            var platform = new LinuxFramebufferPlatform(fbdev);
+            builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev")
+                .SetupWithoutStarting();
+            var tl = new EmbeddableControlRoot(TopLevel = new FramebufferToplevelImpl(platform._fb));
+            tl.Prepare();
+            return tl;
+        }
+    }
+}
+
+public static class LinuxFramebufferPlatformExtensions
+{
+    class TokenClosable : ICloseable
+    {
+        public event EventHandler Closed;
+
+        public TokenClosable(CancellationToken token)
+        {
+            token.Register(() => Dispatcher.UIThread.InvokeAsync(() => Closed?.Invoke(this, new EventArgs())));
+        }
+    }
+
+    public static void InitializeWithLinuxFramebuffer<T>(this T builder, Action<TopLevel> setup,
+        CancellationToken stop = default(CancellationToken), string fbdev = null)
+        where T : AppBuilderBase<T>, new()
+    {
+        setup(LinuxFramebufferPlatform.Initialize(builder, fbdev));
+        builder.BeforeStartCallback(builder);
+        builder.Instance.Run(new TokenClosable(stop));
+    }
+}
+

+ 47 - 0
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class LockedFramebuffer : ILockedFramebuffer
+    {
+        private readonly int _fb;
+        private readonly fb_fix_screeninfo _fixedInfo;
+        private fb_var_screeninfo _varInfo;
+        private readonly IntPtr _address;
+
+        public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi)
+        {
+            _fb = fb;
+            _fixedInfo = fixedInfo;
+            _varInfo = varInfo;
+            _address = address;
+            Dpi = dpi;
+            //Use double buffering to avoid flicker
+            Address = Marshal.AllocHGlobal(RowBytes * Height);
+        }
+
+
+        void VSync()
+        {
+            NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null);
+        }
+
+        public void Dispose()
+        {
+            VSync();
+            NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Height));
+
+            Marshal.FreeHGlobal(Address);
+            Address = IntPtr.Zero;
+        }
+
+        public IntPtr Address { get; private set; }
+        public int Width => (int)_varInfo.xres;
+        public int Height => (int) _varInfo.yres;
+        public int RowBytes => (int) _fixedInfo.line_length;
+        public Size Dpi { get; }
+        public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
+    }
+}

+ 120 - 0
src/Linux/Avalonia.LinuxFramebuffer/Mice.cs

@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    public unsafe class Mice
+    {
+        private readonly double _width;
+        private readonly double _height;
+        private double _x;
+        private double _y;
+
+        public event Action<RawInputEventArgs> Event;
+
+        public Mice(double width, double height)
+        {
+            _width = width;
+            _height = height;
+        }
+
+        public void Start() => AvaloniaLocator.Current.GetService<IRuntimePlatform>().PostThreadPoolItem(Worker);
+
+        private void Worker()
+        {
+
+            var mouseDevices = EvDevDevice.MouseDevices.Where(d => d.IsMouse).ToList();
+            if (mouseDevices.Count == 0)
+                return;
+            var are = new AutoResetEvent(false);
+            while (true)
+            {
+                try
+                {
+                    var rfds = new fd_set {count = mouseDevices.Count};
+                    for (int c = 0; c < mouseDevices.Count; c++)
+                        rfds.fds[c] = mouseDevices[c].Fd;
+                    IntPtr* timeval = stackalloc IntPtr[2];
+                    timeval[0] = new IntPtr(0);
+                    timeval[1] = new IntPtr(100);
+                    are.WaitOne(30);
+                    foreach (var dev in mouseDevices)
+                    {
+                        while(true)
+                        {
+                            var ev = dev.NextEvent();
+                            if (!ev.HasValue)
+                                break;
+
+                            PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value));
+                        } 
+                    }
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e.ToString());
+                }
+            }
+        }
+
+        static double TranslateAxis(input_absinfo axis, int value, double max)
+        {
+            return (value - axis.minimum) / (double) (axis.maximum - axis.minimum) * max;
+        }
+
+        private void ProcessEvent(EvDevDevice device, input_event ev)
+        {
+            if (ev.type == (short)EvType.EV_REL)
+            {
+                if (ev.code == (short) AxisEventCode.REL_X)
+                    _x = Math.Min(_width, Math.Max(0, _x + ev.value));
+                else if (ev.code == (short) AxisEventCode.REL_Y)
+                    _y = Math.Min(_height, Math.Max(0, _y + ev.value));
+                else
+                    return;
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+                    InputModifiers.None));
+            }
+            if (ev.type ==(int) EvType.EV_ABS)
+            {
+                if (ev.code == (short) AbsAxis.ABS_X && device.AbsX.HasValue)
+                    _x = TranslateAxis(device.AbsX.Value, ev.value, _width);
+                else if (ev.code == (short) AbsAxis.ABS_Y && device.AbsY.HasValue)
+                    _y = TranslateAxis(device.AbsY.Value, ev.value, _height);
+                else
+                    return;
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
+                    InputModifiers.None));
+            }
+            if (ev.type == (short) EvType.EV_KEY)
+            {
+                RawMouseEventType? type = null;
+                if (ev.code == (ushort) EvKey.BTN_LEFT)
+                    type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
+                if (ev.code == (ushort)EvKey.BTN_RIGHT)
+                    type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
+                if (ev.code == (ushort) EvKey.BTN_MIDDLE)
+                    type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
+                if (!type.HasValue)
+                    return;
+
+                Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
+                    LinuxFramebufferPlatform.Timestamp,
+                    LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
+            }
+        }
+    }
+}

+ 254 - 0
src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs

@@ -0,0 +1,254 @@
+using __u32 = System.UInt32;
+using __s32 = System.Int32;
+using __u16 = System.UInt16;
+using System;
+using System.Runtime.InteropServices;
+// ReSharper disable FieldCanBeMadeReadOnly.Local
+// ReSharper disable ArrangeTypeMemberModifiers
+// ReSharper disable BuiltInTypeReferenceStyle
+// ReSharper disable InconsistentNaming
+
+namespace Avalonia.LinuxFramebuffer
+{
+    unsafe class NativeUnsafeMethods
+    {
+        [DllImport("libc", EntryPoint = "open", SetLastError = true)]
+        public static extern int open(string pathname, int flags, int mode);
+
+        [DllImport("libc", EntryPoint = "close", SetLastError = true)]
+        public static extern int close(int fd);
+
+        [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
+        public static extern int ioctl(int fd, FbIoCtl code, void* arg);
+
+        [DllImport("libc", EntryPoint = "mmap", SetLastError = true)]
+        public static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags,
+                  int fd, IntPtr offset);
+        [DllImport("libc", EntryPoint = "munmap", SetLastError = true)]
+        public static extern int munmap(IntPtr addr, IntPtr length);
+
+        [DllImport("libc", EntryPoint = "memcpy", SetLastError = true)]
+        public static extern int memcpy(IntPtr dest, IntPtr src, IntPtr length);
+
+        [DllImport("libc", EntryPoint = "select", SetLastError = true)]
+        public static extern int select(int nfds, void* rfds, void* wfds, void* exfds, IntPtr* timevals);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_new_from_fd", SetLastError = true)]
+        public static extern int libevdev_new_from_fd(int fd, out IntPtr dev);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_has_event_type", SetLastError = true)]
+        public static extern int libevdev_has_event_type(IntPtr dev, EvType type);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_next_event", SetLastError = true)]
+        public static extern int libevdev_next_event(IntPtr dev, int flags, out input_event ev);
+
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_get_name", SetLastError = true)]
+        public static extern IntPtr libevdev_get_name(IntPtr dev);
+        [DllImport("libevdev.so.2", EntryPoint = "libevdev_get_abs_info", SetLastError = true)]
+        public static extern input_absinfo* libevdev_get_abs_info(IntPtr dev, int code);
+    }
+
+    enum FbIoCtl : uint
+    {
+        FBIOGET_VSCREENINFO = 0x4600,
+        FBIOPUT_VSCREENINFO = 0x4601,
+        FBIOGET_FSCREENINFO = 0x4602,
+        FBIOGET_VBLANK = 0x80204612u,
+        FBIO_WAITFORVSYNC = 0x40044620,
+        FBIOPAN_DISPLAY = 0x4606
+    }
+
+    [Flags]
+    enum VBlankFlags
+    {
+        FB_VBLANK_VBLANKING = 0x001 /* currently in a vertical blank */,
+        FB_VBLANK_HBLANKING = 0x002 /* currently in a horizontal blank */,
+        FB_VBLANK_HAVE_VBLANK = 0x004 /* vertical blanks can be detected */,
+        FB_VBLANK_HAVE_HBLANK = 0x008 /* horizontal blanks can be detected */,
+        FB_VBLANK_HAVE_COUNT = 0x010 /* global retrace counter is available */,
+        FB_VBLANK_HAVE_VCOUNT = 0x020 /* the vcount field is valid */,
+        FB_VBLANK_HAVE_HCOUNT = 0x040 /* the hcount field is valid */,
+        FB_VBLANK_VSYNCING = 0x080 /* currently in a vsync */,
+        FB_VBLANK_HAVE_VSYNC = 0x100 /* verical syncs can be detected */
+    }
+
+    unsafe struct fb_vblank {
+        public VBlankFlags flags;			/* FB_VBLANK flags */
+        __u32 count;			/* counter of retraces since boot */
+        __u32 vcount;			/* current scanline position */
+        __u32 hcount;			/* current scandot position */
+        fixed __u32 reserved[4];		/* reserved for future compatibility */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fb_fix_screeninfo
+    {
+        public fixed byte id[16]; /* identification string eg "TT Builtin" */
+
+        public IntPtr smem_start; /* Start of frame buffer mem */
+
+        /* (physical address) */
+        public __u32 smem_len; /* Length of frame buffer mem */
+
+        public __u32 type; /* see FB_TYPE_*		*/
+        public __u32 type_aux; /* Interleave for interleaved Planes */
+        public __u32 visual; /* see FB_VISUAL_*		*/
+        public __u16 xpanstep; /* zero if no hardware panning  */
+        public __u16 ypanstep; /* zero if no hardware panning  */
+        public __u16 ywrapstep; /* zero if no hardware ywrap    */
+        public __u32 line_length; /* length of a line in bytes    */
+
+        public IntPtr mmio_start; /* Start of Memory Mapped I/O   */
+
+        /* (physical address) */
+        public __u32 mmio_len; /* Length of Memory Mapped I/O  */
+
+        public __u32 accel; /* Type of acceleration available */
+        public fixed __u16 reserved[3]; /* Reserved for future compatibility */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct fb_bitfield
+    {
+        public __u32 offset; /* beginning of bitfield	*/
+        public __u32 length; /* length of bitfield		*/
+
+        public __u32 msb_right; /* != 0 : Most significant bit is */
+        /* right */
+    };
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fb_var_screeninfo
+    {
+        public __u32 xres; /* visible resolution		*/
+        public __u32 yres;
+        public __u32 xres_virtual; /* virtual resolution		*/
+        public __u32 yres_virtual;
+        public __u32 xoffset; /* offset from virtual to visible */
+        public __u32 yoffset; /* resolution			*/
+
+        public __u32 bits_per_pixel; /* guess what			*/
+        public __u32 grayscale; /* != 0 Graylevels instead of colors */
+
+        public fb_bitfield red; /* bitfield in fb mem if true color, */
+        public fb_bitfield green; /* else only length is significant */
+        public fb_bitfield blue;
+        public fb_bitfield transp; /* transparency			*/
+
+        public __u32 nonstd; /* != 0 Non standard pixel format */
+
+        public __u32 activate; /* see FB_ACTIVATE_*		*/
+
+        public __u32 height; /* height of picture in mm    */
+        public __u32 width; /* width of picture in mm     */
+
+        public __u32 accel_flags; /* acceleration flags (hints)	*/
+
+        /* Timing: All values in pixclocks, except pixclock (of course) */
+        public __u32 pixclock; /* pixel clock in ps (pico seconds) */
+
+        public __u32 left_margin; /* time from sync to picture	*/
+        public __u32 right_margin; /* time from picture to sync	*/
+        public __u32 upper_margin; /* time from sync to picture	*/
+        public __u32 lower_margin;
+        public __u32 hsync_len; /* length of horizontal sync	*/
+        public __u32 vsync_len; /* length of vertical sync	*/
+        public __u32 sync; /* see FB_SYNC_*		*/
+        public __u32 vmode; /* see FB_VMODE_*		*/
+        public fixed __u32 reserved[6]; /* Reserved for future compatibility */
+    };
+
+
+    enum EvType
+    {
+        EV_SYN = 0x00,
+        EV_KEY = 0x01,
+        EV_REL = 0x02,
+        EV_ABS = 0x03,
+        EV_MSC = 0x04,
+        EV_SW = 0x05,
+        EV_LED = 0x11,
+        EV_SND = 0x12,
+        EV_REP = 0x14,
+        EV_FF = 0x15,
+        EV_PWR = 0x16,
+        EV_FF_STATUS = 0x17,
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct input_event
+    {
+        private IntPtr crap1, crap2;
+        public ushort type, code;
+        public int value;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    unsafe struct fd_set
+    {
+        public int count;
+        public fixed int fds [256];
+    }
+
+    enum AxisEventCode
+    {
+        REL_X = 0x00,
+        REL_Y = 0x01,
+        REL_Z = 0x02,
+        REL_RX = 0x03,
+        REL_RY = 0x04,
+        REL_RZ = 0x05,
+        REL_HWHEEL = 0x06,
+        REL_DIAL = 0x07,
+        REL_WHEEL = 0x08,
+        REL_MISC = 0x09,
+        REL_MAX = 0x0f
+    }
+
+    enum AbsAxis
+    {
+        ABS_X = 0x00,
+        ABS_Y = 0x01,
+        ABS_Z = 0x02,
+        ABS_RX = 0x03,
+        ABS_RY = 0x04,
+        ABS_RZ = 0x05,
+        ABS_THROTTLE = 0x06,
+        ABS_RUDDER = 0x07,
+        ABS_WHEEL = 0x08,
+        ABS_GAS = 0x09,
+        ABS_BRAKE = 0x0a,
+        ABS_HAT0X = 0x10,
+        ABS_HAT0Y = 0x11,
+        ABS_HAT1X = 0x12,
+        ABS_HAT1Y = 0x13,
+        ABS_HAT2X = 0x14,
+        ABS_HAT2Y = 0x15,
+        ABS_HAT3X = 0x16,
+        ABS_HAT3Y = 0x17,
+        ABS_PRESSURE = 0x18,
+        ABS_DISTANCE = 0x19,
+        ABS_TILT_X = 0x1a,
+        ABS_TILT_Y = 0x1b,
+        ABS_TOOL_WIDTH = 0x1c
+    }
+
+    enum EvKey
+    {
+        BTN_LEFT = 0x110,
+        BTN_RIGHT = 0x111,
+        BTN_MIDDLE = 0x112
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct input_absinfo
+    {
+        public __s32 value;
+        public __s32 minimum;
+        public __s32 maximum;
+        public __s32 fuzz;
+        public __s32 flat;
+        public __s32 resolution;
+
+    }
+}

+ 112 - 0
src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs

@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop
+    {
+        public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
+
+        public PlatformThreadingInterface()
+        {
+            TlsCurrentThreadIsLoopThread = true;
+            StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
+        }
+
+        private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
+        private readonly AutoResetEvent _queued = new AutoResetEvent(false);
+
+        private readonly Queue<Action> _actions = new Queue<Action>();
+
+        public void RunLoop(CancellationToken cancellationToken)
+        {
+            var handles = new[] {_signaled, _queued};
+            while (true)
+            {
+                if (0 == WaitHandle.WaitAny(handles))
+                    Signaled?.Invoke();
+                else
+                {
+                    while (true)
+                    {
+                        Action item;
+                        lock(_actions)
+                            if (_actions.Count == 0)
+                                break;
+                            else
+                                item = _actions.Dequeue();
+                        item();
+                    }
+                }
+            }
+        }
+
+        public void Send(Action cb)
+        {
+            lock (_actions)
+            {
+                _actions.Enqueue(cb);
+                _queued.Set();
+            }
+        }
+
+        class WatTimer : IDisposable
+        {
+            private readonly IDisposable _timer;
+            private GCHandle _handle;
+
+            public WatTimer(IDisposable timer)
+            {
+                _timer = timer;
+                _handle = GCHandle.Alloc(_timer);
+            }
+            public void Dispose()
+            {
+                _handle.Free();
+                _timer.Dispose();
+            }
+        }
+
+        public IDisposable StartTimer(TimeSpan interval, Action tick)
+        {
+            return new WatTimer(new System.Threading.Timer(delegate
+            {
+                var tcs = new TaskCompletionSource<int>();
+                Send(() =>
+                {
+                    try
+                    {
+                        tick();
+                    }
+                    finally
+                    {
+                        tcs.SetResult(0);
+                    }
+                });
+
+
+                tcs.Task.Wait();
+            }, null, TimeSpan.Zero, interval));
+
+
+        }
+
+        public void Signal()
+        {
+            _signaled.Set();
+        }
+
+        [ThreadStatic] private static bool TlsCurrentThreadIsLoopThread;
+
+        public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
+        public event Action Signaled;
+        public event EventHandler<EventArgs> Tick;
+
+    }
+}

+ 21 - 0
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.LinuxFramebuffer
+{
+    internal class CursorFactoryStub : IStandardCursorFactory
+    {
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+        {
+            return new PlatformHandle(IntPtr.Zero, null);
+        }
+    }
+    internal class PlatformSettings : IPlatformSettings
+    {
+        public Size DoubleClickSize { get; } = new Size(4, 4);
+        public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
+    }
+}