csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
6.7 KiB
183 lines
6.7 KiB
using System;
|
|
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 FbdevOutput : IFramebufferPlatformSurface, IDisposable, IOutputBackend
|
|
{
|
|
private int _fd;
|
|
private fb_fix_screeninfo _fixedInfo;
|
|
private fb_var_screeninfo _varInfo;
|
|
private IntPtr _mappedLength;
|
|
private IntPtr _mappedAddress;
|
|
private FbDevBackBuffer _backBuffer;
|
|
public double Scaling { get; set; }
|
|
|
|
/// <summary>
|
|
/// Create a Linux frame buffer device output
|
|
/// </summary>
|
|
/// <param name="fileName">The frame buffer device name.
|
|
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
|
|
public FbdevOutput(string fileName = null) : this(fileName, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a Linux frame buffer device output
|
|
/// </summary>
|
|
/// <param name="fileName">The frame buffer device name.
|
|
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
|
|
/// <param name="format">The required pixel format for the frame buffer.
|
|
/// A null value will leave the frame buffer in the current pixel format.
|
|
/// Otherwise sets the frame buffer to the required format</param>
|
|
public FbdevOutput(string fileName, PixelFormat? format)
|
|
{
|
|
fileName ??= Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
|
|
_fd = NativeUnsafeMethods.open(fileName, 2, 0);
|
|
if (_fd <= 0)
|
|
throw new Exception("Error: " + Marshal.GetLastWin32Error());
|
|
|
|
try
|
|
{
|
|
Init(format);
|
|
}
|
|
catch
|
|
{
|
|
Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void Init(PixelFormat? format)
|
|
{
|
|
fixed (void* pnfo = &_varInfo)
|
|
{
|
|
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
|
|
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
|
|
|
|
if (format.HasValue)
|
|
{
|
|
SetBpp(format.Value);
|
|
|
|
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
|
|
_varInfo.transp = new fb_bitfield();
|
|
|
|
NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo);
|
|
|
|
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
|
|
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
|
|
|
|
if (_varInfo.bits_per_pixel != 32)
|
|
throw new Exception("Unable to set 32-bit display mode");
|
|
}
|
|
}
|
|
fixed(void*pnfo = &_fixedInfo)
|
|
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo))
|
|
throw new Exception("FBIOGET_FSCREENINFO error: " + Marshal.GetLastWin32Error());
|
|
|
|
_mappedLength = new IntPtr(_fixedInfo.line_length * _varInfo.yres);
|
|
_mappedAddress = NativeUnsafeMethods.mmap(IntPtr.Zero, _mappedLength, 3, 1, _fd, IntPtr.Zero);
|
|
if (_mappedAddress == new IntPtr(-1))
|
|
throw new Exception($"Unable to mmap {_mappedLength} bytes, error {Marshal.GetLastWin32Error()}");
|
|
fixed (fb_fix_screeninfo* pnfo = &_fixedInfo)
|
|
{
|
|
int idlen;
|
|
for (idlen = 0; idlen < 16 && pnfo->id[idlen] != 0; idlen++) ;
|
|
Id = Encoding.ASCII.GetString(pnfo->id, idlen);
|
|
}
|
|
}
|
|
|
|
void SetBpp(PixelFormat format)
|
|
{
|
|
if (format == PixelFormat.Rgba8888)
|
|
{
|
|
_varInfo.bits_per_pixel = 32;
|
|
_varInfo.grayscale = 0;
|
|
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
|
|
{
|
|
length = 8
|
|
};
|
|
_varInfo.green.offset = 8;
|
|
_varInfo.blue.offset = 16;
|
|
_varInfo.transp.offset = 24;
|
|
}
|
|
else if (format == PixelFormat.Bgra8888)
|
|
{
|
|
_varInfo.bits_per_pixel = 32;
|
|
_varInfo.grayscale = 0;
|
|
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
|
|
{
|
|
length = 8
|
|
};
|
|
_varInfo.green.offset = 8;
|
|
_varInfo.red.offset = 16;
|
|
_varInfo.transp.offset = 24;
|
|
}
|
|
else if (format == PixelFormat.Rgb565)
|
|
{
|
|
_varInfo.bits_per_pixel = 16;
|
|
_varInfo.grayscale = 0;
|
|
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
|
|
_varInfo.red.length = 5;
|
|
_varInfo.green.offset = 5;
|
|
_varInfo.green.length = 6;
|
|
_varInfo.blue.offset = 11;
|
|
_varInfo.blue.length = 5;
|
|
}
|
|
else throw new NotSupportedException($"Pixel format {format} is not supported");
|
|
}
|
|
|
|
public string Id { get; private set; }
|
|
|
|
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 PixelSize((int)nfo.xres, (int)nfo.yres);
|
|
}
|
|
}
|
|
|
|
public ILockedFramebuffer Lock()
|
|
{
|
|
if (_fd <= 0)
|
|
throw new ObjectDisposedException("LinuxFramebuffer");
|
|
return (_backBuffer ??=
|
|
new FbDevBackBuffer(_fd, _fixedInfo, _varInfo, _mappedAddress))
|
|
.Lock(new Vector(96, 96) * Scaling);
|
|
}
|
|
|
|
|
|
private void ReleaseUnmanagedResources()
|
|
{
|
|
if (_mappedAddress != IntPtr.Zero)
|
|
{
|
|
NativeUnsafeMethods.munmap(_mappedAddress, _mappedLength);
|
|
_mappedAddress = IntPtr.Zero;
|
|
}
|
|
if(_fd == 0)
|
|
return;
|
|
NativeUnsafeMethods.close(_fd);
|
|
_fd = 0;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_backBuffer?.Dispose();
|
|
_backBuffer = null;
|
|
ReleaseUnmanagedResources();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
~FbdevOutput()
|
|
{
|
|
ReleaseUnmanagedResources();
|
|
}
|
|
}
|
|
}
|
|
|