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