From bef4901659dfdb71449008c7da1ac7d662f1d9c7 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 11:24:53 +0200 Subject: [PATCH] [Linux] Implemented libinput IMouseDriver2 --- .../OpenTK/Platform/Linux/Bindings/Evdev.cs | 119 ++++++++++- .../Platform/Linux/Bindings/LibInput.cs | 123 +++++++++++- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 7 + Source/OpenTK/Platform/Linux/LinuxFactory.cs | 13 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 187 +++++++++++++++--- 5 files changed, 409 insertions(+), 40 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs index f60834f4..8674db33 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -30,7 +30,7 @@ using System; using OpenTK.Input; -namespace OpenTK +namespace OpenTK.Platform.Linux { // Bindings for linux/input.h class Evdev @@ -330,6 +330,123 @@ namespace OpenTK }; #endregion + + public static MouseButton GetMouseButton(EvdevButton button) + { + switch (button) + { + case EvdevButton.LEFT: + return MouseButton.Left; + case EvdevButton.RIGHT: + return MouseButton.Right; + case EvdevButton.MIDDLE: + return MouseButton.Middle; + case EvdevButton.BTN0: + return MouseButton.Button1; + case EvdevButton.BTN1: + return MouseButton.Button2; + case EvdevButton.BTN2: + return MouseButton.Button3; + case EvdevButton.BTN3: + return MouseButton.Button4; + case EvdevButton.BTN4: + return MouseButton.Button5; + case EvdevButton.BTN5: + return MouseButton.Button6; + case EvdevButton.BTN6: + return MouseButton.Button7; + case EvdevButton.BTN7: + return MouseButton.Button8; + case EvdevButton.BTN8: + return MouseButton.Button9; + case EvdevButton.BTN9: + return MouseButton.LastButton; + default: + return MouseButton.LastButton; + } + } + } + + enum EvdevButton : uint + { + MISC = 0x100, + BTN0 = 0x100, + BTN1 = 0x101, + BTN2 = 0x102, + BTN3 = 0x103, + BTN4 = 0x104, + BTN5 = 0x105, + BTN6 = 0x106, + BTN7 = 0x107, + BTN8 = 0x108, + BTN9 = 0x109, + + MOUSE = 0x110, + LEFT = 0x110, + RIGHT = 0x111, + MIDDLE = 0x112, + SIDE = 0x113, + EXTRA = 0x114, + FORWARD = 0x115, + BACK = 0x116, + TASK = 0x117, + + JOYSTICK = 0x120, + TRIGGER = 0x120, + THUMB = 0x121, + THUMB2 = 0x122, + TOP = 0x123, + TOP2 = 0x124, + PINKIE = 0x125, + BASE = 0x126, + BASE2 = 0x127, + BASE3 = 0x128, + BASE4 = 0x129, + BASE5 = 0x12a, + BASE6 = 0x12b, + DEAD = 0x12f, + + GAMEPAD = 0x130, + SOUTH = 0x130, + A = SOUTH, + EAST = 0x131, + B = EAST, + C = 0x132, + NORTH = 0x133, + X = NORTH, + WEST = 0x134, + Y = WEST, + Z = 0x135, + TL = 0x136, + TR = 0x137, + TL2 = 0x138, + TR2 = 0x139, + SELECT = 0x13a, + START = 0x13b, + MODE = 0x13c, + THUMBL = 0x13d, + THUMBR = 0x13e, + + DIGI = 0x140, + TOOL_PEN = 0x140, + TOOL_RUBBER = 0x141, + TOOL_BRUSH = 0x142, + TOOL_PENCIL = 0x143, + TOOL_AIRBRUSH = 0x144, + TOOL_FINGER = 0x145, + TOOL_MOUSE = 0x146, + TOOL_LENS = 0x147, + TOOL_QUINTTAP = 0x148, // Five fingers on trackpad + TOUCH = 0x14a, + STYLUS = 0x14b, + STYLUS2 = 0x14c, + TOOL_DOUBLETAP = 0x14d, + TOOL_TRIPLETAP = 0x14e, + TOOL_QUADTAP = 0x14f, // Four fingers on trackpad + + WHEEL = 0x150, + GEAR_DOWN = 0x150, + GEAR_UP = 0x151, } } diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index f64353dc..29864b93 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -98,6 +98,9 @@ namespace OpenTK.Platform.Linux [DllImport(lib, EntryPoint = "libinput_event_get_keyboard_event", CallingConvention = CallingConvention.Cdecl)] public static extern KeyboardEvent GetKeyboardEvent(IntPtr @event); + [DllImport(lib, EntryPoint = "libinput_event_get_pointer_event", CallingConvention = CallingConvention.Cdecl)] + public static extern PointerEvent GetPointerEvent(IntPtr @event); + [DllImport(lib, EntryPoint = "libinput_event_get_type", CallingConvention = CallingConvention.Cdecl)] public static extern InputEventType GetEventType(IntPtr @event); @@ -148,17 +151,54 @@ namespace OpenTK.Platform.Linux TouchFrame } + enum ButtonState + { + Released = 0, + Pressed = 1 + } + enum KeyState { Released = 0, Pressed = 1 } + enum PointerAxis + { + VerticalScroll = 0, + HorizontalScroll = 1 + } + + struct Fixed24 + { + internal readonly int Value; + + public static implicit operator double(Fixed24 n) + { + long l = ((1023L + 44L) << 52) + (1L << 51) + n.Value; + unsafe + { + double d = *(double*)&l; + return d - (3L << 43); + } + } + + public static implicit operator float(Fixed24 n) + { + return (float)(double)n; + } + + public static explicit operator int(Fixed24 n) + { + return n.Value >> 8; + } + } + [StructLayout(LayoutKind.Sequential)] class InputInterface { - IntPtr open; - IntPtr close; + internal readonly IntPtr open; + internal readonly IntPtr close; public InputInterface( OpenRestrictedCallback open_restricted, @@ -179,25 +219,90 @@ namespace OpenTK.Platform.Linux public IntPtr BaseEvent { get { return GetBaseEvent(@event); } } public IntPtr Event { get { return @event; } } + public uint Time { get { return GetTime(@event); } } public uint Key { get { return GetKey(@event); } } public uint KeyCount { get { return GetSeatKeyCount(@event); } } public KeyState KeyState { get { return GetKeyState(@event); } } - public uint Time { get { return GetTime(@event); } } [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_time", CallingConvention = CallingConvention.Cdecl)] static extern uint GetTime(IntPtr @event); - [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key", CallingConvention = CallingConvention.Cdecl)] - static extern uint GetKey(IntPtr @event); - - [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key_state", CallingConvention = CallingConvention.Cdecl)] - static extern KeyState GetKeyState(IntPtr @event); - [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_base_event", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr GetBaseEvent(IntPtr @event); [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_seat_key_count", CallingConvention = CallingConvention.Cdecl)] static extern uint GetSeatKeyCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetKey(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key_state", CallingConvention = CallingConvention.Cdecl)] + static extern KeyState GetKeyState(IntPtr @event); + } + + + [StructLayout(LayoutKind.Sequential)] + struct PointerEvent + { + IntPtr @event; + + public IntPtr BaseEvent { get { return GetBaseEvent(@event); } } + public IntPtr Event { get { return @event; } } + public uint Time { get { return GetTime(@event); } } + public EvdevButton Button { get { return (EvdevButton)GetButton(@event); } } + public uint ButtonCount { get { return GetButtonCount(@event); } } + public ButtonState ButtonState { get { return GetButtonState(@event); } } + public PointerAxis Axis { get { return GetAxis(@event); } } + public Fixed24 AxisValue { get { return GetAxisValue(@event); } } + public Fixed24 DeltaX { get { return GetDX(@event); } } + public Fixed24 DeltaY { get { return GetDY(@event); } } + public Fixed24 X { get { return GetAbsX(@event); } } + public Fixed24 Y { get { return GetAbsY(@event); } } + // Are the following useful? + //public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); } + //public Fixed24 TransformedY(int height) { return GetAbsXTransformed(@event, height); } + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_time", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetTime(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_base_event", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr GetBaseEvent(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_seat_key_count", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetSeatKeyCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_button", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetButton(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_seat_button_count", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetButtonCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_button_state", CallingConvention = CallingConvention.Cdecl)] + static extern ButtonState GetButtonState(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_axis", CallingConvention = CallingConvention.Cdecl)] + static extern PointerAxis GetAxis(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_axis_value", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAxisValue(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_dx", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetDX(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_dy", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetDY(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_x", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsX(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_y", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsY(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_x_transformed", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsXTransformed(IntPtr @event, int width); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_y_transformed", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsYTransformed(IntPtr @event, int height); } } diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index 06673659..db026a77 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -97,6 +97,13 @@ namespace OpenTK.Platform.Linux public static extern bool isatty(int fd); } + enum ErrorNumber + { + Interrupted = 4, + Again = 11, + InvalidValue = 22, + } + [Flags] enum OpenFlags { diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 943e9dcd..23a3b2cb 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -47,8 +47,7 @@ namespace OpenTK.Platform.Linux IntPtr egl_display; IJoystickDriver2 JoystickDriver; - IKeyboardDriver2 KeyboardDriver; - IMouseDriver2 MouseDriver; + LinuxInput MouseKeyboardDriver; const string gpu_path = "/dev/dri"; // card0, card1, ... @@ -217,14 +216,18 @@ namespace OpenTK.Platform.Linux { lock (this) { - KeyboardDriver = KeyboardDriver ?? new LinuxInput(); - return KeyboardDriver; + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; } } public override IMouseDriver2 CreateMouseDriver() { - throw new NotImplementedException(); + lock (this) + { + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; + } } public override IJoystickDriver2 CreateJoystickDriver() diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index b3b54d22..ddd3a8ae 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -37,17 +37,16 @@ namespace OpenTK.Platform.Linux { class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable { - class KeyboardDevice + class DeviceBase { readonly IntPtr Device; string name; string output; - public KeyboardDevice(IntPtr device, int id) + public DeviceBase(IntPtr device, int id) { Device = device; Id = id; - State.SetIsConnected(true); } public int Id @@ -79,15 +78,28 @@ namespace OpenTK.Platform.Linux return output; } } - - public KeyboardState State; } - class MouseDevice + class KeyboardDevice : DeviceBase + { + public KeyboardState State; + + public KeyboardDevice(IntPtr device, int id) + : base(device, id) + { + State.SetIsConnected(true); + } + } + + class MouseDevice : DeviceBase { - public int FD; - public string Name; public MouseState State; + + public MouseDevice(IntPtr device, int id) + : base(device, id) + { + State.SetIsConnected(true); + } } static readonly object Sync = new object(); @@ -96,6 +108,11 @@ namespace OpenTK.Platform.Linux DeviceCollection Keyboards = new DeviceCollection(); DeviceCollection Mice = new DeviceCollection(); + // Global mouse cursor state + Vector2 CursorPosition = Vector2.Zero; + // Global mouse cursor offset (used for emulating SetPosition) + Vector2 CursorOffset = Vector2.Zero; + IntPtr udev; IntPtr input_context; InputInterface input_interface = new InputInterface( @@ -188,15 +205,20 @@ namespace OpenTK.Platform.Linux while (Interlocked.Read(ref exit) == 0) { int ret = Libc.poll(ref poll_fd, 1, -1); + ErrorNumber error = (ErrorNumber)Marshal.GetLastWin32Error(); + bool is_error = + ret < 0 && !(error == ErrorNumber.Again || error == ErrorNumber.Interrupted) || + (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0; + if (ret > 0 && (poll_fd.revents & (PollFlags.In | PollFlags.Pri)) != 0) { ProcessEvents(input_context); } - if (ret < 0 || (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0) + if (is_error) { Debug.Print("[Input] Exiting input loop {0} due to poll error [ret:{1} events:{2}]. Error: {3}.", - input_thread.ManagedThreadId, ret, poll_fd.revents, Marshal.GetLastWin32Error()); + input_thread.ManagedThreadId, ret, poll_fd.revents, error); Interlocked.Increment(ref exit); } } @@ -281,7 +303,23 @@ namespace OpenTK.Platform.Linux break; case InputEventType.KeyboardKey: - HandleKeyboard(input_context, device, LibInput.GetKeyboardEvent(pevent)); + HandleKeyboard(GetKeyboard(device), LibInput.GetKeyboardEvent(pevent)); + break; + + case InputEventType.PointerAxis: + HandlePointerAxis(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerButton: + HandlePointerButton(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerMotion: + HandlePointerMotion(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerMotionAbsolute: + HandlePointerMotionAbsolute(GetMouse(device), LibInput.GetPointerEvent(pevent)); break; } } @@ -296,17 +334,19 @@ namespace OpenTK.Platform.Linux { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); Keyboards.Add(keyboard.Id, keyboard); - Debug.Print("[Linux] libinput: added keyboard device {0}", keyboard.Id); + Debug.Print("[Input] Added keyboard device {0}", keyboard.Id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) { - Debug.Print("[Linux] Todo: libinput mouse device."); + MouseDevice mouse = new MouseDevice(device, Mice.Count); + Mice.Add(mouse.Id, mouse); + Debug.Print("[Input] Added mouse device {0}", mouse.Id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Touch)) { - Debug.Print("[Linux] Todo: libinput touch device."); + Debug.Print("[Input] Todo: touch device."); } } @@ -317,13 +357,17 @@ namespace OpenTK.Platform.Linux int id = GetId(device); Keyboards.Remove(id); } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) + { + int id = GetId(device); + Mice.Remove(id); + } } - void HandleKeyboard(IntPtr context, IntPtr device, KeyboardEvent e) + void HandleKeyboard(KeyboardDevice device, KeyboardEvent e) { - int id = GetId(device); - KeyboardDevice keyboard = Keyboards.FromHardwareId(id); - if (keyboard != null) + if (device != null) { Key key = Key.Unknown; uint raw = e.Key; @@ -337,19 +381,89 @@ namespace OpenTK.Platform.Linux Debug.Print("[Linux] Unknown key with code '{0}'", raw); } - keyboard.State.SetKeyState(key, e.KeyState == KeyState.Pressed); + device.State.SetKeyState(key, e.KeyState == KeyState.Pressed); } - else + } + + void HandlePointerAxis(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) { - Debug.Print("[Linux] libinput ignoring invalid device id {0}", id); + double value = e.AxisValue; + PointerAxis axis = e.Axis; + switch (axis) + { + case PointerAxis.HorizontalScroll: + mouse.State.SetScrollRelative((float)value, 0); + break; + + case PointerAxis.VerticalScroll: + mouse.State.SetScrollRelative(0, (float)value); + break; + + default: + Debug.Print("[Input] Unknown scroll axis {0}.", axis); + break; + } } } + void HandlePointerButton(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) + { + MouseButton button = Evdev.GetMouseButton(e.Button); + ButtonState state = e.ButtonState; + mouse.State[(MouseButton)button] = state == ButtonState.Pressed; + } + } + + void HandlePointerMotion(MouseDevice mouse, PointerEvent e) + { + Vector2 delta = new Vector2((float)e.X, (float)e.Y); + if (mouse != null) + { + mouse.State.Position += delta; + } + } + + void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e) + { + Vector2 position = new Vector2(e.X, e.Y); + if (mouse != null) + { + mouse.State.Position = position; + } + CursorPosition = position; // update global cursor position + } + static int GetId(IntPtr device) { return LibInput.DeviceGetData(device).ToInt32(); } + KeyboardDevice GetKeyboard(IntPtr device) + { + int id = GetId(device); + KeyboardDevice keyboard = Keyboards.FromHardwareId(id); + if (keyboard == null) + { + Debug.Print("[Input] Keyboard {0} does not exist in device list.", id); + } + return keyboard; + } + + MouseDevice GetMouse(IntPtr device) + { + int id = GetId(device); + MouseDevice mouse = Mice.FromHardwareId(id); + if (mouse == null) + { + Debug.Print("[Input] Mouse {0} does not exist in device list.", id); + } + return mouse; + } + #endregion #region IKeyboardDriver2 implementation @@ -405,22 +519,45 @@ namespace OpenTK.Platform.Linux MouseState IMouseDriver2.GetState() { - throw new NotImplementedException(); + lock (Sync) + { + MouseState state = new MouseState(); + foreach (MouseDevice mouse in Mice) + { + state.MergeBits(mouse.State); + } + return state; + } } MouseState IMouseDriver2.GetState(int index) { - throw new NotImplementedException(); + lock (Sync) + { + MouseDevice device = Mice.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new MouseState(); + } + } } void IMouseDriver2.SetPosition(double x, double y) { - throw new NotImplementedException(); + // Todo: this does not appear to be supported in libinput. + // We will have to emulate this in the KMS mouse rendering code. + CursorOffset = new Vector2((float)x, (float)y); } MouseState IMouseDriver2.GetCursorState() { - throw new NotImplementedException(); + MouseState state = (this as IMouseDriver2).GetState(); + state.Position = CursorPosition + CursorOffset; + return state; } #endregion