Merge pull request #181 from thefiddler/evdev
[Linux] Implement evdev joystick driver
This commit is contained in:
commit
2b3a4c578a
4 changed files with 480 additions and 190 deletions
|
@ -175,6 +175,8 @@ namespace OpenTK.Input
|
|||
sb.Append("St");
|
||||
if (BigButton == ButtonState.Pressed)
|
||||
sb.Append("Gd");
|
||||
if (Back == ButtonState.Pressed)
|
||||
sb.Append("Bk");
|
||||
if (LeftShoulder == ButtonState.Pressed)
|
||||
sb.Append("L");
|
||||
if (RightShoulder == ButtonState.Pressed)
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace OpenTK.Platform.Linux
|
||||
|
@ -36,6 +37,10 @@ namespace OpenTK.Platform.Linux
|
|||
// Bindings for linux/input.h
|
||||
class Evdev
|
||||
{
|
||||
public const int KeyCount = 0x300;
|
||||
public const int AxisCount = 0x40;
|
||||
public const int EventCount = (int)EvdevType.CNT;
|
||||
|
||||
#region KeyMap
|
||||
|
||||
public static readonly Key[] KeyMap = new Key[]
|
||||
|
@ -365,9 +370,118 @@ namespace OpenTK.Platform.Linux
|
|||
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,
|
||||
BTN0 = 0x100,
|
||||
|
@ -447,6 +561,75 @@ namespace OpenTK.Platform.Linux
|
|||
WHEEL = 0x150,
|
||||
GEAR_DOWN = 0x150,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,10 @@ namespace OpenTK.Platform.Linux
|
|||
public static extern int ioctl(int d, JoystickIoctlCode request, StringBuilder data);
|
||||
|
||||
[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)]
|
||||
public static extern int ioctl(int d, KeyboardIoctlCode request, ref IntPtr data);
|
||||
|
@ -106,6 +109,14 @@ namespace OpenTK.Platform.Linux
|
|||
InvalidValue = 22,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum DirectionFlags
|
||||
{
|
||||
None = 0,
|
||||
Write = 1,
|
||||
Read = 2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum OpenFlags
|
||||
{
|
||||
|
@ -116,11 +127,6 @@ namespace OpenTK.Platform.Linux
|
|||
CloseOnExec = 0x0080000
|
||||
}
|
||||
|
||||
enum EvdevIoctlCode : uint
|
||||
{
|
||||
Id = ((byte)'E' << 8) | (0x02 << 0) //EVIOCGID, which is _IOR('E', 0x02, struct input_id)
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum JoystickEventType : byte
|
||||
{
|
||||
|
|
|
@ -35,24 +35,44 @@ using OpenTK.Input;
|
|||
|
||||
namespace OpenTK.Platform.Linux
|
||||
{
|
||||
struct LinuxJoyDetails
|
||||
struct AxisInfo
|
||||
{
|
||||
public Guid Guid;
|
||||
public int FileDescriptor;
|
||||
public JoystickState State;
|
||||
public JoystickAxis Axis;
|
||||
public InputAbsInfo Info;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
#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 FileSystemWatcher watcher = new FileSystemWatcher();
|
||||
|
||||
readonly Dictionary<int, int> index_to_stick = new Dictionary<int, int>();
|
||||
List<JoystickDevice<LinuxJoyDetails>> sticks = new List<JoystickDevice<LinuxJoyDetails>>();
|
||||
readonly DeviceCollection<LinuxJoystickDetails> Sticks =
|
||||
new DeviceCollection<LinuxJoystickDetails>();
|
||||
|
||||
bool disposed;
|
||||
|
||||
|
@ -89,12 +109,10 @@ namespace OpenTK.Platform.Linux
|
|||
{
|
||||
foreach (string file in Directory.GetFiles(path))
|
||||
{
|
||||
JoystickDevice<LinuxJoyDetails> stick = OpenJoystick(file);
|
||||
LinuxJoystickDetails stick = OpenJoystick(file);
|
||||
if (stick != null)
|
||||
{
|
||||
//stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons, {3}{0})",
|
||||
//number, stick.Axis.Count, stick.Button.Count, path);
|
||||
sticks.Add(stick);
|
||||
Sticks.Add(stick.PathIndex, stick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,10 +120,11 @@ namespace OpenTK.Platform.Linux
|
|||
|
||||
int GetJoystickNumber(string path)
|
||||
{
|
||||
if (path.StartsWith("js"))
|
||||
const string evdev = "event";
|
||||
if (path.StartsWith(evdev))
|
||||
{
|
||||
int num;
|
||||
if (Int32.TryParse(path.Substring(2), out num))
|
||||
if (Int32.TryParse(path.Substring(evdev.Length), out num))
|
||||
{
|
||||
return num;
|
||||
}
|
||||
|
@ -129,23 +148,10 @@ namespace OpenTK.Platform.Linux
|
|||
int number = GetJoystickNumber(file);
|
||||
if (number != -1)
|
||||
{
|
||||
// Find which joystick id matches this number
|
||||
int i;
|
||||
for (i = 0; i < sticks.Count; i++)
|
||||
var stick = Sticks.FromHardwareId(number);
|
||||
if (stick != null)
|
||||
{
|
||||
if (sticks[i].Id == number)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == sticks.Count)
|
||||
{
|
||||
Debug.Print("[Evdev] Joystick id {0} does not exist.", number);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseJoystick(sticks[i]);
|
||||
CloseJoystick(stick);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -155,75 +161,111 @@ namespace OpenTK.Platform.Linux
|
|||
|
||||
#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];
|
||||
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);
|
||||
|
||||
#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));
|
||||
if (number >= 0)
|
||||
|
@ -235,123 +277,186 @@ namespace OpenTK.Platform.Linux
|
|||
if (fd == -1)
|
||||
return null;
|
||||
|
||||
// Check joystick driver version (must be 1.0+)
|
||||
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++)
|
||||
unsafe
|
||||
{
|
||||
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);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Print("Error opening joystick: {0}", e.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (stick == null && fd != -1)
|
||||
{
|
||||
// Not a joystick
|
||||
Libc.close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stick;
|
||||
}
|
||||
|
||||
void CloseJoystick(JoystickDevice<LinuxJoyDetails> js)
|
||||
void CloseJoystick(LinuxJoystickDetails js)
|
||||
{
|
||||
Libc.close(js.Details.FileDescriptor);
|
||||
js.Details.State = new JoystickState(); // clear joystick state
|
||||
js.Details.FileDescriptor = -1;
|
||||
Sticks.Remove(js.FileDescriptor);
|
||||
|
||||
// find and remove the joystick index from index_to_stick
|
||||
int key = -1;
|
||||
foreach (int i in index_to_stick.Keys)
|
||||
{
|
||||
if (sticks[index_to_stick[i]] == js)
|
||||
{
|
||||
key = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index_to_stick.ContainsKey(key))
|
||||
{
|
||||
index_to_stick.Remove(key);
|
||||
}
|
||||
Libc.close(js.FileDescriptor);
|
||||
js.FileDescriptor = -1;
|
||||
js.State = new JoystickState(); // clear joystick state
|
||||
js.Caps = new JoystickCapabilities();
|
||||
}
|
||||
|
||||
void PollJoystick(JoystickDevice<LinuxJoyDetails> js)
|
||||
JoystickHatState TranslateHat(int x, int y)
|
||||
{
|
||||
JoystickEvent e;
|
||||
return new JoystickHatState(HatPositions[x, y]);
|
||||
}
|
||||
|
||||
void PollJoystick(LinuxJoystickDetails js)
|
||||
{
|
||||
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:
|
||||
// Flip vertical axes so that +1 point up.
|
||||
if (e.Number % 2 == 0)
|
||||
js.Details.State.SetAxis((JoystickAxis)e.Number, e.Value);
|
||||
else
|
||||
js.Details.State.SetAxis((JoystickAxis)e.Number, unchecked((short)-e.Value));
|
||||
break;
|
||||
InputEvent *e = events + i;
|
||||
switch (e->Type)
|
||||
{
|
||||
case EvdevType.ABS:
|
||||
{
|
||||
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 JoystickEventType.Button:
|
||||
js.Details.State.SetButton((JoystickButton)e.Number, e.Value != 0);
|
||||
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:
|
||||
{
|
||||
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 JoystickPathLegacy = "/dev";
|
||||
|
||||
|
@ -374,7 +479,7 @@ namespace OpenTK.Platform.Linux
|
|||
}
|
||||
|
||||
watcher.Dispose();
|
||||
foreach (JoystickDevice<LinuxJoyDetails> js in sticks)
|
||||
foreach (LinuxJoystickDetails js in Sticks)
|
||||
{
|
||||
CloseJoystick(js);
|
||||
}
|
||||
|
@ -394,39 +499,33 @@ namespace OpenTK.Platform.Linux
|
|||
|
||||
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);
|
||||
return js.Details.State;
|
||||
return js.State;
|
||||
}
|
||||
return new JoystickState();
|
||||
}
|
||||
|
||||
JoystickCapabilities IJoystickDriver2.GetCapabilities(int index)
|
||||
{
|
||||
JoystickCapabilities caps = new JoystickCapabilities();
|
||||
if (IsValid(index))
|
||||
LinuxJoystickDetails js = Sticks.FromIndex(index);
|
||||
if (js != null)
|
||||
{
|
||||
JoystickDevice<LinuxJoyDetails> js = sticks[index_to_stick[index]];
|
||||
caps = new JoystickCapabilities(
|
||||
js.Axis.Count,
|
||||
js.Button.Count,
|
||||
0, // hats not supported by /dev/js
|
||||
js.Details.State.IsConnected);
|
||||
return js.Caps;
|
||||
}
|
||||
return caps;
|
||||
return new JoystickCapabilities();
|
||||
}
|
||||
|
||||
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.Details.Guid;
|
||||
return js.Guid;
|
||||
}
|
||||
return new Guid();
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
Loading…
Reference in a new issue