Browse Source

Support touch input for fbdev via libinput

pull/2764/head
Nikita Tsukanov 7 years ago
parent
commit
a48bc262af
  1. 3
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  2. 8
      src/Avalonia.Input/TouchDevice.cs
  3. 88
      src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs
  4. 14
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  5. 12
      src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs
  6. 7
      src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs
  7. 125
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  8. 122
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
  9. 3
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  10. 117
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  11. 2
      src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs

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

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

8
src/Avalonia.Input/TouchDevice.cs

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

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

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

@ -2,25 +2,26 @@
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.LinuxFramebuffer.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
{
class FramebufferToplevelImpl : IEmbeddableWindowImpl
class FramebufferToplevelImpl : IEmbeddableWindowImpl, IScreenInfoProvider
{
private readonly LinuxFramebuffer _fb;
private readonly IInputBackend _inputBackend;
private bool _renderQueued;
public IInputRoot InputRoot { get; private set; }
public FramebufferToplevelImpl(LinuxFramebuffer fb)
public FramebufferToplevelImpl(LinuxFramebuffer fb, IInputBackend inputBackend)
{
_fb = fb;
_inputBackend = inputBackend;
Invalidate(default(Rect));
var mice = new Mice(this, ClientSize.Width, ClientSize.Height);
mice.Start();
mice.Event += e => Input?.Invoke(e);
_inputBackend.Initialize(this, e => Input?.Invoke(e));
}
public IRenderer CreateRenderer(IRenderRoot root)
@ -49,6 +50,7 @@ namespace Avalonia.LinuxFramebuffer
public void SetInputRoot(IInputRoot inputRoot)
{
InputRoot = inputRoot;
_inputBackend.SetInputRoot(inputRoot);
}
public Point PointToClient(PixelPoint p) => p.ToPoint(1);
@ -73,5 +75,7 @@ namespace Avalonia.LinuxFramebuffer
add {}
remove {}
}
public Size ScaledSize => _fb.PixelSize / 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; }
}
}

125
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs

@ -0,0 +1,125 @@
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 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;
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);
libinput_event_destroy(ev);
}
libinput_dispatch(ctx);
}
}
private void ScheduleInput(RawInputEventArgs ev)
{
_inputQueue.Enqueue(ev);
if (_inputQueue.Count == 1)
{
Dispatcher.UIThread.Post(() =>
{
while (_inputQueue.Count > 0)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
var dequeuedEvent = _inputQueue.Dequeue();
_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));
}
}
public void Initialize(IScreenInfoProvider screen, Action<RawInputEventArgs> onInput)
{
_screen = screen;
_onInput = onInput;
}
public void SetInputRoot(IInputRoot root)
{
_inputRoot = root;
}
}
}

122
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs

@ -0,0 +1,122 @@
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);
}
}

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

@ -8,6 +8,7 @@ using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.LinuxFramebuffer;
using Avalonia.LinuxFramebuffer.Input.LibInput;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
@ -69,7 +70,7 @@ namespace Avalonia.LinuxFramebuffer
if (_topLevel == null)
{
var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb));
var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, new LibInputBackend()));
tl.Prepare();
_topLevel = tl;
}

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

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

@ -188,7 +188,7 @@ namespace Avalonia.LinuxFramebuffer
unsafe struct fd_set
{
public int count;
public fixed int fds [256];
public fixed byte fds [256];
}
enum AxisEventCode

Loading…
Cancel
Save