[Linux] Implemented evdev joystick polling

This commit is contained in:
thefiddler 2014-08-09 21:26:21 +02:00
parent 397bdda076
commit 129fb81224
2 changed files with 154 additions and 74 deletions

View file

@ -381,6 +381,23 @@ namespace OpenTK.Platform.Linux
return (uint)v; return (uint)v;
} }
// Get absolute value / limits
public static int GetAbs(int fd, EvdevAxis axis, out InputAbsInfo info)
{
info = default(InputAbsInfo);
unsafe
{
fixed (InputAbsInfo* pinfo = &info)
{
// EVIOCGABS(abs) = _IOR('E', 0x40 + (abs), struct input_absinfo)
uint ioctl = IOCreate(DirectionFlags.Read, (int)axis + 0x40, BlittableValueType<InputAbsInfo>.Stride);
int retval = Libc.ioctl(fd, ioctl, new IntPtr(pinfo));
return retval;
}
}
}
// Get supported event bits
public static int GetBit(int fd, EvdevType ev, int length, IntPtr data) public static int GetBit(int fd, EvdevType ev, int length, IntPtr data)
{ {
// EVIOCGBIT = _IOC(_IOC_READ, 'E', 0x20 + (ev), len) // EVIOCGBIT = _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
@ -577,6 +594,17 @@ namespace OpenTK.Platform.Linux
Name128 = (2u << 30) | ((byte)'E' << 8) | (0x06u << 0) | (128u << 16), //EVIOCGNAME(len) = _IOC(_IOC_READ, 'E', 0x06, len) Name128 = (2u << 30) | ((byte)'E' << 8) | (0x06u << 0) | (128u << 16), //EVIOCGNAME(len) = _IOC(_IOC_READ, 'E', 0x06, len)
} }
[StructLayout(LayoutKind.Sequential)]
struct InputAbsInfo
{
public int Value;
public int Minimum;
public int Maximum;
public int Fuzz;
public int Flat;
public int Resolution;
};
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
struct InputId struct InputId
{ {

View file

@ -35,6 +35,12 @@ using OpenTK.Input;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
struct AxisInfo
{
public JoystickAxis Axis;
public InputAbsInfo Info;
}
class LinuxJoystickDetails class LinuxJoystickDetails
{ {
public Guid Guid; public Guid Guid;
@ -44,18 +50,23 @@ namespace OpenTK.Platform.Linux
public JoystickState State; public JoystickState State;
public JoystickCapabilities Caps; public JoystickCapabilities Caps;
public readonly Dictionary<EvdevAxis, JoystickAxis> AxisMap = public readonly Dictionary<EvdevAxis, AxisInfo> AxisMap =
new Dictionary<EvdevAxis, JoystickAxis>(); new Dictionary<EvdevAxis, AxisInfo>();
public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap = public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap =
new Dictionary<EvdevButton, JoystickButton>(); new Dictionary<EvdevButton, JoystickButton>();
public readonly Dictionary<int, JoystickHat> HatMap =
new Dictionary<int, JoystickHat>();
} }
sealed class LinuxJoystick : IJoystickDriver2 sealed class LinuxJoystick : IJoystickDriver2
{ {
#region Fields #region Fields
static readonly HatPosition[,] HatPositions = new HatPosition[,]
{
{ HatPosition.UpLeft, HatPosition.Up, HatPosition.UpRight },
{ HatPosition.Left, HatPosition.Centered, HatPosition.Right },
{ HatPosition.DownLeft, HatPosition.Down, HatPosition.DownRight }
};
readonly object sync = new object(); readonly object sync = new object();
readonly FileSystemWatcher watcher = new FileSystemWatcher(); readonly FileSystemWatcher watcher = new FileSystemWatcher();
@ -141,7 +152,6 @@ namespace OpenTK.Platform.Linux
if (stick != null) if (stick != null)
{ {
CloseJoystick(stick); CloseJoystick(stick);
Sticks.TryRemove(number);
} }
} }
} }
@ -198,77 +208,53 @@ namespace OpenTK.Platform.Linux
return (*(ptr + byte_offset) & (1 << bit_offset)) != 0; return (*(ptr + byte_offset) & (1 << bit_offset)) != 0;
} }
unsafe static int AddAxes(LinuxJoystickDetails stick, byte* axisbit, int bytecount) unsafe static void QueryCapabilities(LinuxJoystickDetails stick,
byte* axisbit, int axisbytes,
byte* keybit, int keybytes,
out int axes, out int buttons, out int hats)
{ {
JoystickAxis axes = 0; // Note: since we are trying to be compatible with the SDL2 gamepad database,
JoystickHat hats = 0; // we have to match SDL2 behavior bug-for-bug. This means:
int bitcount = bytecount * 8; // HAT0-HAT3 are all reported as hats (even if the docs say that HAT1 and HAT2 can be analogue triggers)
for (EvdevAxis axis = 0; axis < EvdevAxis.CNT && (int)axis < bitcount; axis++) // DPAD buttons are reported as buttons, not as hats (unlike Windows and Mac OS X)
{
if (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
{
// Axis is analogue hat - skip
continue;
}
if (TestBit(axisbit, (int)axis)) axes = buttons = hats = 0;
for (EvdevAxis axis = 0; axis < EvdevAxis.CNT && (int)axis < axisbytes * 8; axis++)
{
InputAbsInfo info;
bool is_valid = true;
is_valid &= TestBit(axisbit, (int)axis);
is_valid &= Evdev.GetAbs(stick.FileDescriptor, axis, out info) >= 0;
if (is_valid)
{ {
stick.AxisMap.Add(axis, axes++); if (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
} {
else // Analogue hat
{ stick.AxisMap.Add(axis, new AxisInfo
stick.AxisMap.Add(axis, (JoystickAxis)(-1)); {
Axis = (JoystickAxis)(JoystickHat)hats++,
Info = info
});
}
else
{
// Regular axis
stick.AxisMap.Add(axis, new AxisInfo
{
Axis = (JoystickAxis)axes++,
Info = info
});
}
} }
} }
return (int)axes;
}
unsafe static int AddButtons(LinuxJoystickDetails stick, byte* keybit, int bytecount) for (EvdevButton button = 0; button < EvdevButton.Last && (int)button < keybytes * 8; button++)
{
JoystickButton buttons = 0;
int bitcount = bytecount * 8;
for (EvdevButton button = 0; button < EvdevButton.Last && (int)button < bitcount; button++)
{ {
if (button >= EvdevButton.DPAD_UP && button <= EvdevButton.DPAD_RIGHT)
{
// Button is dpad (hat) - skip
continue;
}
if (TestBit(keybit, (int)button)) if (TestBit(keybit, (int)button))
{ {
stick.ButtonMap.Add(button, buttons++); stick.ButtonMap.Add(button, (JoystickButton)buttons++);
}
else
{
stick.ButtonMap.Add(button, (JoystickButton)(-1));
} }
} }
return (int)buttons;
}
unsafe static int AddHats(LinuxJoystickDetails stick,
byte* axisbit, int axiscount,
byte* keybit, int keycount)
{
JoystickHat hats = 0;
for (EvdevAxis hat = EvdevAxis.HAT0X; hat < EvdevAxis.HAT3Y && (int)hat < axiscount * 8; hat++)
{
if (TestBit(axisbit, (int)hat))
{
stick.HatMap.Add((int)hat, hats++);
}
}
for (EvdevButton dpad = EvdevButton.DPAD_UP; dpad < EvdevButton.DPAD_RIGHT && (int)dpad < keycount * 8; dpad++)
{
if (TestBit(axisbit, (int)dpad))
{
stick.HatMap.Add((int)dpad, hats++);
}
}
return (int)hats;
} }
LinuxJoystickDetails OpenJoystick(string path) LinuxJoystickDetails OpenJoystick(string path)
@ -322,12 +308,15 @@ namespace OpenTK.Platform.Linux
Name = name, Name = name,
Guid = CreateGuid(id, name), Guid = CreateGuid(id, name),
}; };
stick.Caps = new JoystickCapabilities(
AddAxes(stick, axisbit, axissize), int axes, buttons, hats;
AddButtons(stick, keybit, keysize), QueryCapabilities(stick, axisbit, axissize, keybit, keysize,
AddHats(stick, axisbit, axissize, keybit, keysize), out axes, out buttons, out hats);
true);
stick.State.SetIsConnected(true); stick.Caps = new JoystickCapabilities(axes, buttons, hats, true);
// Poll the joystick once, to initialize its state
PollJoystick(stick);
} }
} }
@ -360,6 +349,11 @@ namespace OpenTK.Platform.Linux
js.Caps = new JoystickCapabilities(); js.Caps = new JoystickCapabilities();
} }
JoystickHatState TranslateHat(int x, int y)
{
return new JoystickHatState(HatPositions[x, y]);
}
void PollJoystick(LinuxJoystickDetails js) void PollJoystick(LinuxJoystickDetails js)
{ {
unsafe unsafe
@ -381,13 +375,71 @@ namespace OpenTK.Platform.Linux
switch (e->Type) switch (e->Type)
{ {
case EvdevType.ABS: case EvdevType.ABS:
break; {
AxisInfo axis;
if (js.AxisMap.TryGetValue((EvdevAxis)e->Code, out axis))
{
if (axis.Info.Maximum > axis.Info.Minimum)
{
if (e->Code >= (int)EvdevAxis.HAT0X && e->Code <= (int)EvdevAxis.HAT3Y)
{
// We currently treat analogue hats as digital hats
// to maintain compatibility with SDL2. We can do
// better than this, however.
JoystickHat hat = JoystickHat.Hat0 + (e->Code - (int)EvdevAxis.HAT0X) / 2;
JoystickHatState pos = js.State.GetHat(hat);
int xy_axis = (int)axis.Axis & 0x1;
switch (xy_axis)
{
case 0:
// X-axis
pos = TranslateHat(
e->Value.CompareTo(0) + 1,
pos.IsUp ? 0 : pos.IsDown ? 2 : 1);
break;
case 1:
// Y-axis
pos = TranslateHat(
pos.IsLeft ? 0 : pos.IsRight ? 2 : 1,
e->Value.CompareTo(0) + 1);
break;
}
js.State.SetHat(hat, pos);
}
else
{
// This axis represents a regular axis or trigger
js.State.SetAxis(
axis.Axis,
(short)Common.HidHelper.ScaleValue(e->Value,
axis.Info.Minimum, axis.Info.Maximum,
short.MinValue, short.MaxValue));
}
}
}
break;
}
case EvdevType.KEY: case EvdevType.KEY:
break; {
JoystickButton button;
if (js.ButtonMap.TryGetValue((EvdevButton)e->Code, out button))
{
js.State.SetButton(button, e->Value != 0);
}
break;
}
} }
//js.State.SetPacketNumber(unchecked((int)e->Time.Seconds)); // Create a serial number (total seconds in 24.8 fixed point format)
int sec = (int)((long)e->Time.Seconds & 0xffffffff);
int msec = (int)e->Time.MicroSeconds / 1000;
int packet =
((sec & 0x00ffffff) << 24) |
Common.HidHelper.ScaleValue(msec, 0, 1000, 0, 255);
js.State.SetPacketNumber(unchecked((int)e->Time.Seconds));
} }
} }
} }