[Linux] Implemented libinput IMouseDriver2

This commit is contained in:
thefiddler 2014-07-16 11:24:53 +02:00
parent 340d34b07b
commit bef4901659
5 changed files with 409 additions and 40 deletions

View file

@ -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,
}
}

View file

@ -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);
}
}

View file

@ -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
{

View file

@ -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()

View file

@ -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<KeyboardDevice> Keyboards = new DeviceCollection<KeyboardDevice>();
DeviceCollection<MouseDevice> Mice = new DeviceCollection<MouseDevice>();
// 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