Merge pull request #181 from thefiddler/evdev

[Linux] Implement evdev joystick driver
This commit is contained in:
thefiddler 2014-09-20 13:53:12 +02:00
commit 2b3a4c578a
4 changed files with 480 additions and 190 deletions

View file

@ -175,6 +175,8 @@ namespace OpenTK.Input
sb.Append("St"); sb.Append("St");
if (BigButton == ButtonState.Pressed) if (BigButton == ButtonState.Pressed)
sb.Append("Gd"); sb.Append("Gd");
if (Back == ButtonState.Pressed)
sb.Append("Bk");
if (LeftShoulder == ButtonState.Pressed) if (LeftShoulder == ButtonState.Pressed)
sb.Append("L"); sb.Append("L");
if (RightShoulder == ButtonState.Pressed) if (RightShoulder == ButtonState.Pressed)

View file

@ -29,6 +29,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
using OpenTK.Input; using OpenTK.Input;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
@ -36,6 +37,10 @@ namespace OpenTK.Platform.Linux
// Bindings for linux/input.h // Bindings for linux/input.h
class Evdev class Evdev
{ {
public const int KeyCount = 0x300;
public const int AxisCount = 0x40;
public const int EventCount = (int)EvdevType.CNT;
#region KeyMap #region KeyMap
public static readonly Key[] KeyMap = new Key[] public static readonly Key[] KeyMap = new Key[]
@ -365,9 +370,118 @@ namespace OpenTK.Platform.Linux
return MouseButton.Left; return MouseButton.Left;
} }
} }
static uint IOCreate(DirectionFlags dir, int number, int length)
{
long v =
((byte)dir << 30) |
((byte)'E' << 8) |
(number << 0) |
(length << 16);
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)
{
// EVIOCGBIT = _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
uint ioctl = IOCreate(DirectionFlags.Read, (int)ev + 0x20, length);
int retval = Libc.ioctl(fd, ioctl, data);
return retval;
}
public static int GetName(int fd, out string name)
{
unsafe
{
sbyte* pname = stackalloc sbyte[129];
int ret = Libc.ioctl(fd, EvdevIoctl.Name128, new IntPtr(pname));
name = new string(pname);
return ret;
}
}
public static int GetId(int fd, out EvdevInputId id)
{
id = default(EvdevInputId);
unsafe
{
fixed (EvdevInputId* pid = &id)
{
return Libc.ioctl(fd, EvdevIoctl.Id, new IntPtr(pid));
}
}
}
} }
enum EvdevButton : uint enum EvdevAxis
{
X = 0x00,
Y = 0x01,
Z = 0x02,
RX = 0x03,
RY = 0x04,
RZ = 0x05,
THROTTLE = 0x06,
RUDDER = 0x07,
WHEEL = 0x08,
GAS = 0x09,
BRAKE = 0x0a,
HAT0X = 0x10,
HAT0Y = 0x11,
HAT1X = 0x12,
HAT1Y = 0x13,
HAT2X = 0x14,
HAT2Y = 0x15,
HAT3X = 0x16,
HAT3Y = 0x17,
PRESSURE = 0x18,
DISTANCE = 0x19,
TILT_X = 0x1a,
TILT_Y = 0x1b,
TOOL_WIDTH = 0x1c,
VOLUME = 0x20,
MISC = 0x28,
MT_SLOT = 0x2f, /* MT slot being modified */
MT_TOUCH_MAJOR = 0x30, /* Major axis of touching ellipse */
MT_TOUCH_MINOR = 0x31, /* Minor axis (omit if circular) */
MT_WIDTH_MAJOR = 0x32, /* Major axis of approaching ellipse */
MT_WIDTH_MINOR = 0x33, /* Minor axis (omit if circular) */
MT_ORIENTATION = 0x34, /* Ellipse orientation */
MT_POSITION_X = 0x35, /* Center X touch position */
MT_POSITION_Y = 0x36, /* Center Y touch position */
MT_TOOL_TYPE = 0x37, /* Type of touching device */
MT_BLOB_ID = 0x38, /* Group a set of packets as a blob */
MT_TRACKING_ID = 0x39, /* Unique ID of initiated contact */
MT_PRESSURE = 0x3a, /* Pressure on contact area */
MT_DISTANCE = 0x3b, /* Contact hover distance */
MT_TOOL_X = 0x3c, /* Center X tool position */
MT_TOOL_Y = 0x3d, /* Center Y tool position */
MAX = 0x3f,
CNT = (MAX+1),
}
enum EvdevButton
{ {
MISC = 0x100, MISC = 0x100,
BTN0 = 0x100, BTN0 = 0x100,
@ -447,6 +561,75 @@ namespace OpenTK.Platform.Linux
WHEEL = 0x150, WHEEL = 0x150,
GEAR_DOWN = 0x150, GEAR_DOWN = 0x150,
GEAR_UP = 0x151, GEAR_UP = 0x151,
DPAD_UP = 0x220,
DPAD_DOWN = 0x221,
DPAD_LEFT = 0x222,
DPAD_RIGHT = 0x223,
Last = 0x300,
}
enum EvdevType : byte
{
SYN = 0x00,
KEY = 0x01,
REL = 0x02,
ABS = 0x03,
MSC = 0x04,
SW = 0x05,
LED = 0x11,
SND = 0x12,
REP = 0x14,
FF = 0x15,
PWR = 0x16,
FF_STATUS = 0x17,
MAX = 0x1f,
CNT = (MAX+1),
}
enum EvdevIoctl : uint
{
Id = (2u << 30) | ((byte)'E' << 8) | (0x02u << 0) | (8u << 16), //EVIOCGID = _IOR('E', 0x02, struct input_id)
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)]
struct InputId
{
public ushort BusType;
public ushort Vendor;
public ushort Product;
public ushort Version;
}
[StructLayout(LayoutKind.Sequential)]
struct InputEvent
{
public TimeVal Time;
ushort type;
public ushort Code;
public int Value;
public EvdevType Type { get { return (EvdevType)type; } }
}
[StructLayout(LayoutKind.Sequential)]
struct TimeVal
{
public IntPtr Seconds;
public IntPtr MicroSeconds;
} }
} }

View file

@ -52,7 +52,10 @@ namespace OpenTK.Platform.Linux
public static extern int ioctl(int d, JoystickIoctlCode request, StringBuilder data); public static extern int ioctl(int d, JoystickIoctlCode request, StringBuilder data);
[DllImport(lib)] [DllImport(lib)]
public static extern int ioctl(int d, EvdevIoctlCode request, out EvdevInputId data); public static extern int ioctl(int d, EvdevIoctl request, [Out] IntPtr data);
[DllImport(lib)]
public static extern int ioctl(int d, uint request, [Out] IntPtr data);
[DllImport(lib)] [DllImport(lib)]
public static extern int ioctl(int d, KeyboardIoctlCode request, ref IntPtr data); public static extern int ioctl(int d, KeyboardIoctlCode request, ref IntPtr data);
@ -106,6 +109,14 @@ namespace OpenTK.Platform.Linux
InvalidValue = 22, InvalidValue = 22,
} }
[Flags]
enum DirectionFlags
{
None = 0,
Write = 1,
Read = 2
}
[Flags] [Flags]
enum OpenFlags enum OpenFlags
{ {
@ -116,11 +127,6 @@ namespace OpenTK.Platform.Linux
CloseOnExec = 0x0080000 CloseOnExec = 0x0080000
} }
enum EvdevIoctlCode : uint
{
Id = ((byte)'E' << 8) | (0x02 << 0) //EVIOCGID, which is _IOR('E', 0x02, struct input_id)
}
[Flags] [Flags]
enum JoystickEventType : byte enum JoystickEventType : byte
{ {

View file

@ -35,24 +35,44 @@ using OpenTK.Input;
namespace OpenTK.Platform.Linux namespace OpenTK.Platform.Linux
{ {
struct LinuxJoyDetails struct AxisInfo
{ {
public Guid Guid; public JoystickAxis Axis;
public int FileDescriptor; public InputAbsInfo Info;
public JoystickState State; }
class LinuxJoystickDetails
{
public Guid Guid;
public string Name;
public int FileDescriptor;
public int PathIndex; // e.g. "0" for "/dev/input/event0". Used as a hardware id
public JoystickState State;
public JoystickCapabilities Caps;
public readonly Dictionary<EvdevAxis, AxisInfo> AxisMap =
new Dictionary<EvdevAxis, AxisInfo>();
public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap =
new Dictionary<EvdevButton, JoystickButton>();
} }
// Note: despite what the name says, this class is Linux-specific.
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();
readonly Dictionary<int, int> index_to_stick = new Dictionary<int, int>(); readonly DeviceCollection<LinuxJoystickDetails> Sticks =
List<JoystickDevice<LinuxJoyDetails>> sticks = new List<JoystickDevice<LinuxJoyDetails>>(); new DeviceCollection<LinuxJoystickDetails>();
bool disposed; bool disposed;
@ -89,12 +109,10 @@ namespace OpenTK.Platform.Linux
{ {
foreach (string file in Directory.GetFiles(path)) foreach (string file in Directory.GetFiles(path))
{ {
JoystickDevice<LinuxJoyDetails> stick = OpenJoystick(file); LinuxJoystickDetails stick = OpenJoystick(file);
if (stick != null) if (stick != null)
{ {
//stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons, {3}{0})", Sticks.Add(stick.PathIndex, stick);
//number, stick.Axis.Count, stick.Button.Count, path);
sticks.Add(stick);
} }
} }
} }
@ -102,10 +120,11 @@ namespace OpenTK.Platform.Linux
int GetJoystickNumber(string path) int GetJoystickNumber(string path)
{ {
if (path.StartsWith("js")) const string evdev = "event";
if (path.StartsWith(evdev))
{ {
int num; int num;
if (Int32.TryParse(path.Substring(2), out num)) if (Int32.TryParse(path.Substring(evdev.Length), out num))
{ {
return num; return num;
} }
@ -129,23 +148,10 @@ namespace OpenTK.Platform.Linux
int number = GetJoystickNumber(file); int number = GetJoystickNumber(file);
if (number != -1) if (number != -1)
{ {
// Find which joystick id matches this number var stick = Sticks.FromHardwareId(number);
int i; if (stick != null)
for (i = 0; i < sticks.Count; i++)
{ {
if (sticks[i].Id == number) CloseJoystick(stick);
{
break;
}
}
if (i == sticks.Count)
{
Debug.Print("[Evdev] Joystick id {0} does not exist.", number);
}
else
{
CloseJoystick(sticks[i]);
} }
} }
} }
@ -155,75 +161,111 @@ namespace OpenTK.Platform.Linux
#region Private Members #region Private Members
Guid CreateGuid(JoystickDevice<LinuxJoyDetails> js, string path, int number) Guid CreateGuid(EvdevInputId id, string name)
{ {
// Note: the first 8bytes of the Guid are byteswapped
// in three parts when using `new Guid(byte[])`:
// (int, short, short).
// We need to take that into account to match the expected
// Guid in the database. Ugh.
byte[] bytes = new byte[16]; byte[] bytes = new byte[16];
for (int i = 0; i < Math.Min(bytes.Length, js.Description.Length); i++)
int i = 0;
byte[] bus = BitConverter.GetBytes((int)id.BusType);
bytes[i++] = bus[3];
bytes[i++] = bus[2];
bytes[i++] = bus[1];
bytes[i++] = bus[0];
if (id.Vendor != 0 && id.Product != 0 && id.Version != 0)
{ {
bytes[i] = (byte)js.Description[i]; byte[] vendor = BitConverter.GetBytes(id.Vendor);
byte[] product = BitConverter.GetBytes(id.Product);
byte[] version = BitConverter.GetBytes(id.Version);
bytes[i++] = vendor[1];
bytes[i++] = vendor[0];
bytes[i++] = 0;
bytes[i++] = 0;
bytes[i++] = product[0]; // no byteswapping
bytes[i++] = product[1];
bytes[i++] = 0;
bytes[i++] = 0;
bytes[i++] = version[0]; // no byteswapping
bytes[i++] = version[1];
bytes[i++] = 0;
bytes[i++] = 0;
} }
else
{
for (int j = 0; j < bytes.Length - i; j++)
{
bytes[i + j] = (byte)name[j];
}
}
return new Guid(bytes); return new Guid(bytes);
#if false // Todo: move to /dev/input/event* from /dev/input/js*
string evdev_path = Path.Combine(Path.GetDirectoryName(path), "event" + number);
if (!File.Exists(evdev_path))
return new Guid();
int event_fd = UnsafeNativeMethods.open(evdev_path, OpenFlags.NonBlock);
if (event_fd < 0)
return new Guid();
try
{
EventInputId id;
if (UnsafeNativeMethods.ioctl(event_fd, EvdevInputId.Id, out id) < 0)
return new Guid();
int i = 0;
byte[] bus = BitConverter.GetBytes(id.BusType);
bytes[i++] = bus[0];
bytes[i++] = bus[1];
bytes[i++] = 0;
bytes[i++] = 0;
if (id.Vendor != 0 && id.Product != 0 && id.Version != 0)
{
byte[] vendor = BitConverter.GetBytes(id.Vendor);
byte[] product = BitConverter.GetBytes(id.Product);
byte[] version = BitConverter.GetBytes(id.Version);
bytes[i++] = vendor[0];
bytes[i++] = vendor[1];
bytes[i++] = 0;
bytes[i++] = 0;
bytes[i++] = product[0];
bytes[i++] = product[1];
bytes[i++] = 0;
bytes[i++] = 0;
bytes[i++] = version[0];
bytes[i++] = version[1];
bytes[i++] = 0;
bytes[i++] = 0;
}
else
{
for (; i < bytes.Length; i++)
{
bytes[i] = (byte)js.Description[i];
}
}
return new Guid(bytes);
}
finally
{
UnsafeNativeMethods.close(event_fd);
}
#endif
} }
JoystickDevice<LinuxJoyDetails> OpenJoystick(string path) unsafe static bool TestBit(byte* ptr, int bit)
{ {
JoystickDevice<LinuxJoyDetails> stick = null; int byte_offset = bit / 8;
int bit_offset = bit % 8;
return (*(ptr + byte_offset) & (1 << bit_offset)) != 0;
}
unsafe static void QueryCapabilities(LinuxJoystickDetails stick,
byte* axisbit, int axisbytes,
byte* keybit, int keybytes,
out int axes, out int buttons, out int hats)
{
// Note: since we are trying to be compatible with the SDL2 gamepad database,
// we have to match SDL2 behavior bug-for-bug. This means:
// HAT0-HAT3 are all reported as hats (even if the docs say that HAT1 and HAT2 can be analogue triggers)
// DPAD buttons are reported as buttons, not as hats (unlike Windows and Mac OS X)
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)
{
if (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
{
// Analogue hat
stick.AxisMap.Add(axis, new AxisInfo
{
Axis = (JoystickAxis)(JoystickHat)hats++,
Info = info
});
}
else
{
// Regular axis
stick.AxisMap.Add(axis, new AxisInfo
{
Axis = (JoystickAxis)axes++,
Info = info
});
}
}
}
for (EvdevButton button = 0; button < EvdevButton.Last && (int)button < keybytes * 8; button++)
{
if (TestBit(keybit, (int)button))
{
stick.ButtonMap.Add(button, (JoystickButton)buttons++);
}
}
}
LinuxJoystickDetails OpenJoystick(string path)
{
LinuxJoystickDetails stick = null;
int number = GetJoystickNumber(Path.GetFileName(path)); int number = GetJoystickNumber(Path.GetFileName(path));
if (number >= 0) if (number >= 0)
@ -235,123 +277,186 @@ namespace OpenTK.Platform.Linux
if (fd == -1) if (fd == -1)
return null; return null;
// Check joystick driver version (must be 1.0+) unsafe
int driver_version = 0x00000800;
Libc.ioctl(fd, JoystickIoctlCode.Version, ref driver_version);
if (driver_version < 0x00010000)
return null;
// Get number of joystick axes
int axes = 0;
Libc.ioctl(fd, JoystickIoctlCode.Axes, ref axes);
// Get number of joystick buttons
int buttons = 0;
Libc.ioctl(fd, JoystickIoctlCode.Buttons, ref buttons);
stick = new JoystickDevice<LinuxJoyDetails>(number, axes, buttons);
StringBuilder sb = new StringBuilder(128);
Libc.ioctl(fd, JoystickIoctlCode.Name128, sb);
stick.Description = sb.ToString();
stick.Details.FileDescriptor = fd;
stick.Details.State.SetIsConnected(true);
stick.Details.Guid = CreateGuid(stick, path, number);
// Find the first disconnected joystick (if any)
int i;
for (i = 0; i < sticks.Count; i++)
{ {
if (!sticks[i].Details.State.IsConnected) const int evsize = Evdev.EventCount / 8;
const int axissize = Evdev.AxisCount / 8;
const int keysize = Evdev.KeyCount / 8;
byte* evbit = stackalloc byte[evsize];
byte* axisbit = stackalloc byte[axissize];
byte* keybit = stackalloc byte[keysize];
string name;
EvdevInputId id;
// Ensure this is a joystick device
bool is_valid = true;
is_valid &= Evdev.GetBit(fd, 0, evsize, new IntPtr(evbit)) >= 0;
is_valid &= Evdev.GetBit(fd, EvdevType.ABS, axissize, new IntPtr(axisbit)) >= 0;
is_valid &= Evdev.GetBit(fd, EvdevType.KEY, keysize, new IntPtr(keybit)) >= 0;
is_valid &= TestBit(evbit, (int)EvdevType.KEY);
is_valid &= TestBit(evbit, (int)EvdevType.ABS);
is_valid &= TestBit(axisbit, (int)EvdevAxis.X);
is_valid &= TestBit(axisbit, (int)EvdevAxis.Y);
is_valid &= Evdev.GetName(fd, out name) >= 0;
is_valid &= Evdev.GetId(fd, out id) >= 0;
if (is_valid)
{ {
break; stick = new LinuxJoystickDetails
{
FileDescriptor = fd,
PathIndex = number,
State = new JoystickState(),
Name = name,
Guid = CreateGuid(id, name),
};
int axes, buttons, hats;
QueryCapabilities(stick, axisbit, axissize, keybit, keysize,
out axes, out buttons, out hats);
stick.Caps = new JoystickCapabilities(axes, buttons, hats, false);
// Poll the joystick once, to initialize its state
PollJoystick(stick);
} }
} }
// If no disconnected joystick exists, append a new slot
if (i == sticks.Count)
{
sticks.Add(stick);
}
else
{
sticks[i] = stick;
}
// Map player index to joystick
index_to_stick.Add(index_to_stick.Count, i);
Debug.Print("Found joystick on path {0}", path); Debug.Print("Found joystick on path {0}", path);
} }
catch (Exception e)
{
Debug.Print("Error opening joystick: {0}", e.ToString());
}
finally finally
{ {
if (stick == null && fd != -1) if (stick == null && fd != -1)
{
// Not a joystick
Libc.close(fd); Libc.close(fd);
}
} }
} }
return stick; return stick;
} }
void CloseJoystick(JoystickDevice<LinuxJoyDetails> js) void CloseJoystick(LinuxJoystickDetails js)
{ {
Libc.close(js.Details.FileDescriptor); Sticks.Remove(js.FileDescriptor);
js.Details.State = new JoystickState(); // clear joystick state
js.Details.FileDescriptor = -1;
// find and remove the joystick index from index_to_stick Libc.close(js.FileDescriptor);
int key = -1; js.FileDescriptor = -1;
foreach (int i in index_to_stick.Keys) js.State = new JoystickState(); // clear joystick state
{ js.Caps = new JoystickCapabilities();
if (sticks[index_to_stick[i]] == js)
{
key = i;
break;
}
}
if (index_to_stick.ContainsKey(key))
{
index_to_stick.Remove(key);
}
} }
void PollJoystick(JoystickDevice<LinuxJoyDetails> js) JoystickHatState TranslateHat(int x, int y)
{ {
JoystickEvent e; return new JoystickHatState(HatPositions[x, y]);
}
void PollJoystick(LinuxJoystickDetails js)
{
unsafe unsafe
{ {
while ((long)Libc.read(js.Details.FileDescriptor, (void*)&e, (UIntPtr)sizeof(JoystickEvent)) > 0) const int EventCount = 32;
InputEvent* events = stackalloc InputEvent[EventCount];
long length = 0;
while (true)
{ {
e.Type &= ~JoystickEventType.Init; length = (long)Libc.read(js.FileDescriptor, (void*)events, (UIntPtr)(sizeof(InputEvent) * EventCount));
if (length <= 0)
break;
switch (e.Type) // Only mark the joystick as connected when we actually start receiving events.
// Otherwise, the Xbox wireless receiver will register 4 joysticks even if no
// actual joystick is connected to the receiver.
js.Caps.SetIsConnected(true);
js.State.SetIsConnected(true);
length /= sizeof(InputEvent);
for (int i = 0; i < length; i++)
{ {
case JoystickEventType.Axis: InputEvent *e = events + i;
// Flip vertical axes so that +1 point up. switch (e->Type)
if (e.Number % 2 == 0) {
js.Details.State.SetAxis((JoystickAxis)e.Number, e.Value); case EvdevType.ABS:
else {
js.Details.State.SetAxis((JoystickAxis)e.Number, unchecked((short)-e.Value)); AxisInfo axis;
break; 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 JoystickEventType.Button: case 1:
js.Details.State.SetButton((JoystickButton)e.Number, e.Value != 0); // Y-axis
break; 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:
{
JoystickButton button;
if (js.ButtonMap.TryGetValue((EvdevButton)e->Code, out button))
{
js.State.SetButton(button, e->Value != 0);
}
break;
}
}
// 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(packet);
} }
js.Details.State.SetPacketNumber(unchecked((int)e.Time));
} }
} }
} }
bool IsValid(int index)
{
return index_to_stick.ContainsKey(index);
}
static readonly string JoystickPath = "/dev/input"; static readonly string JoystickPath = "/dev/input";
static readonly string JoystickPathLegacy = "/dev"; static readonly string JoystickPathLegacy = "/dev";
@ -374,7 +479,7 @@ namespace OpenTK.Platform.Linux
} }
watcher.Dispose(); watcher.Dispose();
foreach (JoystickDevice<LinuxJoyDetails> js in sticks) foreach (LinuxJoystickDetails js in Sticks)
{ {
CloseJoystick(js); CloseJoystick(js);
} }
@ -394,39 +499,33 @@ namespace OpenTK.Platform.Linux
JoystickState IJoystickDriver2.GetState(int index) JoystickState IJoystickDriver2.GetState(int index)
{ {
if (IsValid(index)) LinuxJoystickDetails js = Sticks.FromIndex(index);
if (js != null)
{ {
JoystickDevice<LinuxJoyDetails> js =
sticks[index_to_stick[index]];
PollJoystick(js); PollJoystick(js);
return js.Details.State; return js.State;
} }
return new JoystickState(); return new JoystickState();
} }
JoystickCapabilities IJoystickDriver2.GetCapabilities(int index) JoystickCapabilities IJoystickDriver2.GetCapabilities(int index)
{ {
JoystickCapabilities caps = new JoystickCapabilities(); LinuxJoystickDetails js = Sticks.FromIndex(index);
if (IsValid(index)) if (js != null)
{ {
JoystickDevice<LinuxJoyDetails> js = sticks[index_to_stick[index]]; return js.Caps;
caps = new JoystickCapabilities(
js.Axis.Count,
js.Button.Count,
0, // hats not supported by /dev/js
js.Details.State.IsConnected);
} }
return caps; return new JoystickCapabilities();
} }
Guid IJoystickDriver2.GetGuid(int index) Guid IJoystickDriver2.GetGuid(int index)
{ {
if (IsValid(index)) LinuxJoystickDetails js = Sticks.FromIndex(index);
if (js != null)
{ {
JoystickDevice<LinuxJoyDetails> js = sticks[index_to_stick[index]]; return js.Guid;
return js.Details.Guid;
} }
return new Guid(); return Guid.Empty;
} }
#endregion #endregion