diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs index b728844e97..6fac90f255 100644 --- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -19,7 +19,8 @@ namespace Avalonia.Input.Raw NonClientLeftButtonDown, TouchBegin, TouchUpdate, - TouchEnd + TouchEnd, + TouchCancel } /// diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 7f473bb320..c85f98b04a 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/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)); } + + } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs b/src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs deleted file mode 100644 index f28dca81b8..0000000000 --- a/src/Linux/Avalonia.LinuxFramebuffer/EvDevDevice.cs +++ /dev/null @@ -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> AllMouseDevices = new Lazy>(() - => OpenMouseDevices()); - - private static List OpenMouseDevices() - { - var rv = new List(); - 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 MouseDevices => AllMouseDevices.Value; - - - public int Fd { get; } - private IntPtr _dev; - public string Name { get; } - public List EventTypes { get; private set; } = new List(); - 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; } - } -} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 78369a3648..9b9bd6c7b8 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/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; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/IInputBackend.cs new file mode 100644 index 0000000000..84a903bb9d --- /dev/null +++ b/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 onInput); + void SetInputRoot(IInputRoot root); + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs new file mode 100644 index 0000000000..cb0e51862a --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/IScreenInfoProvider.cs @@ -0,0 +1,7 @@ +namespace Avalonia.LinuxFramebuffer.Input +{ + public interface IScreenInfoProvider + { + Size ScaledSize { get; } + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs new file mode 100644 index 0000000000..ca31db6100 --- /dev/null +++ b/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 _inputThreadActions = new Queue(); + private TouchDevice _touch = new TouchDevice(); + + private readonly Queue _inputQueue = new Queue(); + private Action _onInput; + private Dictionary _pointers = new Dictionary(); + + 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 onInput) + { + _screen = screen; + _onInput = onInput; + } + + public void SetInputRoot(IInputRoot root) + { + _inputRoot = root; + } + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs new file mode 100644 index 0000000000..6c82f53071 --- /dev/null +++ b/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 del) + { + GCHandle.Alloc(del); + return Marshal.GetFunctionPointerForDelegate(del); + } + + s_Interface[0] = Convert(OpenRestricted); + s_Interface[1] = Convert(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); + + + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 396942c8dd..1eea1c07f7 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/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; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs b/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs deleted file mode 100644 index 2b82b4f4aa..0000000000 --- a/src/Linux/Avalonia.LinuxFramebuffer/Mice.cs +++ /dev/null @@ -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 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))); - } - } - } -} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs index 5427af7d44..8fbd78588f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/NativeUnsafeMethods.cs +++ b/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