11 changed files with 288 additions and 213 deletions
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Avalonia.LinuxFramebuffer.Input |
|||
{ |
|||
public interface IScreenInfoProvider |
|||
{ |
|||
Size ScaledSize { get; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
|
|||
|
|||
} |
|||
} |
|||
@ -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))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue