using System;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using OpenTK.Input;
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[]
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;
+ }
+
+ 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 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 : uint
+ enum EvdevButton
{
MISC = 0x100,
BTN0 = 0x100,
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 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;
}
}
namespace OpenTK.Platform.Linux
{
- struct LinuxJoyDetails
+ 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, JoystickAxis> AxisMap =
+ new Dictionary<EvdevAxis, JoystickAxis>();
+ public readonly Dictionary<EvdevButton, JoystickButton> ButtonMap =
+ new Dictionary<EvdevButton, JoystickButton>();
+ public readonly Dictionary<int, JoystickHat> HatMap =
+ new Dictionary<int, JoystickHat>();
}
- // Note: despite what the name says, this class is Linux-specific.
sealed class LinuxJoystick : IJoystickDriver2
{
#region Fields
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;
{
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);
}
}
}
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;
}
int number = GetJoystickNumber(file);
if (number != -1)
{
- // Find which joystick id matches this number
- int i;
- for (i = 0; i < sticks.Count; i++)
- {
- if (sticks[i].Id == number)
- {
- break;
- }
- }
-
- if (i == sticks.Count)
- {
- Debug.Print("[Evdev] Joystick id {0} does not exist.", number);
- }
- else
+ var stick = Sticks.FromHardwareId(number);
+ if (stick != null)
{
- CloseJoystick(sticks[i]);
+ CloseJoystick(stick);
+ Sticks.TryRemove(number);
}
}
}
#region Private Members
- Guid CreateGuid(JoystickDevice<LinuxJoyDetails> js, string path, int number)
+ Guid CreateGuid(EvdevInputId id, string name)
{
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(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
{
- bytes[i] = (byte)js.Description[i];
+ 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();
+ unsafe static bool TestBit(byte* ptr, int bit)
+ {
+ int byte_offset = bit / 8;
+ int bit_offset = bit % 8;
+ return (*(ptr + byte_offset) & (1 << bit_offset)) != 0;
+ }
- try
+ unsafe static int AddAxes(LinuxJoystickDetails stick, byte* axisbit, int bytecount)
+ {
+ JoystickAxis axes = 0;
+ JoystickHat hats = 0;
+ int bitcount = bytecount * 8;
+ for (EvdevAxis axis = 0; axis < EvdevAxis.CNT && (int)axis < bitcount; axis++)
{
- 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 (axis >= EvdevAxis.HAT0X && axis <= EvdevAxis.HAT3Y)
+ {
+ // Axis is analogue hat - skip
+ continue;
+ }
- if (id.Vendor != 0 && id.Product != 0 && id.Version != 0)
+ if (TestBit(axisbit, (int)axis))
{
- 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;
+ stick.AxisMap.Add(axis, axes++);
}
else
{
- for (; i < bytes.Length; i++)
- {
- bytes[i] = (byte)js.Description[i];
- }
+ stick.AxisMap.Add(axis, (JoystickAxis)(-1));
}
+ }
+ return (int)axes;
+ }
- return new Guid(bytes);
+ unsafe static int AddButtons(LinuxJoystickDetails stick, byte* keybit, int bytecount)
+ {
+ 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))
+ {
+ stick.ButtonMap.Add(button, buttons++);
+ }
+ else
+ {
+ stick.ButtonMap.Add(button, (JoystickButton)(-1));
+ }
}
- finally
+ 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++)
{
- UnsafeNativeMethods.close(event_fd);
+ if (TestBit(axisbit, (int)hat))
+ {
+ stick.HatMap.Add((int)hat, hats++);
+ }
}
-#endif
+
+ 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;
}
-
- JoystickDevice<LinuxJoyDetails> OpenJoystick(string path)
+
+ LinuxJoystickDetails OpenJoystick(string path)
{
- JoystickDevice<LinuxJoyDetails> stick = null;
+ LinuxJoystickDetails stick = null;
int number = GetJoystickNumber(Path.GetFileName(path));
if (number >= 0)
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;
+ unsafe
+ {
+ 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];
- // Get number of joystick axes
- int axes = 0;
- Libc.ioctl(fd, JoystickIoctlCode.Axes, ref axes);
+ string name;
+ EvdevInputId id;
- // Get number of joystick buttons
- int buttons = 0;
- Libc.ioctl(fd, JoystickIoctlCode.Buttons, ref buttons);
+ // Ensure this is a joystick device
+ bool is_valid = true;
- stick = new JoystickDevice<LinuxJoyDetails>(number, axes, buttons);
+ 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;
- StringBuilder sb = new StringBuilder(128);
- Libc.ioctl(fd, JoystickIoctlCode.Name128, sb);
- stick.Description = sb.ToString();
+ 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);
- 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)
+ 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),
+ };
+ stick.Caps = new JoystickCapabilities(
+ AddAxes(stick, axisbit, axissize),
+ AddButtons(stick, keybit, keysize),
+ AddHats(stick, axisbit, axissize, keybit, keysize),
+ true);
+ stick.State.SetIsConnected(true);
}
}
- // 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;
-
- // 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;
- }
- }
+ Sticks.Remove(js.FileDescriptor);
- 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)
+ void PollJoystick(LinuxJoystickDetails js)
{
- JoystickEvent e;
-
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)
+ 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;
-
- case JoystickEventType.Button:
- js.Details.State.SetButton((JoystickButton)e.Number, e.Value != 0);
- break;
- }
+ InputEvent *e = events + i;
+ switch (e->Type)
+ {
+ case EvdevType.ABS:
+ break;
+
+ case EvdevType.KEY:
+ break;
+ }
- js.Details.State.SetPacketNumber(unchecked((int)e.Time));
+ //js.State.SetPacketNumber(unchecked((int)e->Time.Seconds));
+ }
}
}
}
- bool IsValid(int index)
- {
- return index_to_stick.ContainsKey(index);
- }
-
static readonly string JoystickPath = "/dev/input";
static readonly string JoystickPathLegacy = "/dev";
}
watcher.Dispose();
- foreach (JoystickDevice<LinuxJoyDetails> js in sticks)
+ foreach (LinuxJoystickDetails js in Sticks)
{
CloseJoystick(js);
}
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