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");
|
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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue