[Linux] Implemented evdev joystick device discovery
authorthefiddler <stapostol@gmail.com>
Thu, 7 Aug 2014 16:08:53 +0000 (18:08 +0200)
committerthefiddler <stapostol@gmail.com>
Wed, 17 Sep 2014 23:20:21 +0000 (01:20 +0200)
Source/OpenTK/Platform/Linux/Bindings/Evdev.cs
Source/OpenTK/Platform/Linux/Bindings/Libc.cs
Source/OpenTK/Platform/Linux/LinuxJoystick.cs

index 11988eb..e37cce7 100644 (file)
@@ -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,101 @@ 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;
+        }
+
+        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,
@@ -447,6 +544,64 @@ 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 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;
     }
 }
 
index 4b29624..1131875 100644 (file)
@@ -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);
@@ -107,6 +110,14 @@ namespace OpenTK.Platform.Linux
     }
 
     [Flags]
+    enum DirectionFlags
+    {
+        None = 0,
+        Write = 1,
+        Read = 2
+    }
+
+    [Flags]
     enum OpenFlags
     {
         ReadOnly = 0x0000,
@@ -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
     {
index 5199279..39a2f88 100644 (file)
@@ -35,14 +35,23 @@ using OpenTK.Input;
 
 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
@@ -51,8 +60,8 @@ namespace OpenTK.Platform.Linux
 
         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 +98,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 +109,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 +137,11 @@ 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++)
-                    {
-                        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);
                     }
                 }
             }
@@ -155,75 +151,129 @@ namespace OpenTK.Platform.Linux
 
         #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)
@@ -235,123 +285,114 @@ 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;
+                    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";
 
@@ -374,7 +415,7 @@ namespace OpenTK.Platform.Linux
                 }
 
                 watcher.Dispose();
-                foreach (JoystickDevice<LinuxJoyDetails> js in sticks)
+                foreach (LinuxJoystickDetails js in Sticks)
                 {
                     CloseJoystick(js);
                 }
@@ -394,39 +435,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