Browse Source

Merge branch 'master' into diagnostics-cleanup

pull/2757/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
7ece91f9b7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/SkiaSharp.props
  2. 21
      samples/ControlCatalog.NetCore/Program.cs
  3. 85
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  4. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  5. 3
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  6. 8
      src/Avalonia.Input/TouchDevice.cs
  7. 8
      src/Avalonia.OpenGL/EglContext.cs
  8. 94
      src/Avalonia.OpenGL/EglDisplay.cs
  9. 5
      src/Avalonia.OpenGL/EglInterface.cs
  10. 2
      src/Avalonia.OpenGL/GlInterface.cs
  11. 88
      src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs
  12. 38
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  13. 12
      src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs
  14. 7
      src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs
  15. 183
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  16. 139
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
  17. 46
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  18. 117
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  19. 13
      src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs
  20. 292
      src/Linux/Avalonia.LinuxFramebuffer/Output/Drm.cs
  21. 158
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmBindings.cs
  22. 250
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  23. 11
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  24. 7
      src/Linux/Avalonia.LinuxFramebuffer/Output/IOutputBackend.cs

2
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" /> <PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

21
samples/ControlCatalog.NetCore/Program.cs

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

85
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@ -9,94 +9,69 @@ using Avalonia.Threading;
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface, IRenderTimer public class InternalPlatformThreadingInterface : IPlatformThreadingInterface
{ {
public InternalPlatformThreadingInterface() public InternalPlatformThreadingInterface()
{ {
TlsCurrentThreadIsLoopThread = true; 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 _signaled = new AutoResetEvent(false);
private readonly AutoResetEvent _queued = new AutoResetEvent(false);
private readonly Queue<Action> _actions = new Queue<Action>();
public void RunLoop(CancellationToken cancellationToken) public void RunLoop(CancellationToken cancellationToken)
{ {
var handles = new[] {_signaled, _queued};
while (true) while (true)
{ {
if (0 == WaitHandle.WaitAny(handles)) Signaled?.Invoke(null);
Signaled?.Invoke(null); _signaled.WaitOne();
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 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; 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); _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() public void Dispose()
{ {
_handle.Free(); _handle.Free();
_timer.Dispose(); _timer.Dispose();
_timer = null;
} }
} }
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{ {
return new WatTimer(new System.Threading.Timer(delegate return new TimerImpl(priority, interval, tick);
{
var tcs = new TaskCompletionSource<int>();
Send(() =>
{
try
{
tick();
}
finally
{
tcs.SetResult(0);
}
});
tcs.Task.Wait();
}, null, TimeSpan.Zero, interval));
} }
public void Signal(DispatcherPriority prio) public void Signal(DispatcherPriority prio)

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

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

3
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

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

8
src/Avalonia.Input/TouchDevice.cs

@ -61,6 +61,12 @@ namespace Avalonia.Input
pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); 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) if (args.Type == RawPointerEventType.TouchUpdate)
{ {
@ -68,6 +74,8 @@ namespace Avalonia.Input
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
args.Position, ev.Timestamp, new PointerPointProperties(modifiers), modifiers)); args.Position, ev.Timestamp, new PointerPointProperties(modifiers), modifiers));
} }
} }
} }

8
src/Avalonia.OpenGL/EglContext.cs

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

94
src/Avalonia.OpenGL/EglDisplay.cs

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

5
src/Avalonia.OpenGL/EglInterface.cs

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

2
src/Avalonia.OpenGL/GlInterface.cs

@ -39,7 +39,7 @@ namespace Avalonia.OpenGL
[GlEntryPoint("glClearStencil")] [GlEntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; } 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")] [GlEntryPoint("glClearColor")]
public GlClearColor ClearColor { get; } public GlClearColor ClearColor { get; }

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; }
}
}

38
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

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

12
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
src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs

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

183
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
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);
}
}

46
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

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

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)));
}
}
}
}

13
src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs

@ -33,6 +33,10 @@ namespace Avalonia.LinuxFramebuffer
[DllImport("libc", EntryPoint = "select", SetLastError = true)] [DllImport("libc", EntryPoint = "select", SetLastError = true)]
public static extern int select(int nfds, void* rfds, void* wfds, void* exfds, IntPtr* timevals); 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)] [DllImport("libevdev.so.2", EntryPoint = "libevdev_new_from_fd", SetLastError = true)]
public static extern int libevdev_new_from_fd(int fd, out IntPtr dev); 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); 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 enum FbIoCtl : uint
{ {
FBIOGET_VSCREENINFO = 0x4600, FBIOGET_VSCREENINFO = 0x4600,
@ -188,7 +199,7 @@ namespace Avalonia.LinuxFramebuffer
unsafe struct fd_set unsafe struct fd_set
{ {
public int count; public int count;
public fixed int fds [256]; public fixed byte fds [256];
} }
enum AxisEventCode enum AxisEventCode

292
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
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
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;
}
}

11
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebuffer.cs → src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

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

7
src/Linux/Avalonia.LinuxFramebuffer/Output/IOutputBackend.cs

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