Răsfoiți Sursa

Merge pull request #2764 from AvaloniaUI/libinput

Improvements for fbdev backend
Nikita Tsukanov 6 ani în urmă
părinte
comite
8fc29d96ee
23 a modificat fișierele cu 1240 adăugiri și 349 ștergeri
  1. 15 6
      samples/ControlCatalog.NetCore/Program.cs
  2. 30 55
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  3. 1 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  4. 2 1
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  5. 8 0
      src/Avalonia.Input/TouchDevice.cs
  6. 4 4
      src/Avalonia.OpenGL/EglContext.cs
  7. 59 35
      src/Avalonia.OpenGL/EglDisplay.cs
  8. 5 0
      src/Avalonia.OpenGL/EglInterface.cs
  9. 1 1
      src/Avalonia.OpenGL/GlInterface.cs
  10. 0 88
      src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs
  11. 19 19
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  12. 12 0
      src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs
  13. 7 0
      src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs
  14. 183 0
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  15. 139 0
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
  16. 30 16
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  17. 0 117
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  18. 12 1
      src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs
  19. 292 0
      src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
  20. 158 0
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  21. 250 0
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  22. 6 5
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  23. 7 0
      src/Linux/Avalonia.LinuxFramebuffer/Output/IOutputBackend.cs

+ 15 - 6
samples/ControlCatalog.NetCore/Program.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Threading;
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.LinuxFramebuffer.Output;
 using Avalonia.Skia;
 using Avalonia.ReactiveUI;
 
@@ -29,8 +30,13 @@ namespace ControlCatalog.NetCore
             var builder = BuildAvaloniaApp();
             if (args.Contains("--fbdev"))
             {
-                System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
-                return builder.StartLinuxFramebuffer(args);
+                SilenceConsole();
+                return builder.StartLinuxFbDev(args);
+            }
+            else if (args.Contains("--drm"))
+            {
+                SilenceConsole();
+                return builder.StartLinuxDrm(args);
             }
             else
                 return builder.StartWithClassicDesktopLifetime(args);
@@ -51,11 +57,14 @@ namespace ControlCatalog.NetCore
                 .UseSkia()
                 .UseReactiveUI();
 
-        static void ConsoleSilencer()
+        static void SilenceConsole()
         {
-            Console.CursorVisible = false;
-            while (true)
-                Console.ReadKey(true);
+            new Thread(() =>
+            {
+                Console.CursorVisible = false;
+                while (true)
+                    Console.ReadKey(true);
+            }) {IsBackground = true}.Start();
         }
     }
 }

+ 30 - 55
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@@ -9,94 +9,69 @@ using Avalonia.Threading;
 
 namespace Avalonia.Controls.Platform
 {
-    public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer
+    public class InternalPlatformThreadingInterface : IPlatformThreadingInterface
     {
         public InternalPlatformThreadingInterface()
         {
             TlsCurrentThreadIsLoopThread = true;
-            StartTimer(
-                DispatcherPriority.Render,
-                new TimeSpan(0, 0, 0, 0, 66),
-                () => Tick?.Invoke(TimeSpan.FromMilliseconds(Environment.TickCount)));
         }
 
         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(null);
-                else
-                {
-                    while (true)
-                    {
-                        Action item;
-                        lock (_actions)
-                            if (_actions.Count == 0)
-                                break;
-                            else
-                                item = _actions.Dequeue();
-                        item();
-                    }
-                }
+                Signaled?.Invoke(null);
+                _signaled.WaitOne();
             }
         }
 
-        public void Send(Action cb)
-        {
-            lock (_actions)
-            {
-                _actions.Enqueue(cb);
-                _queued.Set();
-            }
-        }
 
-        class WatTimer : IDisposable
+        class TimerImpl : IDisposable
         {
-            private readonly IDisposable _timer;
+            private readonly DispatcherPriority _priority;
+            private readonly TimeSpan _interval;
+            private readonly Action _tick;
+            private Timer _timer;
             private GCHandle _handle;
 
-            public WatTimer(IDisposable timer)
+            public TimerImpl(DispatcherPriority priority, TimeSpan interval, Action tick)
             {
-                _timer = timer;
+                _priority = priority;
+                _interval = interval;
+                _tick = tick;
+                _timer = new Timer(OnTimer, null, interval, TimeSpan.FromMilliseconds(-1));
                 _handle = GCHandle.Alloc(_timer);
             }
 
+            private void OnTimer(object state)
+            {
+                if (_timer == null)
+                    return;
+                Dispatcher.UIThread.Post(() =>
+                {
+                    
+                    if (_timer == null)
+                        return;
+                    _tick();
+                    _timer?.Change(_interval, TimeSpan.FromMilliseconds(-1));
+                });
+            }
+
+
             public void Dispose()
             {
                 _handle.Free();
                 _timer.Dispose();
+                _timer = null;
             }
         }
 
         public IDisposable StartTimer(DispatcherPriority priority, 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));
-
-
+            return new TimerImpl(priority, interval, tick);
         }
 
         public void Signal(DispatcherPriority prio)

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@@ -54,7 +54,7 @@ namespace Avalonia.DesignerSupport.Remote
                 .Bind<IPlatformSettings>().ToConstant(instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(threading)
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
-                .Bind<IRenderTimer>().ToConstant(threading)
+                .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>()
                 .Bind<IWindowingPlatform>().ToConstant(instance)
                 .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()

+ 2 - 1
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@@ -19,7 +19,8 @@ namespace Avalonia.Input.Raw
         NonClientLeftButtonDown,
         TouchBegin,
         TouchUpdate,
-        TouchEnd
+        TouchEnd,
+        TouchCancel
     }
 
     /// <summary>

+ 8 - 0
src/Avalonia.Input/TouchDevice.cs

@@ -61,6 +61,12 @@ namespace Avalonia.Input
                         pointer.IsPrimary ? MouseButton.Left : MouseButton.None));
                 }
             }
+            if (args.Type == RawPointerEventType.TouchCancel)
+            {
+                _pointers.Remove(args.TouchPointId);
+                using (pointer)
+                    pointer.Capture(null);
+            }
 
             if (args.Type == RawPointerEventType.TouchUpdate)
             {
@@ -68,6 +74,8 @@ namespace Avalonia.Input
                 target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
                     args.Position, ev.Timestamp, new PointerPointProperties(modifiers), modifiers));
             }
+
+            
         }
         
     }

+ 4 - 4
src/Avalonia.OpenGL/EglContext.cs

@@ -10,7 +10,7 @@ namespace Avalonia.OpenGL
         private readonly EglInterface _egl;
         private readonly object _lock = new object();
 
-        public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, IntPtr offscreenSurface)
+        public EglContext(EglDisplay display, EglInterface egl, IntPtr ctx, EglSurface offscreenSurface)
         {
             _disp = display;
             _egl = egl;
@@ -19,7 +19,7 @@ namespace Avalonia.OpenGL
         }
 
         public IntPtr Context { get; }
-        public IntPtr OffscreenSurface { get; }
+        public EglSurface OffscreenSurface { get; }
         public IGlDisplay Display => _disp;
 
         public IDisposable Lock()
@@ -36,8 +36,8 @@ namespace Avalonia.OpenGL
         
         public void MakeCurrent(EglSurface surface)
         {
-            var surf = surface?.DangerousGetHandle() ?? OffscreenSurface;
-            if (!_egl.MakeCurrent(_disp.Handle, surf, surf, Context))
+            var surf = surface ?? OffscreenSurface;
+            if (!_egl.MakeCurrent(_disp.Handle, surf.DangerousGetHandle(), surf.DangerousGetHandle(), Context))
                 throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
         }
     }

+ 59 - 35
src/Avalonia.OpenGL/EglDisplay.cs

@@ -12,49 +12,62 @@ namespace Avalonia.OpenGL
         private readonly IntPtr _display;
         private readonly IntPtr _config;
         private readonly int[] _contextAttributes;
+        private readonly int _surfaceType;
 
         public IntPtr Handle => _display;
         private AngleOptions.PlatformApi? _angleApi;
-        public EglDisplay(EglInterface egl)
+
+        public EglDisplay(EglInterface egl) : this(egl, -1, IntPtr.Zero, null)
+        {
+            
+        }
+        public EglDisplay(EglInterface egl, int platformType, IntPtr platformDisplay, int[] attrs)
         {
-            _egl = egl;  
+            _egl = egl;
 
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            if (platformType == -1 && platformDisplay == IntPtr.Zero)
             {
-                if (_egl.GetPlatformDisplayEXT == null)
-                    throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
-                
-                var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
-                              ?? new List<AngleOptions.PlatformApi> {AngleOptions.PlatformApi.DirectX9};              
-                
-                foreach (var platformApi in allowedApis)
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                 {
-                    int dapi;
-                    if (platformApi == AngleOptions.PlatformApi.DirectX9)
-                        dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
-                    else if (platformApi == AngleOptions.PlatformApi.DirectX11)
-                        dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
-                    else 
-                        continue;
-                    
-                    _display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero, new[]
-                    {
-                        EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE
-                    });
-                    if (_display != IntPtr.Zero)
+                    if (_egl.GetPlatformDisplayEXT == null)
+                        throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
+
+                    var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
+                                      ?? new List<AngleOptions.PlatformApi> {AngleOptions.PlatformApi.DirectX9};
+
+                    foreach (var platformApi in allowedApis)
                     {
-                        _angleApi = platformApi;
-                        break;
+                        int dapi;
+                        if (platformApi == AngleOptions.PlatformApi.DirectX9)
+                            dapi = EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE;
+                        else if (platformApi == AngleOptions.PlatformApi.DirectX11)
+                            dapi = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
+                        else
+                            continue;
+
+                        _display = _egl.GetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, IntPtr.Zero,
+                            new[] {EGL_PLATFORM_ANGLE_TYPE_ANGLE, dapi, EGL_NONE});
+                        if (_display != IntPtr.Zero)
+                        {
+                            _angleApi = platformApi;
+                            break;
+                        }
                     }
+
+                    if (_display == IntPtr.Zero)
+                        throw new OpenGlException("Unable to create ANGLE display");
                 }
 
                 if (_display == IntPtr.Zero)
-                    throw new OpenGlException("Unable to create ANGLE display");
+                    _display = _egl.GetDisplay(IntPtr.Zero);
+            }
+            else
+            {
+                if (_egl.GetPlatformDisplayEXT == null)
+                    throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl");
+                _display = _egl.GetPlatformDisplayEXT(platformType, platformDisplay, attrs);
             }
 
-            if (_display == IntPtr.Zero)
-                _display = _egl.GetDisplay(IntPtr.Zero);
-            
             if (_display == IntPtr.Zero)
                 throw OpenGlException.GetFormattedException("eglGetDisplay", _egl);
 
@@ -85,16 +98,14 @@ namespace Avalonia.OpenGL
             {
                 if (!_egl.BindApi(cfg.Api))
                     continue;
-
+                foreach(var surfaceType in new[]{EGL_PBUFFER_BIT|EGL_WINDOW_BIT, EGL_WINDOW_BIT})
                 foreach(var stencilSize in new[]{8, 1, 0})
                 foreach (var depthSize in new []{8, 1, 0})
                 {
                     var attribs = new[]
                     {
-                        EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
-
+                        EGL_SURFACE_TYPE, surfaceType,
                         EGL_RENDERABLE_TYPE, cfg.RenderableTypeBit,
-
                         EGL_RED_SIZE, 8,
                         EGL_GREEN_SIZE, 8,
                         EGL_BLUE_SIZE, 8,
@@ -108,6 +119,7 @@ namespace Avalonia.OpenGL
                     if (numConfigs == 0)
                         continue;
                     _contextAttributes = cfg.Attributes;
+                    _surfaceType = surfaceType;
                     Type = cfg.Type;
                 }
             }
@@ -126,8 +138,10 @@ namespace Avalonia.OpenGL
         public GlDisplayType Type { get; }
         public GlInterface GlInterface { get; }
         public EglInterface EglInterface => _egl;
-        public IGlContext CreateContext(IGlContext share)
+        public EglContext CreateContext(IGlContext share)
         {
+            if((_surfaceType|EGL_PBUFFER_BIT) == 0)
+                throw new InvalidOperationException("Platform doesn't support PBUFFER surfaces");
             var shareCtx = (EglContext)share;
             var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
             if (ctx == IntPtr.Zero)
@@ -140,7 +154,17 @@ namespace Avalonia.OpenGL
             });
             if (surf == IntPtr.Zero)
                 throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
-            var rv = new EglContext(this, _egl, ctx, surf);
+            var rv = new EglContext(this, _egl, ctx, new EglSurface(this, _egl, surf));
+            rv.MakeCurrent(null);
+            return rv;
+        }
+
+        public EglContext CreateContext(EglContext share, EglSurface offscreenSurface)
+        {
+            var ctx = _egl.CreateContext(_display, _config, share?.Context ?? IntPtr.Zero, _contextAttributes);
+            if (ctx == IntPtr.Zero)
+                throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
+            var rv = new EglContext(this, _egl, ctx, offscreenSurface);
             rv.MakeCurrent(null);
             return rv;
         }

+ 5 - 0
src/Avalonia.OpenGL/EglInterface.cs

@@ -10,6 +10,11 @@ namespace Avalonia.OpenGL
         public EglInterface() : base(Load())
         {
             
+        }
+
+        public EglInterface(Func<Utf8Buffer,IntPtr> getProcAddress) : base(getProcAddress)
+        {
+            
         }
         
         public EglInterface(string library) : base(Load(library))

+ 1 - 1
src/Avalonia.OpenGL/GlInterface.cs

@@ -39,7 +39,7 @@ namespace Avalonia.OpenGL
         [GlEntryPoint("glClearStencil")]
         public GlClearStencil ClearStencil { get; }
 
-        public delegate void GlClearColor(int r, int g, int b, int a);
+        public delegate void GlClearColor(float r, float g, float b, float a);
         [GlEntryPoint("glClearColor")]
         public GlClearColor ClearColor { get; }
 

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

@@ -1,88 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-
-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; }
-    }
-}

+ 19 - 19
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -2,30 +2,35 @@
 using System.Collections.Generic;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
+using Avalonia.LinuxFramebuffer.Input;
+using Avalonia.LinuxFramebuffer.Output;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Threading;
 
 namespace Avalonia.LinuxFramebuffer
 {
-    class FramebufferToplevelImpl : IEmbeddableWindowImpl
+    class FramebufferToplevelImpl : IEmbeddableWindowImpl, IScreenInfoProvider
     {
-        private readonly LinuxFramebuffer _fb;
+        private readonly IOutputBackend _outputBackend;
+        private readonly IInputBackend _inputBackend;
         private bool _renderQueued;
         public IInputRoot InputRoot { get; private set; }
 
-        public FramebufferToplevelImpl(LinuxFramebuffer fb)
+        public FramebufferToplevelImpl(IOutputBackend outputBackend, IInputBackend inputBackend)
         {
-            _fb = fb;
+            _outputBackend = outputBackend;
+            _inputBackend = inputBackend;
             Invalidate(default(Rect));
-            var mice = new Mice(this, ClientSize.Width, ClientSize.Height);
-            mice.Start();
-            mice.Event += e => Input?.Invoke(e);
+            _inputBackend.Initialize(this, e => Input?.Invoke(e));
         }
 
         public IRenderer CreateRenderer(IRenderRoot root)
         {
-            return new ImmediateRenderer(root);
+            return new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>())
+            {
+                
+            };
         }
 
         public void Dispose()
@@ -36,19 +41,12 @@ namespace Avalonia.LinuxFramebuffer
         
         public void Invalidate(Rect rect)
         {
-            if(_renderQueued)
-                return;
-            _renderQueued = true;
-            Dispatcher.UIThread.Post(() =>
-            {
-                Paint?.Invoke(new Rect(default(Point), ClientSize));
-                _renderQueued = false;
-            });
         }
 
         public void SetInputRoot(IInputRoot inputRoot)
         {
             InputRoot = inputRoot;
+            _inputBackend.SetInputRoot(inputRoot);
         }
 
         public Point PointToClient(PixelPoint p) => p.ToPoint(1);
@@ -59,10 +57,10 @@ namespace Avalonia.LinuxFramebuffer
         {
         }
 
-        public Size ClientSize => _fb.PixelSize;
-        public IMouseDevice MouseDevice => LinuxFramebufferPlatform.MouseDevice;
+        public Size ClientSize => ScaledSize;
+        public IMouseDevice MouseDevice => new MouseDevice();
         public double Scaling => 1;
-        public IEnumerable<object> Surfaces => new object[] {_fb};
+        public IEnumerable<object> Surfaces => new object[] {_outputBackend};
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }
@@ -73,5 +71,7 @@ namespace Avalonia.LinuxFramebuffer
             add {}
             remove {}
         }
+
+        public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
     }
 }

+ 12 - 0
src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs

@@ -0,0 +1,12 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+
+namespace Avalonia.LinuxFramebuffer.Input
+{
+    public interface IInputBackend
+    {
+        void Initialize(IScreenInfoProvider info, Action<RawInputEventArgs> onInput);
+        void SetInputRoot(IInputRoot root);
+    }
+}

+ 7 - 0
src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs

@@ -0,0 +1,7 @@
+namespace Avalonia.LinuxFramebuffer.Input
+{
+    public interface IScreenInfoProvider
+    {
+        Size ScaledSize { get; }
+    }
+}

+ 183 - 0
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs

@@ -0,0 +1,183 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Threading;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Threading;
+using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; 
+namespace Avalonia.LinuxFramebuffer.Input.LibInput
+{
+    public class LibInputBackend : IInputBackend
+    {
+        private IScreenInfoProvider _screen;
+        private IInputRoot _inputRoot;
+        private readonly Queue<Action> _inputThreadActions = new Queue<Action>();
+        private TouchDevice _touch = new TouchDevice();
+        private MouseDevice _mouse = new MouseDevice();
+        private Point _mousePosition;
+        
+        private readonly Queue<RawInputEventArgs> _inputQueue = new Queue<RawInputEventArgs>();
+        private Action<RawInputEventArgs> _onInput;
+        private Dictionary<int, Point> _pointers = new Dictionary<int, Point>();
+
+        public LibInputBackend()
+        {
+            var ctx = libinput_path_create_context();
+            
+            new Thread(()=>InputThread(ctx)).Start();
+        }
+
+        
+        
+        private unsafe void InputThread(IntPtr ctx)
+        {
+            var fd = libinput_get_fd(ctx);
+            
+            var timeval = stackalloc IntPtr[2];
+
+
+            foreach (var f in Directory.GetFiles("/dev/input", "event*"))
+                libinput_path_add_device(ctx, f);
+            while (true)
+            {
+                
+                IntPtr ev;
+                libinput_dispatch(ctx);
+                while ((ev = libinput_get_event(ctx)) != IntPtr.Zero)
+                {
+                    
+                    var type = libinput_event_get_type(ev);
+                    if (type >= LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN &&
+                        type <= LibInputEventType.LIBINPUT_EVENT_TOUCH_CANCEL)
+                        HandleTouch(ev, type);
+
+                    if (type >= LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION
+                        && type <= LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS)
+                        HandlePointer(ev, type);
+                    
+                    libinput_event_destroy(ev);
+                    libinput_dispatch(ctx);
+                }
+
+                pollfd pfd = new pollfd {fd = fd, events = 1};
+                NativeUnsafeMethods.poll(&pfd, new IntPtr(1), 10);
+            }
+        }
+
+        private void ScheduleInput(RawInputEventArgs ev)
+        {
+            lock (_inputQueue)
+            {
+                _inputQueue.Enqueue(ev);
+                if (_inputQueue.Count == 1)
+                {
+                    Dispatcher.UIThread.Post(() =>
+                    {
+                        while (true)
+                        {
+                            Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
+                            RawInputEventArgs dequeuedEvent = null;
+                            lock(_inputQueue)
+                                if (_inputQueue.Count != 0)
+                                    dequeuedEvent = _inputQueue.Dequeue();
+                            if (dequeuedEvent == null)
+                                return;
+                            _onInput?.Invoke(dequeuedEvent);
+                        }
+                    }, DispatcherPriority.Input);
+                }
+            }
+        }
+
+        private void HandleTouch(IntPtr ev, LibInputEventType type)
+        {
+            var tev = libinput_event_get_touch_event(ev);
+            if(tev == IntPtr.Zero)
+                return;
+            if (type < LibInputEventType.LIBINPUT_EVENT_TOUCH_FRAME)
+            {
+                var info = _screen.ScaledSize;
+                var slot = libinput_event_touch_get_slot(tev);
+                Point pt;
+
+                if (type == LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN
+                    || type == LibInputEventType.LIBINPUT_EVENT_TOUCH_MOTION)
+                {
+                    var x = libinput_event_touch_get_x_transformed(tev, (int)info.Width);
+                    var y = libinput_event_touch_get_y_transformed(tev, (int)info.Height);
+                    pt = new Point(x, y);
+                    _pointers[slot] = pt;
+                }
+                else
+                {
+                    _pointers.TryGetValue(slot, out pt);
+                    _pointers.Remove(slot);
+                }
+
+                var ts = libinput_event_touch_get_time_usec(tev) / 1000;
+                if (_inputRoot == null)
+                    return;
+                ScheduleInput(new RawTouchEventArgs(_touch, ts,
+                    _inputRoot,
+                    type == LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN ? RawPointerEventType.TouchBegin
+                    : type == LibInputEventType.LIBINPUT_EVENT_TOUCH_UP ? RawPointerEventType.TouchEnd
+                    : type == LibInputEventType.LIBINPUT_EVENT_TOUCH_MOTION ? RawPointerEventType.TouchUpdate
+                    : RawPointerEventType.TouchCancel,
+                    pt, InputModifiers.None, slot));
+            }
+        }
+
+        private void HandlePointer(IntPtr ev, LibInputEventType type)
+        {
+            //TODO: support input modifiers
+            var pev = libinput_event_get_pointer_event(ev);
+            var info = _screen.ScaledSize;
+            var ts = libinput_event_pointer_get_time_usec(pev) / 1000;
+            if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE)
+            {
+                _mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width),
+                    libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height));
+                ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition,
+                    InputModifiers.None));
+            }
+            else if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON)
+            {
+                var button = (EvKey)libinput_event_pointer_get_button(pev);
+                var buttonState = libinput_event_pointer_get_button_state(pev);
+
+
+                var evnt = button == EvKey.BTN_LEFT ?
+                    (buttonState == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) :
+                    button == EvKey.BTN_MIDDLE ?
+                        (buttonState == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) :
+                        button == EvKey.BTN_RIGHT ?
+                            (buttonState == 1 ?
+                                RawPointerEventType.RightButtonDown :
+                                RawPointerEventType.RightButtonUp) :
+                            (RawPointerEventType)(-1);
+                if (evnt == (RawPointerEventType)(-1))
+                    return;
+                        
+
+                ScheduleInput(
+                    new RawPointerEventArgs(_mouse, ts, _inputRoot, evnt, _mousePosition, InputModifiers.None));
+            }
+            
+        }
+            
+        
+
+        public void Initialize(IScreenInfoProvider screen, Action<RawInputEventArgs> onInput)
+        {
+            _screen = screen;
+            _onInput = onInput;
+        }
+
+        public void SetInputRoot(IInputRoot root)
+        {
+            _inputRoot = root;
+        }
+    }
+}

+ 139 - 0
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs

@@ -0,0 +1,139 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.LinuxFramebuffer.Input.LibInput
+{
+    unsafe class LibInputNativeUnsafeMethods
+    {
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        delegate int OpenRestrictedCallbackDelegate(IntPtr path, int flags, IntPtr userData);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        delegate void CloseRestrictedCallbackDelegate(int fd, IntPtr userData);
+
+        static int OpenRestricted(IntPtr path, int flags, IntPtr userData)
+        {
+            var fd = NativeUnsafeMethods.open(Marshal.PtrToStringAnsi(path), flags, 0);
+            if (fd == -1)
+                return -Marshal.GetLastWin32Error();
+
+            return fd;
+        }
+
+        static void CloseRestricted(int fd, IntPtr userData)
+        {
+            NativeUnsafeMethods.close(fd);
+        }
+
+        private static readonly IntPtr* s_Interface;
+
+        static LibInputNativeUnsafeMethods()
+        {
+            s_Interface = (IntPtr*)Marshal.AllocHGlobal(IntPtr.Size * 2);
+
+            IntPtr Convert<TDelegate>(TDelegate del)
+            {
+                GCHandle.Alloc(del);
+                return Marshal.GetFunctionPointerForDelegate(del);
+            }
+
+            s_Interface[0] = Convert<OpenRestrictedCallbackDelegate>(OpenRestricted);
+            s_Interface[1] = Convert<CloseRestrictedCallbackDelegate>(CloseRestricted);
+        }
+
+        private const string LibInput = "libinput.so.10";
+        
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_path_create_context(IntPtr* iface, IntPtr userData);
+
+        public static IntPtr libinput_path_create_context() =>
+            libinput_path_create_context(s_Interface, IntPtr.Zero);
+
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_path_add_device(IntPtr ctx, [MarshalAs(UnmanagedType.LPStr)] string path);
+        
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_path_remove_device(IntPtr device);
+        
+        [DllImport(LibInput)]
+        public extern static int libinput_get_fd(IntPtr ctx);
+        
+        [DllImport(LibInput)]
+        public extern static void libinput_dispatch(IntPtr ctx);
+        
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_get_event(IntPtr ctx);
+        
+        [DllImport(LibInput)]
+        public extern static LibInputEventType libinput_event_get_type(IntPtr ev);
+
+        public enum LibInputEventType
+        {
+            LIBINPUT_EVENT_NONE = 0,
+            LIBINPUT_EVENT_DEVICE_ADDED,
+            LIBINPUT_EVENT_DEVICE_REMOVED,
+            LIBINPUT_EVENT_KEYBOARD_KEY = 300,
+            LIBINPUT_EVENT_POINTER_MOTION = 400,
+            LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
+            LIBINPUT_EVENT_POINTER_BUTTON,
+            LIBINPUT_EVENT_POINTER_AXIS,
+            LIBINPUT_EVENT_TOUCH_DOWN = 500,
+            LIBINPUT_EVENT_TOUCH_UP,
+            LIBINPUT_EVENT_TOUCH_MOTION,
+            LIBINPUT_EVENT_TOUCH_CANCEL,
+            LIBINPUT_EVENT_TOUCH_FRAME,
+            LIBINPUT_EVENT_TABLET_TOOL_AXIS = 600,
+            LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY,
+            LIBINPUT_EVENT_TABLET_TOOL_TIP,
+            LIBINPUT_EVENT_TABLET_TOOL_BUTTON,
+            LIBINPUT_EVENT_TABLET_PAD_BUTTON = 700,
+            LIBINPUT_EVENT_TABLET_PAD_RING,
+            LIBINPUT_EVENT_TABLET_PAD_STRIP,
+            LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN = 800,
+            LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE,
+            LIBINPUT_EVENT_GESTURE_SWIPE_END,
+            LIBINPUT_EVENT_GESTURE_PINCH_BEGIN,
+            LIBINPUT_EVENT_GESTURE_PINCH_UPDATE,
+            LIBINPUT_EVENT_GESTURE_PINCH_END,
+            LIBINPUT_EVENT_SWITCH_TOGGLE = 900,
+        }
+        
+        
+        [DllImport(LibInput)]
+        public extern static void libinput_event_destroy(IntPtr ev);
+        
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_event_get_touch_event(IntPtr ev);
+        
+        [DllImport(LibInput)]
+        public extern static int libinput_event_touch_get_slot(IntPtr ev);
+        
+        [DllImport(LibInput)]
+        public extern static ulong libinput_event_touch_get_time_usec(IntPtr ev);
+
+        [DllImport(LibInput)]
+        public extern static double libinput_event_touch_get_x_transformed(IntPtr ev, int width);
+        
+        [DllImport(LibInput)]
+        public extern static double libinput_event_touch_get_y_transformed(IntPtr ev, int height);
+        
+        [DllImport(LibInput)]
+        public extern static IntPtr libinput_event_get_pointer_event(IntPtr ev);
+        
+        
+        [DllImport(LibInput)]
+        public extern static ulong libinput_event_pointer_get_time_usec(IntPtr ev);
+        
+        [DllImport(LibInput)]
+        public extern static double libinput_event_pointer_get_absolute_x_transformed(IntPtr ev, int width);
+        
+        [DllImport(LibInput)]
+        public extern static double libinput_event_pointer_get_absolute_y_transformed(IntPtr ev, int height);
+        
+        [DllImport(LibInput)]
+        public extern static int libinput_event_pointer_get_button(IntPtr ev);
+        
+        [DllImport(LibInput)]
+        public extern static int libinput_event_pointer_get_button_state(IntPtr ev);
+    }
+}

+ 30 - 16
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@@ -8,6 +8,9 @@ using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Input.Platform;
 using Avalonia.LinuxFramebuffer;
+using Avalonia.LinuxFramebuffer.Input.LibInput;
+using Avalonia.LinuxFramebuffer.Output;
+using Avalonia.OpenGL;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Threading;
@@ -16,34 +19,37 @@ namespace Avalonia.LinuxFramebuffer
 {
     class LinuxFramebufferPlatform
     {
-        LinuxFramebuffer _fb;
-        public static KeyboardDevice KeyboardDevice = new KeyboardDevice();
-        public static MouseDevice MouseDevice = new MouseDevice();
+        IOutputBackend _fb;
         private static readonly Stopwatch St = Stopwatch.StartNew();
         internal static uint Timestamp => (uint)St.ElapsedTicks;
         public static InternalPlatformThreadingInterface Threading;
-        LinuxFramebufferPlatform(string fbdev = null)
+        LinuxFramebufferPlatform(IOutputBackend backend)
         {
-            _fb = new LinuxFramebuffer(fbdev);
+            _fb = backend;
         }
 
 
         void Initialize()
         {
             Threading = new InternalPlatformThreadingInterface();
+            if (_fb is IWindowingPlatformGlFeature glFeature)
+                AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(glFeature);
             AvaloniaLocator.CurrentMutable
+                .Bind<IPlatformThreadingInterface>().ToConstant(Threading)
+                .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
+                .Bind<IRenderLoop>().ToConstant(new RenderLoop())
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
-                .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
+                .Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
                 .Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
-                .Bind<IPlatformThreadingInterface>().ToConstant(Threading)
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
-                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
-                .Bind<IRenderTimer>().ToConstant(Threading);
+                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
+
         }
 
-        internal static LinuxFramebufferLifetime Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new()
+       
+        internal static LinuxFramebufferLifetime Initialize<T>(T builder, IOutputBackend outputBackend) where T : AppBuilderBase<T>, new()
         {
-            var platform = new LinuxFramebufferPlatform(fbdev);
+            var platform = new LinuxFramebufferPlatform(outputBackend);
             builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev");
             return new LinuxFramebufferLifetime(platform._fb);
         }
@@ -51,12 +57,12 @@ namespace Avalonia.LinuxFramebuffer
 
     class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime
     {
-        private readonly LinuxFramebuffer _fb;
+        private readonly IOutputBackend _fb;
         private TopLevel _topLevel;
         private readonly CancellationTokenSource _cts = new CancellationTokenSource();
         public CancellationToken Token => _cts.Token;
 
-        public LinuxFramebufferLifetime(LinuxFramebuffer fb)
+        public LinuxFramebufferLifetime(IOutputBackend fb)
         {
             _fb = fb;
         }
@@ -69,10 +75,12 @@ namespace Avalonia.LinuxFramebuffer
                 if (_topLevel == null)
                 {
 
-                    var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb));
+                    var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, new LibInputBackend()));
                     tl.Prepare();
                     _topLevel = tl;
+                    _topLevel.Renderer.Start();
                 }
+
                 _topLevel.Content = value;
             }
         }
@@ -99,10 +107,16 @@ namespace Avalonia.LinuxFramebuffer
 
 public static class LinuxFramebufferPlatformExtensions
 {
-    public static int StartLinuxFramebuffer<T>(this T builder, string[] args, string fbdev = null)
+    public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev = null)
+        where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new FbdevOutput(fbdev));
+
+    public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null)
+        where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card));
+    
+    public static int StartLinuxDirect<T>(this T builder, string[] args, IOutputBackend backend)
         where T : AppBuilderBase<T>, new()
     {
-        var lifetime = LinuxFramebufferPlatform.Initialize(builder, fbdev);
+        var lifetime = LinuxFramebufferPlatform.Initialize(builder, backend);
         builder.Instance.ApplicationLifetime = lifetime;
         builder.SetupWithoutStarting();
         lifetime.Start(args);

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

@@ -1,117 +0,0 @@
-using System;
-using System.Linq;
-using System.Threading;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Platform;
-
-namespace Avalonia.LinuxFramebuffer
-{
-    unsafe class Mice
-    {
-        private readonly FramebufferToplevelImpl _topLevel;
-        private readonly double _width;
-        private readonly double _height;
-        private double _x;
-        private double _y;
-
-        public event Action<RawInputEventArgs> Event;
-
-        public Mice(FramebufferToplevelImpl topLevel, double width, double height)
-        {
-            _topLevel = topLevel;
-            _width = width;
-            _height = height;
-        }
-
-        public void Start() => ThreadPool.UnsafeQueueUserWorkItem(_ => Worker(), null);
-
-        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;
-
-                            LinuxFramebufferPlatform.Threading.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 RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
-                    LinuxFramebufferPlatform.Timestamp,
-                    _topLevel.InputRoot, RawPointerEventType.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 RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
-                    LinuxFramebufferPlatform.Timestamp,
-                    _topLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
-                    InputModifiers.None));
-            }
-            if (ev.type == (short) EvType.EV_KEY)
-            {
-                RawPointerEventType? type = null;
-                if (ev.code == (ushort) EvKey.BTN_LEFT)
-                    type = ev.value == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
-                if (ev.code == (ushort)EvKey.BTN_RIGHT)
-                    type = ev.value == 1 ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
-                if (ev.code == (ushort) EvKey.BTN_MIDDLE)
-                    type = ev.value == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
-                if (!type.HasValue)
-                    return;
-
-                Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
-                    LinuxFramebufferPlatform.Timestamp,
-                    _topLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
-            }
-        }
-    }
-}

+ 12 - 1
src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs

@@ -33,6 +33,10 @@ namespace Avalonia.LinuxFramebuffer
         [DllImport("libc", EntryPoint = "select", SetLastError = true)]
         public static extern int select(int nfds, void* rfds, void* wfds, void* exfds, IntPtr* timevals);
 
+
+        [DllImport("libc", EntryPoint = "poll", SetLastError = true)]
+        public static extern int poll(pollfd* fds, IntPtr nfds, int timeout);
+
         [DllImport("libevdev.so.2", EntryPoint = "libevdev_new_from_fd", SetLastError = true)]
         public static extern int libevdev_new_from_fd(int fd, out IntPtr dev);
 
@@ -48,6 +52,13 @@ namespace Avalonia.LinuxFramebuffer
         public static extern input_absinfo* libevdev_get_abs_info(IntPtr dev, int code);
     }
 
+    [StructLayout(LayoutKind.Sequential)]
+    struct pollfd {
+        public int   fd;         /* file descriptor */
+        public short events;     /* requested events */
+        public short revents;    /* returned events */
+    };
+    
     enum FbIoCtl : uint
     {
         FBIOGET_VSCREENINFO = 0x4600,
@@ -188,7 +199,7 @@ namespace Avalonia.LinuxFramebuffer
     unsafe struct fd_set
     {
         public int count;
-        public fixed int fds [256];
+        public fixed byte fds [256];
     }
 
     enum AxisEventCode

+ 292 - 0
src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs

@@ -0,0 +1,292 @@
+using System;
+using System.Runtime.InteropServices;
+// ReSharper disable FieldCanBeMadeReadOnly.Global
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable FieldCanBeMadeReadOnly.Local
+
+namespace Avalonia.LinuxFramebuffer.Output
+{
+    public enum DrmModeConnection
+    {
+        DRM_MODE_CONNECTED = 1,
+        DRM_MODE_DISCONNECTED = 2,
+        DRM_MODE_UNKNOWNCONNECTION = 3
+    }
+        
+    public enum DrmModeSubPixel{
+        DRM_MODE_SUBPIXEL_UNKNOWN        = 1,
+        DRM_MODE_SUBPIXEL_HORIZONTAL_RGB = 2,
+        DRM_MODE_SUBPIXEL_HORIZONTAL_BGR = 3,
+        DRM_MODE_SUBPIXEL_VERTICAL_RGB   = 4,
+        DRM_MODE_SUBPIXEL_VERTICAL_BGR   = 5,
+        DRM_MODE_SUBPIXEL_NONE           = 6
+    }
+    
+    static unsafe class LibDrm
+    {
+        private const string libdrm = "libdrm.so.2";
+        private const string libgbm = "libgbm.so.1";
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public unsafe delegate void DrmEventVBlankHandlerDelegate(int fd,
+            uint sequence,
+            uint tv_sec,
+            uint tv_usec,
+            void* user_data);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public unsafe delegate void DrmEventPageFlipHandlerDelegate(int fd,
+            uint sequence,
+            uint tv_sec,
+            uint tv_usec,
+            void* user_data);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public unsafe delegate IntPtr DrmEventPageFlipHandler2Delegate(int fd,
+            uint sequence,
+            uint tv_sec,
+            uint tv_usec,
+            uint crtc_id,
+            void* user_data);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public unsafe delegate void DrmEventSequenceHandlerDelegate(int fd,
+            ulong sequence,
+            ulong ns,
+            ulong user_data);
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct DrmEventContext
+        {
+            public int version; //4
+            public IntPtr vblank_handler;
+            public IntPtr page_flip_handler;
+            public IntPtr page_flip_handler2;
+            public IntPtr sequence_handler;
+        }
+        
+        [StructLayout(LayoutKind.Sequential)]
+        public struct drmModeRes {
+
+            public int count_fbs;
+            public uint *fbs;
+
+            public int count_crtcs;
+            public uint *crtcs;
+
+            public int count_connectors;
+            public uint *connectors;
+
+            public int count_encoders;
+            public uint *encoders;
+
+            uint min_width, max_width;
+            uint min_height, max_height;
+        }
+
+        [Flags]
+        public enum DrmModeType
+        {
+            DRM_MODE_TYPE_BUILTIN = (1 << 0),
+            DRM_MODE_TYPE_CLOCK_C = ((1 << 1) | DRM_MODE_TYPE_BUILTIN),
+            DRM_MODE_TYPE_CRTC_C = ((1 << 2) | DRM_MODE_TYPE_BUILTIN),
+            DRM_MODE_TYPE_PREFERRED = (1 << 3),
+            DRM_MODE_TYPE_DEFAULT = (1 << 4),
+            DRM_MODE_TYPE_USERDEF = (1 << 5),
+            DRM_MODE_TYPE_DRIVER = (1 << 6)
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct drmModeModeInfo
+        {
+            public uint clock;
+            public ushort hdisplay, hsync_start, hsync_end, htotal, hskew;
+            public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan;
+
+            public uint vrefresh;
+
+            public uint flags;
+            public DrmModeType type;
+            public fixed byte name[32];
+            public PixelSize Resolution => new PixelSize(hdisplay, vdisplay);
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct drmModeConnector {
+            public uint connector_id;
+            public uint encoder_id; /**< Encoder currently connected to */
+            public uint connector_type;
+            public uint connector_type_id;
+            public DrmModeConnection connection;
+            public uint mmWidth, mmHeight; /**< HxW in millimeters */
+            public DrmModeSubPixel subpixel;
+
+            public int count_modes;
+            public drmModeModeInfo* modes;
+
+            public int count_props;
+            public uint *props; /**< List of property ids */
+            public ulong *prop_values; /**< List of property values */
+
+            public int count_encoders;
+            public uint *encoders; /**< List of encoder ids */
+        }
+        
+        [StructLayout(LayoutKind.Sequential)]
+        public struct drmModeEncoder {
+            public uint encoder_id;
+            public uint encoder_type;
+            public uint crtc_id;
+            public uint possible_crtcs;
+            public uint possible_clones;
+        }
+        
+        [StructLayout(LayoutKind.Sequential)]
+        public struct drmModeCrtc {
+            public uint crtc_id;
+            public uint buffer_id; /**< FB id to connect to 0 = disconnect */
+
+            public uint x, y; /**< Position on the framebuffer */
+            public uint width, height;
+            public int mode_valid;
+            public drmModeModeInfo mode;
+
+            public int gamma_size; /**< Number of gamma stops */
+
+        }
+        
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern drmModeRes* drmModeGetResources(int fd);
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModeFreeResources(drmModeRes* res);
+
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern drmModeConnector* drmModeGetConnector(int fd, uint connector);
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModeFreeConnector(drmModeConnector* res);
+        
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern drmModeEncoder* drmModeGetEncoder(int fd, uint id);
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModeFreeEncoder(drmModeEncoder* enc);
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern drmModeCrtc* drmModeGetCrtc(int fd, uint id);
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModeFreeCrtc(drmModeCrtc* enc);
+
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern int drmModeAddFB(int fd, uint width, uint height, byte depth,
+            byte bpp, uint pitch, uint bo_handle,
+            out uint buf_id); 
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern int drmModeSetCrtc(int fd, uint crtcId, uint bufferId,
+            uint x, uint y, uint *connectors, int count,
+            drmModeModeInfo* mode);
+        
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModeRmFB(int fd, int id);
+
+        [Flags]
+        public enum DrmModePageFlip
+        {
+            Event = 1,
+            Async = 2,
+            Absolute = 4,
+            Relative = 8,
+        }
+
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmModePageFlip(int fd, uint crtc_id, uint fb_id,
+            DrmModePageFlip flags, void *user_data);
+
+
+        [DllImport(libdrm, SetLastError = true)]
+        public static extern void drmHandleEvent(int fd, DrmEventContext* context);
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern IntPtr gbm_create_device(int fd);
+        
+        
+        [Flags]
+        public enum GbmBoFlags {
+            /**
+             * Buffer is going to be presented to the screen using an API such as KMS
+             */
+            GBM_BO_USE_SCANOUT      = (1 << 0),
+            /**
+             * Buffer is going to be used as cursor
+             */
+            GBM_BO_USE_CURSOR       = (1 << 1),
+            /**
+             * Deprecated
+             */
+            GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR,
+            /**
+             * Buffer is to be used for rendering - for example it is going to be used
+             * as the storage for a color buffer
+             */
+            GBM_BO_USE_RENDERING    = (1 << 2),
+            /**
+             * Buffer can be used for gbm_bo_write.  This is guaranteed to work
+             * with GBM_BO_USE_CURSOR, but may not work for other combinations.
+             */
+            GBM_BO_USE_WRITE    = (1 << 3),
+            /**
+             * Buffer is linear, i.e. not tiled.
+             */
+            GBM_BO_USE_LINEAR = (1 << 4),
+        };
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern IntPtr gbm_surface_create(IntPtr device, int width, int height, uint format, GbmBoFlags flags);
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern IntPtr gbm_surface_lock_front_buffer(IntPtr surface);
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern int gbm_surface_release_buffer(IntPtr surface, IntPtr bo);
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern IntPtr gbm_bo_get_user_data(IntPtr surface);
+
+        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+        public delegate void GbmBoUserDataDestroyCallbackDelegate(IntPtr bo, IntPtr data);
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern IntPtr gbm_bo_set_user_data(IntPtr bo, IntPtr userData,
+            GbmBoUserDataDestroyCallbackDelegate onFree);
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern uint gbm_bo_get_width(IntPtr bo);
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern uint gbm_bo_get_height(IntPtr bo);
+
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern uint gbm_bo_get_stride(IntPtr bo);
+
+
+        [StructLayout(LayoutKind.Explicit)]
+        public struct GbmBoHandle
+        {
+            [FieldOffset(0)]
+            public void *ptr;
+            [FieldOffset(0)]
+            public int s32;
+            [FieldOffset(0)]
+            public uint u32;
+            [FieldOffset(0)]
+            public long s64;
+            [FieldOffset(0)]
+            public ulong u64;
+        }
+        
+        [DllImport(libgbm, SetLastError = true)]
+        public static extern ulong gbm_bo_get_handle(IntPtr bo);
+
+        public static  class GbmColorFormats
+        {
+            public static uint FourCC(char a, char b, char c, char d) =>
+                (uint)a | ((uint)b) << 8 | ((uint)c) << 16 | ((uint)d) << 24;
+
+            public static uint GBM_FORMAT_XRGB8888 { get; } = FourCC('X', 'R', '2', '4');
+        }
+    }
+    
+}

+ 158 - 0
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
+using static Avalonia.LinuxFramebuffer.Output.LibDrm;
+
+namespace Avalonia.LinuxFramebuffer.Output
+{
+    public unsafe class DrmConnector
+    {
+        private static string[] KnownConnectorTypes =
+        {
+            "None", "VGA", "DVI-I", "DVI-D", "DVI-A", "Composite", "S-Video", "LVDS", "Component", "DIN",
+            "DisplayPort", "HDMI-A", "HDMI-B", "TV", "eDP", "Virtual", "DSI"
+        };
+
+        public DrmModeConnection Connection { get; }
+        public uint Id { get; }
+        public string Name { get; }
+        public Size SizeMm { get; }
+        public DrmModeSubPixel SubPixel { get; }
+        internal uint EncoderId { get; }
+        internal List<uint> EncoderIds { get; } = new List<uint>();
+        public List<DrmModeInfo> Modes { get; } = new List<DrmModeInfo>();
+        internal DrmConnector(drmModeConnector* conn)
+        {
+            Connection = conn->connection;
+            Id = conn->connector_id;
+            SizeMm = new Size(conn->mmWidth, conn->mmHeight);
+            SubPixel = conn->subpixel;
+            for (var c = 0; c < conn->count_encoders;c++) 
+                EncoderIds.Add(conn->encoders[c]);
+            EncoderId = conn->encoder_id;
+            for(var c=0; c<conn->count_modes; c++)
+                Modes.Add(new DrmModeInfo(ref conn->modes[c]));
+
+            if (conn->connector_type > KnownConnectorTypes.Length - 1)
+                Name = $"Unknown({conn->connector_type})-{conn->connector_type_id}";
+            else
+                Name = KnownConnectorTypes[conn->connector_type] + "-" + conn->connector_type_id;
+        }
+    }
+
+    public unsafe class DrmModeInfo
+    {
+        internal drmModeModeInfo Mode;
+
+        internal DrmModeInfo(ref drmModeModeInfo info)
+        {
+            Mode = info;
+            fixed (void* pName = info.name)
+                Name = Marshal.PtrToStringAnsi(new IntPtr(pName));
+        }
+
+        public PixelSize Resolution => new PixelSize(Mode.hdisplay, Mode.vdisplay);
+        public bool IsPreferred => Mode.type.HasFlag(DrmModeType.DRM_MODE_TYPE_PREFERRED);
+
+        public string Name { get; }
+    }
+
+    unsafe class DrmEncoder
+    {
+        public drmModeEncoder Encoder { get; }
+        public List<drmModeCrtc> PossibleCrtcs { get; } = new List<drmModeCrtc>();
+
+        public DrmEncoder(drmModeEncoder encoder, drmModeCrtc[] crtcs)
+        {
+            Encoder = encoder;
+            for (var c = 0; c < crtcs.Length; c++)
+            {
+                var bit = 1 << c;
+                if ((encoder.possible_crtcs & bit) != 0)
+                    PossibleCrtcs.Add(crtcs[c]);
+            }
+        }
+    }
+    
+    
+    
+    public unsafe class DrmResources
+    {
+        public List<DrmConnector> Connectors { get; }= new List<DrmConnector>();
+        internal Dictionary<uint, DrmEncoder> Encoders { get; } = new Dictionary<uint, DrmEncoder>();
+        public DrmResources(int fd)
+        {
+            var res = drmModeGetResources(fd);
+            if (res == null)
+                throw new Win32Exception("drmModeGetResources failed");
+            
+            var crtcs = new drmModeCrtc[res->count_crtcs];
+            for (var c = 0; c < res->count_crtcs; c++)
+            {
+                var crtc = drmModeGetCrtc(fd, res->crtcs[c]);
+                crtcs[c] = *crtc;
+                drmModeFreeCrtc(crtc);
+            }
+            
+            for (var c = 0; c < res->count_encoders; c++)
+            {
+                var enc = drmModeGetEncoder(fd, res->encoders[c]);
+                Encoders[res->encoders[c]] = new DrmEncoder(*enc, crtcs);
+                drmModeFreeEncoder(enc);
+            }
+            
+            for (var c = 0; c < res->count_connectors; c++)
+            {
+                var conn = drmModeGetConnector(fd, res->connectors[c]);
+                Connectors.Add(new DrmConnector(conn));
+                drmModeFreeConnector(conn);
+            }
+
+
+        }
+
+        public void Dump()
+        {
+            void Print(int off, string s)
+            {
+                for (var c = 0; c < off; c++)
+                    Console.Write("    ");
+                Console.WriteLine(s);
+            }
+            Print(0, "Connectors");
+            foreach (var conn in Connectors)
+            {
+                Print(1, $"{conn.Name}:");
+                Print(2, $"Id: {conn.Id}");
+                Print(2, $"Size: {conn.SizeMm} mm");
+                Print(2, $"Encoder id: {conn.EncoderId}");
+                Print(2, "Modes");
+                foreach (var m in conn.Modes)
+                    Print(3, $"{m.Name} {(m.IsPreferred ? "PREFERRED" : "")}");
+
+
+            }
+        }
+    }
+    
+    public unsafe class DrmCard : IDisposable
+    {
+        public int Fd { get; private set; }
+        public DrmCard(string path = null)
+        {
+            path = path ?? "/dev/dri/card0";
+            Fd = open(path, 2, 0);
+            if (Fd == -1)
+                throw new Win32Exception("Couldn't open " + path);
+        }
+
+        public DrmResources GetResources() => new DrmResources(Fd);
+        public void Dispose()
+        {
+            close(Fd);
+            Fd = -1;
+        }
+    }
+}

+ 250 - 0
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.InteropServices;
+using Avalonia.OpenGL;
+using Avalonia.Platform.Interop;
+using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
+using static Avalonia.LinuxFramebuffer.Output.LibDrm;
+namespace Avalonia.LinuxFramebuffer.Output
+{
+    public unsafe class DrmOutput : IOutputBackend, IGlPlatformSurface, IWindowingPlatformGlFeature
+    {
+        private DrmCard _card;
+        private readonly EglGlPlatformSurface _eglPlatformSurface;
+        public PixelSize PixelSize => _mode.Resolution;
+
+        public DrmOutput(string path = null)
+        {
+            var card = new DrmCard(path);
+
+            var resources = card.GetResources();
+
+
+            var connector =
+                resources.Connectors.FirstOrDefault(x => x.Connection == DrmModeConnection.DRM_MODE_CONNECTED);
+            if(connector == null)
+                throw new InvalidOperationException("Unable to find connected DRM connector");
+
+            var mode = connector.Modes.OrderByDescending(x => x.IsPreferred)
+                .ThenByDescending(x => x.Resolution.Width * x.Resolution.Height)
+                //.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height)
+                .FirstOrDefault();
+            if(mode == null)
+                throw new InvalidOperationException("Unable to find a usable DRM mode");
+            Init(card, resources, connector, mode);
+        }
+
+        public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo)
+        {
+            Init(card, resources, connector, modeInfo);
+        }
+
+        [DllImport("libEGL.so.1")]
+        static extern IntPtr eglGetProcAddress(Utf8Buffer proc);
+
+        private GbmBoUserDataDestroyCallbackDelegate FbDestroyDelegate;
+        private drmModeModeInfo _mode;
+        private EglDisplay _eglDisplay;
+        private EglSurface _eglSurface;
+        private EglContext _immediateContext;
+        private EglContext _deferredContext;
+        private IntPtr _currentBo;
+        private IntPtr _gbmTargetSurface;
+        private uint _crtcId;
+
+        void FbDestroyCallback(IntPtr bo, IntPtr userData)
+        {
+            drmModeRmFB(_card.Fd, userData.ToInt32());
+        }
+
+        uint GetFbIdForBo(IntPtr bo)
+        {
+            if (bo == IntPtr.Zero)
+                throw new ArgumentException("bo is 0");
+            var data = gbm_bo_get_user_data(bo);
+            if (data != IntPtr.Zero)
+                return (uint)data.ToInt32();
+
+            var w = gbm_bo_get_width(bo); 
+            var h = gbm_bo_get_height(bo);
+            var stride = gbm_bo_get_stride(bo);
+            var handle = gbm_bo_get_handle(bo);
+
+            var ret = drmModeAddFB(_card.Fd, w, h, 24, 32, stride, (uint)handle, out var fbHandle);
+            if (ret != 0)
+                throw new Win32Exception(ret, "drmModeAddFb failed");
+
+            gbm_bo_set_user_data(bo, new IntPtr((int)fbHandle), FbDestroyDelegate);
+            
+            
+            return fbHandle;
+        }
+        
+        
+        void Init(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo)
+        {
+            FbDestroyDelegate = FbDestroyCallback;
+            _card = card;
+            uint GetCrtc()
+            {
+                if (resources.Encoders.TryGetValue(connector.EncoderId, out var encoder))
+                {
+                    // Not sure why that should work
+                    return encoder.Encoder.crtc_id;
+                }
+                else
+                {
+                    foreach (var encId in connector.EncoderIds)
+                    {
+                        if (resources.Encoders.TryGetValue(encId, out encoder)
+                            && encoder.PossibleCrtcs.Count>0)
+                            return encoder.PossibleCrtcs.First().crtc_id;
+                    }
+
+                    throw new InvalidOperationException("Unable to find CRTC matching the desired mode");
+                }
+            }
+
+            _crtcId = GetCrtc();
+            var device = gbm_create_device(card.Fd);
+            _gbmTargetSurface = gbm_surface_create(device, modeInfo.Resolution.Width, modeInfo.Resolution.Height,
+                GbmColorFormats.GBM_FORMAT_XRGB8888, GbmBoFlags.GBM_BO_USE_SCANOUT | GbmBoFlags.GBM_BO_USE_RENDERING);
+            if(_gbmTargetSurface == null)
+                throw new InvalidOperationException("Unable to create GBM surface");
+
+            
+            
+            _eglDisplay = new EglDisplay(new EglInterface(eglGetProcAddress), 0x31D7, device, null);
+            _eglSurface = _eglDisplay.CreateWindowSurface(_gbmTargetSurface);
+
+
+            EglContext CreateContext(EglContext share)
+            {
+                var offSurf = gbm_surface_create(device, 1, 1, GbmColorFormats.GBM_FORMAT_XRGB8888,
+                    GbmBoFlags.GBM_BO_USE_RENDERING);
+                if (offSurf == null)
+                    throw new InvalidOperationException("Unable to create 1x1 sized GBM surface");
+                return _eglDisplay.CreateContext(share, _eglDisplay.CreateWindowSurface(offSurf));
+            }
+            
+            _immediateContext = CreateContext(null);
+            _deferredContext = CreateContext(_immediateContext);
+            
+            _immediateContext.MakeCurrent(_eglSurface);
+            _eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
+            _eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
+            _eglSurface.SwapBuffers();
+            var bo = gbm_surface_lock_front_buffer(_gbmTargetSurface);
+            var fbId = GetFbIdForBo(bo);
+            var connectorId = connector.Id;
+            var mode = modeInfo.Mode;
+
+            
+            var res = drmModeSetCrtc(_card.Fd, _crtcId, fbId, 0, 0, &connectorId, 1, &mode);
+            if (res != 0)
+                throw new Win32Exception(res, "drmModeSetCrtc failed");
+
+            _mode = mode;
+            _currentBo = bo;
+            
+            // Go trough two cycles of buffer swapping (there are render artifacts otherwise)
+            for(var c=0;c<2;c++)
+                using (CreateGlRenderTarget().BeginDraw())
+                {
+                    _eglDisplay.GlInterface.ClearColor(0, 0, 0, 0);
+                    _eglDisplay.GlInterface.Clear(GlConsts.GL_COLOR_BUFFER_BIT | GlConsts.GL_STENCIL_BUFFER_BIT);
+                }
+        }
+
+        public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
+        {
+            return new RenderTarget(this);
+        }
+
+        class RenderTarget : IGlPlatformSurfaceRenderTarget
+        {
+            private readonly DrmOutput _parent;
+
+            public RenderTarget(DrmOutput parent)
+            {
+                _parent = parent;
+            }
+            public void Dispose()
+            {
+                // We are wrapping GBM buffer chain associated with CRTC, and don't free it on a whim
+            }
+
+            class RenderSession : IGlPlatformSurfaceRenderingSession
+            {
+                private readonly DrmOutput _parent;
+
+                public RenderSession(DrmOutput parent)
+                {
+                    _parent = parent;
+                }
+
+                public void Dispose()
+                {
+                    _parent._eglDisplay.GlInterface.Flush();
+                    _parent._eglSurface.SwapBuffers();
+                    
+                    var nextBo = gbm_surface_lock_front_buffer(_parent._gbmTargetSurface);
+                    if (nextBo == IntPtr.Zero)
+                    {
+                        // Not sure what else can be done
+                        Console.WriteLine("gbm_surface_lock_front_buffer failed");
+                    }
+                    else
+                    {
+
+                        var fb = _parent.GetFbIdForBo(nextBo);
+                        bool waitingForFlip = true;
+
+                        drmModePageFlip(_parent._card.Fd, _parent._crtcId, fb, DrmModePageFlip.Event, null);
+
+                        DrmEventPageFlipHandlerDelegate flipCb =
+                            (int fd, uint sequence, uint tv_sec, uint tv_usec, void* user_data) =>
+                            {
+                                waitingForFlip = false;
+                            };
+                        var cbHandle = GCHandle.Alloc(flipCb);
+                        var ctx = new DrmEventContext
+                        {
+                            version = 4, page_flip_handler = Marshal.GetFunctionPointerForDelegate(flipCb)
+                        };
+                        while (waitingForFlip)
+                        {
+                            var pfd = new pollfd {events = 1, fd = _parent._card.Fd};
+                            poll(&pfd, new IntPtr(1), -1);
+                            drmHandleEvent(_parent._card.Fd, &ctx);
+                        }
+
+                        cbHandle.Free();
+                        gbm_surface_release_buffer(_parent._gbmTargetSurface, _parent._currentBo);
+                        _parent._currentBo = nextBo;
+                    }
+                    _parent._eglDisplay.ClearContext();
+                }
+
+
+                public IGlDisplay Display => _parent._eglDisplay;
+
+                public PixelSize Size => _parent._mode.Resolution;
+
+                public double Scaling => 1;
+            }
+
+            public IGlPlatformSurfaceRenderingSession BeginDraw()
+            {
+                _parent._deferredContext.MakeCurrent(_parent._eglSurface);
+                return new RenderSession(_parent);
+            }
+        }
+
+        IGlContext IWindowingPlatformGlFeature.ImmediateContext => _immediateContext;
+    }
+
+
+}

+ 6 - 5
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs → src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@@ -2,11 +2,12 @@
 using System.Runtime.InteropServices;
 using System.Text;
 using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.LinuxFramebuffer.Output;
 using Avalonia.Platform;
 
 namespace Avalonia.LinuxFramebuffer
 {
-    public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable
+    public sealed unsafe class FbdevOutput : IFramebufferPlatformSurface, IDisposable, IOutputBackend
     {
         private readonly Vector _dpi;
         private int _fd;
@@ -15,7 +16,7 @@ namespace Avalonia.LinuxFramebuffer
         private IntPtr _mappedLength;
         private IntPtr _mappedAddress;
 
-        public LinuxFramebuffer(string fileName = null, Vector? dpi = null)
+        public FbdevOutput(string fileName = null, Vector? dpi = null)
         {
             _dpi = dpi ?? new Vector(96, 96);
             fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
@@ -85,14 +86,14 @@ namespace Avalonia.LinuxFramebuffer
 
         public string Id { get; private set; }
 
-        public Size PixelSize
+        public PixelSize 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);
+                return new PixelSize((int)nfo.xres, (int)nfo.yres);
             }
         }
 
@@ -123,7 +124,7 @@ namespace Avalonia.LinuxFramebuffer
             GC.SuppressFinalize(this);
         }
 
-        ~LinuxFramebuffer()
+        ~FbdevOutput()
         {
             ReleaseUnmanagedResources();
         }

+ 7 - 0
src/Linux/Avalonia.LinuxFramebuffer/Output/IOutputBackend.cs

@@ -0,0 +1,7 @@
+namespace Avalonia.LinuxFramebuffer.Output
+{
+    public interface IOutputBackend
+    {
+        PixelSize PixelSize { get; }
+    }
+}