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