ソースを参照

Support touch input for fbdev via libinput

Nikita Tsukanov 6 年 前
コミット
a48bc262af

+ 2 - 1
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

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

+ 8 - 0
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));
             }
+
+            
         }
         
     }

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

+ 9 - 5
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 - 0
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 - 0
src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs

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

+ 125 - 0
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 - 0
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);
+
+
+    }
+}

+ 2 - 1
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;
                 }

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

+ 1 - 1
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