Browse Source
Conflicts: src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs src/Avalonia.Visuals/Media/Typeface.cs src/Avalonia.Visuals/Rendering/RendererMixin.cs src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs tests/Avalonia.UnitTests/TestServices.cs tests/Avalonia.Visuals.UnitTests/Media/TypefaceTests.csscenegraph-after-breakage
33 changed files with 1120 additions and 39 deletions
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,14 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard1.3</TargetFramework> |
|||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
|||
</PropertyGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
|||
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
|||
<ProjectReference Include="..\..\Skia\Avalonia.Skia.Desktop.NetStandard\Avalonia.Skia.Desktop.NetStandard.csproj" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,89 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
unsafe class EvDevDevice |
|||
{ |
|||
private static readonly Lazy<List<EvDevDevice>> AllMouseDevices = new Lazy<List<EvDevDevice>>(() |
|||
=> OpenMouseDevices()); |
|||
|
|||
private static List<EvDevDevice> OpenMouseDevices() |
|||
{ |
|||
var rv = new List<EvDevDevice>(); |
|||
foreach (var dev in Directory.GetFiles("/dev/input", "event*").Select(Open)) |
|||
{ |
|||
if (!dev.IsMouse) |
|||
NativeUnsafeMethods.close(dev.Fd); |
|||
else |
|||
rv.Add(dev); |
|||
} |
|||
return rv; |
|||
} |
|||
|
|||
public static IReadOnlyList<EvDevDevice> MouseDevices => AllMouseDevices.Value; |
|||
|
|||
|
|||
public int Fd { get; } |
|||
private IntPtr _dev; |
|||
public string Name { get; } |
|||
public List<EvType> EventTypes { get; private set; } = new List<EvType>(); |
|||
public input_absinfo? AbsX { get; } |
|||
public input_absinfo? AbsY { get; } |
|||
|
|||
public EvDevDevice(int fd, IntPtr dev) |
|||
{ |
|||
Fd = fd; |
|||
_dev = dev; |
|||
Name = Marshal.PtrToStringAnsi(NativeUnsafeMethods.libevdev_get_name(_dev)); |
|||
foreach (EvType type in Enum.GetValues(typeof(EvType))) |
|||
{ |
|||
if (NativeUnsafeMethods.libevdev_has_event_type(dev, type) != 0) |
|||
EventTypes.Add(type); |
|||
} |
|||
var ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int) AbsAxis.ABS_X); |
|||
if (ptr != null) |
|||
AbsX = *ptr; |
|||
ptr = NativeUnsafeMethods.libevdev_get_abs_info(dev, (int)AbsAxis.ABS_Y); |
|||
if (ptr != null) |
|||
AbsY = *ptr; |
|||
} |
|||
|
|||
public input_event? NextEvent() |
|||
{ |
|||
input_event ev; |
|||
if (NativeUnsafeMethods.libevdev_next_event(_dev, 2, out ev) == 0) |
|||
return ev; |
|||
return null; |
|||
} |
|||
|
|||
public bool IsMouse => EventTypes.Contains(EvType.EV_REL); |
|||
|
|||
public static EvDevDevice Open(string device) |
|||
{ |
|||
var fd = NativeUnsafeMethods.open(device, 2048, 0); |
|||
if (fd <= 0) |
|||
throw new Exception($"Unable to open {device} code {Marshal.GetLastWin32Error()}"); |
|||
IntPtr dev; |
|||
var rc = NativeUnsafeMethods.libevdev_new_from_fd(fd, out dev); |
|||
if (rc < 0) |
|||
{ |
|||
NativeUnsafeMethods.close(fd); |
|||
throw new Exception($"Unable to initialize evdev for {device} code {Marshal.GetLastWin32Error()}"); |
|||
} |
|||
return new EvDevDevice(fd, dev); |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
public class EvDevAxisInfo |
|||
{ |
|||
public int Minimum { get; set; } |
|||
public int Maximum { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
class FramebufferToplevelImpl : IEmbeddableWindowImpl |
|||
{ |
|||
private readonly LinuxFramebuffer _fb; |
|||
private bool _renderQueued; |
|||
public IInputRoot InputRoot { get; private set; } |
|||
|
|||
public FramebufferToplevelImpl(LinuxFramebuffer fb) |
|||
{ |
|||
_fb = fb; |
|||
Invalidate(default(Rect)); |
|||
var mice = new Mice(ClientSize.Width, ClientSize.Height); |
|||
mice.Start(); |
|||
mice.Event += e => Input?.Invoke(e); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
|
|||
public void Invalidate(Rect rect) |
|||
{ |
|||
if(_renderQueued) |
|||
return; |
|||
_renderQueued = true; |
|||
Dispatcher.UIThread.InvokeAsync(() => |
|||
{ |
|||
Paint?.Invoke(new Rect(default(Point), ClientSize)); |
|||
_renderQueued = false; |
|||
}); |
|||
} |
|||
|
|||
public void SetInputRoot(IInputRoot inputRoot) |
|||
{ |
|||
InputRoot = inputRoot; |
|||
} |
|||
|
|||
public Point PointToClient(Point point) => point; |
|||
|
|||
public Point PointToScreen(Point point) => point; |
|||
|
|||
public void SetCursor(IPlatformHandle cursor) |
|||
{ |
|||
} |
|||
|
|||
public Size ClientSize => _fb.PixelSize; |
|||
public double Scaling => 1; |
|||
public IEnumerable<object> Surfaces => new object[] {_fb}; |
|||
public Action<RawInputEventArgs> Input { get; set; } |
|||
public Action<Rect> Paint { get; set; } |
|||
public Action<Size> Resized { get; set; } |
|||
public Action<double> ScalingChanged { get; set; } |
|||
public Action Closed { get; set; } |
|||
public event Action LostFocus; |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls.Platform.Surfaces; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
public sealed unsafe class LinuxFramebuffer : IFramebufferPlatformSurface, IDisposable |
|||
{ |
|||
private readonly Size _dpi; |
|||
private int _fd; |
|||
private fb_fix_screeninfo _fixedInfo; |
|||
private fb_var_screeninfo _varInfo; |
|||
private IntPtr _mappedLength; |
|||
private IntPtr _mappedAddress; |
|||
|
|||
public LinuxFramebuffer(string fileName = null, Size? dpi = null) |
|||
{ |
|||
_dpi = dpi ?? new Size(96, 96); |
|||
fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0"; |
|||
_fd = NativeUnsafeMethods.open(fileName, 2, 0); |
|||
if (_fd <= 0) |
|||
throw new Exception("Error: " + Marshal.GetLastWin32Error()); |
|||
|
|||
try |
|||
{ |
|||
Init(); |
|||
} |
|||
catch |
|||
{ |
|||
Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
void Init() |
|||
{ |
|||
fixed (void* pnfo = &_varInfo) |
|||
{ |
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) |
|||
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); |
|||
|
|||
SetBpp(); |
|||
|
|||
_varInfo.yoffset = 100; |
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) |
|||
_varInfo.transp = new fb_bitfield(); |
|||
|
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) |
|||
throw new Exception("FBIOPUT_VSCREENINFO error: " + Marshal.GetLastWin32Error()); |
|||
|
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) |
|||
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); |
|||
|
|||
if (_varInfo.bits_per_pixel != 32) |
|||
throw new Exception("Unable to set 32-bit display mode"); |
|||
} |
|||
fixed(void*pnfo = &_fixedInfo) |
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo)) |
|||
throw new Exception("FBIOGET_FSCREENINFO error: " + Marshal.GetLastWin32Error()); |
|||
|
|||
_mappedLength = new IntPtr(_fixedInfo.line_length * _varInfo.yres); |
|||
_mappedAddress = NativeUnsafeMethods.mmap(IntPtr.Zero, _mappedLength, 3, 1, _fd, IntPtr.Zero); |
|||
if (_mappedAddress == new IntPtr(-1)) |
|||
throw new Exception($"Unable to mmap {_mappedLength} bytes, error {Marshal.GetLastWin32Error()}"); |
|||
fixed (fb_fix_screeninfo* pnfo = &_fixedInfo) |
|||
{ |
|||
int idlen; |
|||
for (idlen = 0; idlen < 16 && pnfo->id[idlen] != 0; idlen++) ; |
|||
Id = Encoding.ASCII.GetString(pnfo->id, idlen); |
|||
} |
|||
} |
|||
|
|||
void SetBpp() |
|||
{ |
|||
_varInfo.bits_per_pixel = 32; |
|||
_varInfo.grayscale = 0; |
|||
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield |
|||
{ |
|||
length = 8 |
|||
}; |
|||
_varInfo.green.offset = 8; |
|||
_varInfo.blue.offset = 16; |
|||
_varInfo.transp.offset = 24; |
|||
} |
|||
|
|||
public string Id { get; private set; } |
|||
|
|||
public Size PixelSize |
|||
{ |
|||
get |
|||
{ |
|||
fb_var_screeninfo nfo; |
|||
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, &nfo)) |
|||
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); |
|||
return new Size(nfo.xres, nfo.yres); |
|||
} |
|||
} |
|||
|
|||
public ILockedFramebuffer Lock() |
|||
{ |
|||
if (_fd <= 0) |
|||
throw new ObjectDisposedException("LinuxFramebuffer"); |
|||
return new LockedFramebuffer(_fd, _fixedInfo, _varInfo, _mappedAddress, _dpi); |
|||
} |
|||
|
|||
|
|||
private void ReleaseUnmanagedResources() |
|||
{ |
|||
if (_mappedAddress != IntPtr.Zero) |
|||
{ |
|||
NativeUnsafeMethods.munmap(_mappedAddress, _mappedLength); |
|||
_mappedAddress = IntPtr.Zero; |
|||
} |
|||
if(_fd == 0) |
|||
return; |
|||
NativeUnsafeMethods.close(_fd); |
|||
_fd = 0; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
ReleaseUnmanagedResources(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
~LinuxFramebuffer() |
|||
{ |
|||
ReleaseUnmanagedResources(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Embedding; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Platform; |
|||
using Avalonia.LinuxFramebuffer; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
class LinuxFramebufferPlatform |
|||
{ |
|||
LinuxFramebuffer _fb; |
|||
public static KeyboardDevice KeyboardDevice = new KeyboardDevice(); |
|||
public static MouseDevice MouseDevice = new MouseDevice(); |
|||
private static readonly Stopwatch St = Stopwatch.StartNew(); |
|||
internal static uint Timestamp => (uint)St.ElapsedTicks; |
|||
public static FramebufferToplevelImpl TopLevel; |
|||
LinuxFramebufferPlatform(string fbdev = null) |
|||
{ |
|||
_fb = new LinuxFramebuffer(fbdev); |
|||
} |
|||
|
|||
|
|||
void Initialize() |
|||
{ |
|||
AvaloniaLocator.CurrentMutable |
|||
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>() |
|||
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice) |
|||
.Bind<IMouseDevice>().ToConstant(MouseDevice) |
|||
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>() |
|||
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance) |
|||
.Bind<IRenderLoop>().ToConstant(PlatformThreadingInterface.Instance); |
|||
} |
|||
|
|||
internal static TopLevel Initialize<T>(T builder, string fbdev = null) where T : AppBuilderBase<T>, new() |
|||
{ |
|||
var platform = new LinuxFramebufferPlatform(fbdev); |
|||
builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev") |
|||
.SetupWithoutStarting(); |
|||
var tl = new EmbeddableControlRoot(TopLevel = new FramebufferToplevelImpl(platform._fb)); |
|||
tl.Prepare(); |
|||
return tl; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static class LinuxFramebufferPlatformExtensions |
|||
{ |
|||
class TokenClosable : ICloseable |
|||
{ |
|||
public event EventHandler Closed; |
|||
|
|||
public TokenClosable(CancellationToken token) |
|||
{ |
|||
token.Register(() => Dispatcher.UIThread.InvokeAsync(() => Closed?.Invoke(this, new EventArgs()))); |
|||
} |
|||
} |
|||
|
|||
public static void InitializeWithLinuxFramebuffer<T>(this T builder, Action<TopLevel> setup, |
|||
CancellationToken stop = default(CancellationToken), string fbdev = null) |
|||
where T : AppBuilderBase<T>, new() |
|||
{ |
|||
setup(LinuxFramebufferPlatform.Initialize(builder, fbdev)); |
|||
builder.BeforeStartCallback(builder); |
|||
builder.Instance.Run(new TokenClosable(stop)); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,47 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
unsafe class LockedFramebuffer : ILockedFramebuffer |
|||
{ |
|||
private readonly int _fb; |
|||
private readonly fb_fix_screeninfo _fixedInfo; |
|||
private fb_var_screeninfo _varInfo; |
|||
private readonly IntPtr _address; |
|||
|
|||
public LockedFramebuffer(int fb, fb_fix_screeninfo fixedInfo, fb_var_screeninfo varInfo, IntPtr address, Size dpi) |
|||
{ |
|||
_fb = fb; |
|||
_fixedInfo = fixedInfo; |
|||
_varInfo = varInfo; |
|||
_address = address; |
|||
Dpi = dpi; |
|||
//Use double buffering to avoid flicker
|
|||
Address = Marshal.AllocHGlobal(RowBytes * Height); |
|||
} |
|||
|
|||
|
|||
void VSync() |
|||
{ |
|||
NativeUnsafeMethods.ioctl(_fb, FbIoCtl.FBIO_WAITFORVSYNC, null); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
VSync(); |
|||
NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Height)); |
|||
|
|||
Marshal.FreeHGlobal(Address); |
|||
Address = IntPtr.Zero; |
|||
} |
|||
|
|||
public IntPtr Address { get; private set; } |
|||
public int Width => (int)_varInfo.xres; |
|||
public int Height => (int) _varInfo.yres; |
|||
public int RowBytes => (int) _fixedInfo.line_length; |
|||
public Size Dpi { get; } |
|||
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; |
|||
} |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
public unsafe class Mice |
|||
{ |
|||
private readonly double _width; |
|||
private readonly double _height; |
|||
private double _x; |
|||
private double _y; |
|||
|
|||
public event Action<RawInputEventArgs> Event; |
|||
|
|||
public Mice(double width, double height) |
|||
{ |
|||
_width = width; |
|||
_height = height; |
|||
} |
|||
|
|||
public void Start() => AvaloniaLocator.Current.GetService<IRuntimePlatform>().PostThreadPoolItem(Worker); |
|||
|
|||
private void Worker() |
|||
{ |
|||
|
|||
var mouseDevices = EvDevDevice.MouseDevices.Where(d => d.IsMouse).ToList(); |
|||
if (mouseDevices.Count == 0) |
|||
return; |
|||
var are = new AutoResetEvent(false); |
|||
while (true) |
|||
{ |
|||
try |
|||
{ |
|||
var rfds = new fd_set {count = mouseDevices.Count}; |
|||
for (int c = 0; c < mouseDevices.Count; c++) |
|||
rfds.fds[c] = mouseDevices[c].Fd; |
|||
IntPtr* timeval = stackalloc IntPtr[2]; |
|||
timeval[0] = new IntPtr(0); |
|||
timeval[1] = new IntPtr(100); |
|||
are.WaitOne(30); |
|||
foreach (var dev in mouseDevices) |
|||
{ |
|||
while(true) |
|||
{ |
|||
var ev = dev.NextEvent(); |
|||
if (!ev.HasValue) |
|||
break; |
|||
|
|||
PlatformThreadingInterface.Instance.Send(() => ProcessEvent(dev, ev.Value)); |
|||
} |
|||
} |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Console.Error.WriteLine(e.ToString()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static double TranslateAxis(input_absinfo axis, int value, double max) |
|||
{ |
|||
return (value - axis.minimum) / (double) (axis.maximum - axis.minimum) * max; |
|||
} |
|||
|
|||
private void ProcessEvent(EvDevDevice device, input_event ev) |
|||
{ |
|||
if (ev.type == (short)EvType.EV_REL) |
|||
{ |
|||
if (ev.code == (short) AxisEventCode.REL_X) |
|||
_x = Math.Min(_width, Math.Max(0, _x + ev.value)); |
|||
else if (ev.code == (short) AxisEventCode.REL_Y) |
|||
_y = Math.Min(_height, Math.Max(0, _y + ev.value)); |
|||
else |
|||
return; |
|||
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, |
|||
LinuxFramebufferPlatform.Timestamp, |
|||
LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y), |
|||
InputModifiers.None)); |
|||
} |
|||
if (ev.type ==(int) EvType.EV_ABS) |
|||
{ |
|||
if (ev.code == (short) AbsAxis.ABS_X && device.AbsX.HasValue) |
|||
_x = TranslateAxis(device.AbsX.Value, ev.value, _width); |
|||
else if (ev.code == (short) AbsAxis.ABS_Y && device.AbsY.HasValue) |
|||
_y = TranslateAxis(device.AbsY.Value, ev.value, _height); |
|||
else |
|||
return; |
|||
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, |
|||
LinuxFramebufferPlatform.Timestamp, |
|||
LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y), |
|||
InputModifiers.None)); |
|||
} |
|||
if (ev.type == (short) EvType.EV_KEY) |
|||
{ |
|||
RawMouseEventType? type = null; |
|||
if (ev.code == (ushort) EvKey.BTN_LEFT) |
|||
type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp; |
|||
if (ev.code == (ushort)EvKey.BTN_RIGHT) |
|||
type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp; |
|||
if (ev.code == (ushort) EvKey.BTN_MIDDLE) |
|||
type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp; |
|||
if (!type.HasValue) |
|||
return; |
|||
|
|||
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice, |
|||
LinuxFramebufferPlatform.Timestamp, |
|||
LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,254 @@ |
|||
using __u32 = System.UInt32; |
|||
using __s32 = System.Int32; |
|||
using __u16 = System.UInt16; |
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
// ReSharper disable FieldCanBeMadeReadOnly.Local
|
|||
// ReSharper disable ArrangeTypeMemberModifiers
|
|||
// ReSharper disable BuiltInTypeReferenceStyle
|
|||
// ReSharper disable InconsistentNaming
|
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
unsafe class NativeUnsafeMethods |
|||
{ |
|||
[DllImport("libc", EntryPoint = "open", SetLastError = true)] |
|||
public static extern int open(string pathname, int flags, int mode); |
|||
|
|||
[DllImport("libc", EntryPoint = "close", SetLastError = true)] |
|||
public static extern int close(int fd); |
|||
|
|||
[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] |
|||
public static extern int ioctl(int fd, FbIoCtl code, void* arg); |
|||
|
|||
[DllImport("libc", EntryPoint = "mmap", SetLastError = true)] |
|||
public static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, |
|||
int fd, IntPtr offset); |
|||
[DllImport("libc", EntryPoint = "munmap", SetLastError = true)] |
|||
public static extern int munmap(IntPtr addr, IntPtr length); |
|||
|
|||
[DllImport("libc", EntryPoint = "memcpy", SetLastError = true)] |
|||
public static extern int memcpy(IntPtr dest, IntPtr src, IntPtr length); |
|||
|
|||
[DllImport("libc", EntryPoint = "select", SetLastError = true)] |
|||
public static extern int select(int nfds, void* rfds, void* wfds, void* exfds, IntPtr* timevals); |
|||
|
|||
[DllImport("libevdev.so.2", EntryPoint = "libevdev_new_from_fd", SetLastError = true)] |
|||
public static extern int libevdev_new_from_fd(int fd, out IntPtr dev); |
|||
|
|||
[DllImport("libevdev.so.2", EntryPoint = "libevdev_has_event_type", SetLastError = true)] |
|||
public static extern int libevdev_has_event_type(IntPtr dev, EvType type); |
|||
|
|||
[DllImport("libevdev.so.2", EntryPoint = "libevdev_next_event", SetLastError = true)] |
|||
public static extern int libevdev_next_event(IntPtr dev, int flags, out input_event ev); |
|||
|
|||
[DllImport("libevdev.so.2", EntryPoint = "libevdev_get_name", SetLastError = true)] |
|||
public static extern IntPtr libevdev_get_name(IntPtr dev); |
|||
[DllImport("libevdev.so.2", EntryPoint = "libevdev_get_abs_info", SetLastError = true)] |
|||
public static extern input_absinfo* libevdev_get_abs_info(IntPtr dev, int code); |
|||
} |
|||
|
|||
enum FbIoCtl : uint |
|||
{ |
|||
FBIOGET_VSCREENINFO = 0x4600, |
|||
FBIOPUT_VSCREENINFO = 0x4601, |
|||
FBIOGET_FSCREENINFO = 0x4602, |
|||
FBIOGET_VBLANK = 0x80204612u, |
|||
FBIO_WAITFORVSYNC = 0x40044620, |
|||
FBIOPAN_DISPLAY = 0x4606 |
|||
} |
|||
|
|||
[Flags] |
|||
enum VBlankFlags |
|||
{ |
|||
FB_VBLANK_VBLANKING = 0x001 /* currently in a vertical blank */, |
|||
FB_VBLANK_HBLANKING = 0x002 /* currently in a horizontal blank */, |
|||
FB_VBLANK_HAVE_VBLANK = 0x004 /* vertical blanks can be detected */, |
|||
FB_VBLANK_HAVE_HBLANK = 0x008 /* horizontal blanks can be detected */, |
|||
FB_VBLANK_HAVE_COUNT = 0x010 /* global retrace counter is available */, |
|||
FB_VBLANK_HAVE_VCOUNT = 0x020 /* the vcount field is valid */, |
|||
FB_VBLANK_HAVE_HCOUNT = 0x040 /* the hcount field is valid */, |
|||
FB_VBLANK_VSYNCING = 0x080 /* currently in a vsync */, |
|||
FB_VBLANK_HAVE_VSYNC = 0x100 /* verical syncs can be detected */ |
|||
} |
|||
|
|||
unsafe struct fb_vblank { |
|||
public VBlankFlags flags; /* FB_VBLANK flags */ |
|||
__u32 count; /* counter of retraces since boot */ |
|||
__u32 vcount; /* current scanline position */ |
|||
__u32 hcount; /* current scandot position */ |
|||
fixed __u32 reserved[4]; /* reserved for future compatibility */ |
|||
}; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
unsafe struct fb_fix_screeninfo |
|||
{ |
|||
public fixed byte id[16]; /* identification string eg "TT Builtin" */ |
|||
|
|||
public IntPtr smem_start; /* Start of frame buffer mem */ |
|||
|
|||
/* (physical address) */ |
|||
public __u32 smem_len; /* Length of frame buffer mem */ |
|||
|
|||
public __u32 type; /* see FB_TYPE_* */ |
|||
public __u32 type_aux; /* Interleave for interleaved Planes */ |
|||
public __u32 visual; /* see FB_VISUAL_* */ |
|||
public __u16 xpanstep; /* zero if no hardware panning */ |
|||
public __u16 ypanstep; /* zero if no hardware panning */ |
|||
public __u16 ywrapstep; /* zero if no hardware ywrap */ |
|||
public __u32 line_length; /* length of a line in bytes */ |
|||
|
|||
public IntPtr mmio_start; /* Start of Memory Mapped I/O */ |
|||
|
|||
/* (physical address) */ |
|||
public __u32 mmio_len; /* Length of Memory Mapped I/O */ |
|||
|
|||
public __u32 accel; /* Type of acceleration available */ |
|||
public fixed __u16 reserved[3]; /* Reserved for future compatibility */ |
|||
}; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct fb_bitfield |
|||
{ |
|||
public __u32 offset; /* beginning of bitfield */ |
|||
public __u32 length; /* length of bitfield */ |
|||
|
|||
public __u32 msb_right; /* != 0 : Most significant bit is */ |
|||
/* right */ |
|||
}; |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
unsafe struct fb_var_screeninfo |
|||
{ |
|||
public __u32 xres; /* visible resolution */ |
|||
public __u32 yres; |
|||
public __u32 xres_virtual; /* virtual resolution */ |
|||
public __u32 yres_virtual; |
|||
public __u32 xoffset; /* offset from virtual to visible */ |
|||
public __u32 yoffset; /* resolution */ |
|||
|
|||
public __u32 bits_per_pixel; /* guess what */ |
|||
public __u32 grayscale; /* != 0 Graylevels instead of colors */ |
|||
|
|||
public fb_bitfield red; /* bitfield in fb mem if true color, */ |
|||
public fb_bitfield green; /* else only length is significant */ |
|||
public fb_bitfield blue; |
|||
public fb_bitfield transp; /* transparency */ |
|||
|
|||
public __u32 nonstd; /* != 0 Non standard pixel format */ |
|||
|
|||
public __u32 activate; /* see FB_ACTIVATE_* */ |
|||
|
|||
public __u32 height; /* height of picture in mm */ |
|||
public __u32 width; /* width of picture in mm */ |
|||
|
|||
public __u32 accel_flags; /* acceleration flags (hints) */ |
|||
|
|||
/* Timing: All values in pixclocks, except pixclock (of course) */ |
|||
public __u32 pixclock; /* pixel clock in ps (pico seconds) */ |
|||
|
|||
public __u32 left_margin; /* time from sync to picture */ |
|||
public __u32 right_margin; /* time from picture to sync */ |
|||
public __u32 upper_margin; /* time from sync to picture */ |
|||
public __u32 lower_margin; |
|||
public __u32 hsync_len; /* length of horizontal sync */ |
|||
public __u32 vsync_len; /* length of vertical sync */ |
|||
public __u32 sync; /* see FB_SYNC_* */ |
|||
public __u32 vmode; /* see FB_VMODE_* */ |
|||
public fixed __u32 reserved[6]; /* Reserved for future compatibility */ |
|||
}; |
|||
|
|||
|
|||
enum EvType |
|||
{ |
|||
EV_SYN = 0x00, |
|||
EV_KEY = 0x01, |
|||
EV_REL = 0x02, |
|||
EV_ABS = 0x03, |
|||
EV_MSC = 0x04, |
|||
EV_SW = 0x05, |
|||
EV_LED = 0x11, |
|||
EV_SND = 0x12, |
|||
EV_REP = 0x14, |
|||
EV_FF = 0x15, |
|||
EV_PWR = 0x16, |
|||
EV_FF_STATUS = 0x17, |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct input_event |
|||
{ |
|||
private IntPtr crap1, crap2; |
|||
public ushort type, code; |
|||
public int value; |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
unsafe struct fd_set |
|||
{ |
|||
public int count; |
|||
public fixed int fds [256]; |
|||
} |
|||
|
|||
enum AxisEventCode |
|||
{ |
|||
REL_X = 0x00, |
|||
REL_Y = 0x01, |
|||
REL_Z = 0x02, |
|||
REL_RX = 0x03, |
|||
REL_RY = 0x04, |
|||
REL_RZ = 0x05, |
|||
REL_HWHEEL = 0x06, |
|||
REL_DIAL = 0x07, |
|||
REL_WHEEL = 0x08, |
|||
REL_MISC = 0x09, |
|||
REL_MAX = 0x0f |
|||
} |
|||
|
|||
enum AbsAxis |
|||
{ |
|||
ABS_X = 0x00, |
|||
ABS_Y = 0x01, |
|||
ABS_Z = 0x02, |
|||
ABS_RX = 0x03, |
|||
ABS_RY = 0x04, |
|||
ABS_RZ = 0x05, |
|||
ABS_THROTTLE = 0x06, |
|||
ABS_RUDDER = 0x07, |
|||
ABS_WHEEL = 0x08, |
|||
ABS_GAS = 0x09, |
|||
ABS_BRAKE = 0x0a, |
|||
ABS_HAT0X = 0x10, |
|||
ABS_HAT0Y = 0x11, |
|||
ABS_HAT1X = 0x12, |
|||
ABS_HAT1Y = 0x13, |
|||
ABS_HAT2X = 0x14, |
|||
ABS_HAT2Y = 0x15, |
|||
ABS_HAT3X = 0x16, |
|||
ABS_HAT3Y = 0x17, |
|||
ABS_PRESSURE = 0x18, |
|||
ABS_DISTANCE = 0x19, |
|||
ABS_TILT_X = 0x1a, |
|||
ABS_TILT_Y = 0x1b, |
|||
ABS_TOOL_WIDTH = 0x1c |
|||
} |
|||
|
|||
enum EvKey |
|||
{ |
|||
BTN_LEFT = 0x110, |
|||
BTN_RIGHT = 0x111, |
|||
BTN_MIDDLE = 0x112 |
|||
} |
|||
|
|||
[StructLayout(LayoutKind.Sequential)] |
|||
struct input_absinfo |
|||
{ |
|||
public __s32 value; |
|||
public __s32 minimum; |
|||
public __s32 maximum; |
|||
public __s32 fuzz; |
|||
public __s32 flat; |
|||
public __s32 resolution; |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,112 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.InteropServices; |
|||
using System.Text; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
class PlatformThreadingInterface : IPlatformThreadingInterface, IRenderLoop |
|||
{ |
|||
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); |
|||
|
|||
public PlatformThreadingInterface() |
|||
{ |
|||
TlsCurrentThreadIsLoopThread = true; |
|||
StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs())); |
|||
} |
|||
|
|||
private readonly AutoResetEvent _signaled = new AutoResetEvent(false); |
|||
private readonly AutoResetEvent _queued = new AutoResetEvent(false); |
|||
|
|||
private readonly Queue<Action> _actions = new Queue<Action>(); |
|||
|
|||
public void RunLoop(CancellationToken cancellationToken) |
|||
{ |
|||
var handles = new[] {_signaled, _queued}; |
|||
while (true) |
|||
{ |
|||
if (0 == WaitHandle.WaitAny(handles)) |
|||
Signaled?.Invoke(); |
|||
else |
|||
{ |
|||
while (true) |
|||
{ |
|||
Action item; |
|||
lock(_actions) |
|||
if (_actions.Count == 0) |
|||
break; |
|||
else |
|||
item = _actions.Dequeue(); |
|||
item(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Send(Action cb) |
|||
{ |
|||
lock (_actions) |
|||
{ |
|||
_actions.Enqueue(cb); |
|||
_queued.Set(); |
|||
} |
|||
} |
|||
|
|||
class WatTimer : IDisposable |
|||
{ |
|||
private readonly IDisposable _timer; |
|||
private GCHandle _handle; |
|||
|
|||
public WatTimer(IDisposable timer) |
|||
{ |
|||
_timer = timer; |
|||
_handle = GCHandle.Alloc(_timer); |
|||
} |
|||
public void Dispose() |
|||
{ |
|||
_handle.Free(); |
|||
_timer.Dispose(); |
|||
} |
|||
} |
|||
|
|||
public IDisposable StartTimer(TimeSpan interval, Action tick) |
|||
{ |
|||
return new WatTimer(new System.Threading.Timer(delegate |
|||
{ |
|||
var tcs = new TaskCompletionSource<int>(); |
|||
Send(() => |
|||
{ |
|||
try |
|||
{ |
|||
tick(); |
|||
} |
|||
finally |
|||
{ |
|||
tcs.SetResult(0); |
|||
} |
|||
}); |
|||
|
|||
|
|||
tcs.Task.Wait(); |
|||
}, null, TimeSpan.Zero, interval)); |
|||
|
|||
|
|||
} |
|||
|
|||
public void Signal() |
|||
{ |
|||
_signaled.Set(); |
|||
} |
|||
|
|||
[ThreadStatic] private static bool TlsCurrentThreadIsLoopThread; |
|||
|
|||
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread; |
|||
public event Action Signaled; |
|||
public event EventHandler<EventArgs> Tick; |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Input; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer |
|||
{ |
|||
internal class CursorFactoryStub : IStandardCursorFactory |
|||
{ |
|||
public IPlatformHandle GetCursor(StandardCursorType cursorType) |
|||
{ |
|||
return new PlatformHandle(IntPtr.Zero, null); |
|||
} |
|||
} |
|||
internal class PlatformSettings : IPlatformSettings |
|||
{ |
|||
public Size DoubleClickSize { get; } = new Size(4, 4); |
|||
public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue