[Linux] Implemented TTY and libinput keyboard drivers
authorthefiddler <stapostol@gmail.com>
Mon, 14 Jul 2014 15:42:17 +0000 (15:42 +0000)
committerthefiddler <stapostol@gmail.com>
Wed, 16 Jul 2014 12:28:27 +0000 (14:28 +0200)
Source/OpenTK/OpenTK.csproj
Source/OpenTK/Platform/Linux/Bindings/LibInput.cs
Source/OpenTK/Platform/Linux/Bindings/Libc.cs
Source/OpenTK/Platform/Linux/Bindings/Poll.cs
Source/OpenTK/Platform/Linux/Bindings/Terminal.cs [new file with mode: 0644]
Source/OpenTK/Platform/Linux/Bindings/Udev.cs
Source/OpenTK/Platform/Linux/LinuxFactory.cs
Source/OpenTK/Platform/Linux/LinuxInput.cs [new file with mode: 0644]
Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs [deleted file]
Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs

index b8a4763..76514e6 100644 (file)
     <Compile Include="Platform\Linux\Bindings\Poll.cs" />
     <Compile Include="Platform\Linux\LinuxKeyboardTTY.cs" />
     <Compile Include="Platform\Linux\Bindings\Udev.cs" />
-    <Compile Include="Platform\Linux\LinuxKeyboardLibInput.cs" />
     <Compile Include="Platform\Linux\Bindings\LibInput.cs" />
+    <Compile Include="Platform\Linux\LinuxInput.cs" />
+    <Compile Include="Platform\Linux\Bindings\Terminal.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
index aadf4aa..b5cdd9e 100644 (file)
@@ -27,7 +27,7 @@
 //
 #endregion
 
-#pragma warning disable 0169
+#pragma warning disable 0169, 0219
 
 using System;
 using System.Diagnostics;
@@ -48,6 +48,60 @@ namespace OpenTK.Platform.Linux
         [DllImport(lib, EntryPoint = "libinput_udev_create_for_seat", CallingConvention = CallingConvention.Cdecl)]
         public static extern IntPtr CreateContext(InputInterface @interface,
             IntPtr user_data, IntPtr udev, string seat_id);
+
+        [DllImport(lib, EntryPoint = "libinput_destroy", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void DestroyContext(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_event_destroy", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void DestroyEvent(IntPtr @event);
+
+        [DllImport(lib, EntryPoint = "libinput_dispatch", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int Dispatch(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_get_event", CallingConvention = CallingConvention.Cdecl)]
+        public static extern IntPtr GetEvent(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_event_get_type", CallingConvention = CallingConvention.Cdecl)]
+        public static extern InputEventType GetEventType(IntPtr @event);
+
+        [DllImport(lib, EntryPoint = "libinput_get_fd", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int GetFD(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_next_event_type", CallingConvention = CallingConvention.Cdecl)]
+        public static extern InputEventType NextEventType(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_resume", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void Resume(IntPtr libinput);
+
+        [DllImport(lib, EntryPoint = "libinput_suspend", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void Suspend(IntPtr libinput);
+    }
+
+    enum InputEventType
+    {
+        None = 0,
+
+        DeviceAdded,
+        DeviceRemoved,
+
+        KeyboardKey = 300,
+
+        PointerMotion = 400,
+        PointerMotionAbsolute,
+        PointerButton,
+        PointerAxis,
+
+        TouchDown = 500,
+        TouchUP,
+        TouchMotion,
+        TouchCancel,
+
+        /// \internal
+        /// <summary>
+        /// Signals the end of a set of touchpoints at one device sample
+        /// time. This event has no coordinate information attached.
+        /// </summary>
+        TouchFrame
     }
 
     [StructLayout(LayoutKind.Sequential)]
@@ -66,27 +120,6 @@ namespace OpenTK.Platform.Linux
             open = Marshal.GetFunctionPointerForDelegate(open_restricted);
             close = Marshal.GetFunctionPointerForDelegate(close_restricted);
         }
-
-        #region Default implementation
-
-        static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler;
-        static void CloseRestrictedHandler(int fd, IntPtr data)
-        {
-            Debug.Print("[Input] Closing fd {0}", fd);
-            Libc.close(fd);
-        }
-
-        static OpenRestrictedCallback OpenRestricted = OpenRestrictedHandler;
-        static int OpenRestrictedHandler(IntPtr path, int flags, IntPtr data) 
-        {
-            Debug.Print("[Input] Opening path '{0}'", Marshal.PtrToStringAnsi(path));
-            return Libc.open(path, (OpenFlags)flags);
-        }
-
-        public static readonly InputInterface Default = new InputInterface(
-            OpenRestricted, CloseRestricted);
-
-        #endregion
     }
 }
 
index fc77953..264bab2 100644 (file)
@@ -47,6 +47,12 @@ namespace OpenTK.Platform.Linux
         public static extern int ioctl(int d, EvdevIoctlCode request, out EvdevInputId data);
 
         [DllImport(lib)]
+        public static extern int ioctl(int d, KeyboardIoctlCode request, ref IntPtr data);
+
+        [DllImport(lib)]
+        public static extern int ioctl(int d, KeyboardIoctlCode request, int data);
+
+        [DllImport(lib)]
         public static extern int open([MarshalAs(UnmanagedType.LPStr)]string pathname, OpenFlags flags);
 
         [DllImport(lib)]
@@ -58,6 +64,28 @@ namespace OpenTK.Platform.Linux
         [DllImport(lib)]
         unsafe public static extern IntPtr read(int fd, void* buffer, UIntPtr count);
 
+        public static int read(int fd, out byte b)
+        {
+            unsafe
+            {
+                fixed (byte* pb = &b)
+                {
+                    return read(fd, pb, (UIntPtr)1).ToInt32();
+                }
+            }
+        }
+
+        public static int read(int fd, out short s)
+        {
+            unsafe
+            {
+                fixed (short* ps = &s)
+                {
+                    return read(fd, ps, (UIntPtr)2).ToInt32();
+                }
+            }
+        }
+
         [DllImport(lib)]
         [return: MarshalAs(UnmanagedType.Bool)]
         public static extern bool isatty(int fd);
@@ -94,6 +122,12 @@ namespace OpenTK.Platform.Linux
         Name128 = (2u << 30) | (0x6A << 8) | (0x13 << 0) | (128 << 16) //JSIOCGNAME(128), which is _IOC(_IO_READ, 'j', 0x13, len)
     }
 
+    enum KeyboardIoctlCode
+    {
+        GetMode = 0x4b44,
+        SetMode = 0x4b45,
+    }
+
     [StructLayout(LayoutKind.Sequential)]
     struct Stat
     {
index 6796369..a048398 100644 (file)
@@ -34,7 +34,7 @@ namespace OpenTK.Platform.Linux
 {
     partial class Libc
     {
-        [DllImport(lib)]
+        [DllImport(lib, CallingConvention = CallingConvention.Cdecl)]
         public static extern int poll(ref PollFD fd, IntPtr fd_count, int timeout);
 
         public static int poll(ref PollFD fd, int fd_count, int timeout)
diff --git a/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs
new file mode 100644 (file)
index 0000000..8ed7257
--- /dev/null
@@ -0,0 +1,166 @@
+#region License
+//
+// Terminal.cs
+//
+// Author:
+//       Stefanos A. <stapostol@gmail.com>
+//
+// Copyright (c) 2006-2014 Stefanos Apostolopoulos
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+#endregion
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace OpenTK.Platform.Linux
+{
+    class Terminal
+    {
+        const string lib = "libc";
+
+        [DllImport(lib, EntryPoint = "tcgetattr", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int GetAttributes(int fd, out TerminalState state);
+
+        [DllImport(lib, EntryPoint = "tcsetattr", CallingConvention = CallingConvention.Cdecl)]
+        public static extern int SetAttributes(int fd, OptionalActions actions, ref TerminalState state);
+    }
+
+    [Flags]
+    enum InputFlags
+    {
+        IGNBRK = 1 << 0,
+        BRKINT = 1 << 1,
+        IGNPAR = 1 << 2,
+        PARMRK = 1 << 3,
+        INPCK  = 1 << 4,
+        ISTRIP = 1 << 5,
+        INLCR  = 1 << 6,
+        IGNCR  = 1 << 7,
+        ICRNL  = 1 << 8,
+        IUCLC  = 1 << 9,
+        IXON   = 1 << 10,
+        IXANY  = 1 << 11,
+        IXOFF  = 1 << 12,
+        IMAXBEL = 1 << 13,
+        IUTF8  = 1 << 14,
+    }
+
+    [Flags]
+    enum OutputFlags
+    {
+        OPOST  = 1 << 1,
+        OLCUC  = 1 << 2,
+        ONLCR  = 1 << 3,
+        OCRNL  = 1 << 4,
+        ONOCR  = 1 << 5,
+        ONLRET = 1 << 6,
+        OFILL  = 1 << 7,
+        OFDEL  = 1 << 8,
+    }
+
+    [Flags]
+    enum ControlFlags
+    {
+        B0 = 0, // hang up
+        B50,
+        B75,
+        B110,
+        B134,
+        B150,
+        B200,
+        B300,
+        B600,
+        B1200,
+        B1800,
+        B2400,
+        B4800,
+        B9600,
+        B19200,
+        B38400,
+    }
+
+    [Flags]
+    enum LocalFlags
+    {
+        ISIG = 0x01,
+        ICANON = 0x02,
+        ECHO = 0x08,
+    }
+
+    enum OptionalActions
+    {
+        NOW = 0,
+        DRAIN = 1,
+        FLUSH = 2
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct TerminalState
+    {
+        public InputFlags InputMode;
+        public OutputFlags OutputMode;
+        public ControlFlags ControlMode;
+        public LocalFlags LocalMode;
+        public byte LineDiscipline;
+        public ControlCharacters ControlCharacters;
+        public int InputSpeed;
+        public int OutputSpeed;
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
+    struct ControlCharacters
+    {
+        public byte VINTR;
+        public byte VQUIT;
+        public byte VERASE;
+        public byte VKILL;
+        public byte VEOF;
+        public byte VTIME;
+        public byte VMIN;
+        public byte VSWTC;
+        public byte VSTART;
+        public byte VSTOP;
+        public byte VSUSP;
+        public byte VEOL;
+        public byte VREPRINT;
+        public byte VDISCARD;
+        public byte VWERASE;
+        public byte VLNEXT;
+        public byte VEOL2;
+        public byte C17;
+        public byte C18;
+        public byte C19;
+        public byte C20;
+        public byte C21;
+        public byte C22;
+        public byte C23;
+        public byte C24;
+        public byte C25;
+        public byte C26;
+        public byte C27;
+        public byte C28;
+        public byte C29;
+        public byte C30;
+        public byte C31;
+
+    }
+}
+
index 0f674f5..4c341b7 100644 (file)
@@ -36,8 +36,11 @@ namespace OpenTK.Platform.Linux
     {
         const string lib = "libudev";
 
-        [DllImport(lib, EntryPoint = "udev_new")]
+        [DllImport(lib, EntryPoint = "udev_new", CallingConvention = CallingConvention.Cdecl)]
         public static extern IntPtr New();
+
+        [DllImport(lib, EntryPoint = "udev_destroy", CallingConvention = CallingConvention.Cdecl)]
+        public static extern void Destroy(IntPtr Udev);
     }
 }
 
index f9d9c0a..8d0d8e2 100644 (file)
@@ -217,7 +217,7 @@ namespace OpenTK.Platform.Linux
             lock (this)
             {
                 KeyboardDriver = KeyboardDriver ??
-                    (IKeyboardDriver2)new LinuxKeyboardLibInput() ??
+                    // Todo: use LinuxInput driver if available?
                     (IKeyboardDriver2)new LinuxKeyboardTTY();
                 return KeyboardDriver;
             }
diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs
new file mode 100644 (file)
index 0000000..19081c9
--- /dev/null
@@ -0,0 +1,226 @@
+#region License
+//
+// LinuxKeyboardLibInput.cs
+//
+// Author:
+//       Stefanos A. <stapostol@gmail.com>
+//
+// Copyright (c) 2006-2014 Stefanos Apostolopoulos
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+#endregion
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using OpenTK.Input;
+
+namespace OpenTK.Platform.Linux
+{
+    class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable
+    {
+        IntPtr udev;
+        IntPtr input_context;
+        InputInterface input_interface = new InputInterface(
+            OpenRestricted, CloseRestricted);
+        int fd;
+        Thread input_thread;
+        long exit;
+
+        public LinuxInput()
+        {
+            Debug.Print("[Linux] Initializing {0}", GetType().Name);
+
+            input_thread = new Thread(ProcessEvents);
+            input_thread.IsBackground = true;
+
+            // Todo: add static path fallback when udev is not installed.
+            udev = Udev.New();
+            if (udev == IntPtr.Zero)
+            {
+                throw new NotSupportedException("[Input] Udev.New() failed.");
+            }
+
+            input_context = LibInput.CreateContext(input_interface,
+                IntPtr.Zero, udev, "seat0");
+            if (input_context == IntPtr.Zero)
+            {
+                throw new NotSupportedException(
+                    String.Format("[Input] LibInput.CreateContext({0:x}) failed.", udev));
+            }
+
+            fd = LibInput.GetFD(input_context);
+            if (fd < 0)
+            {
+                throw new NotSupportedException(
+                    String.Format("[Input] LibInput.GetFD({0:x}) failed.", input_context));
+            }
+
+            input_thread.Start();
+        }
+
+        #region Private Members
+
+        static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler;
+        static void CloseRestrictedHandler(int fd, IntPtr data)
+        {
+            Debug.Print("[Input] Closing fd {0}", fd);
+            Libc.close(fd);
+        }
+
+        static OpenRestrictedCallback OpenRestricted = OpenRestrictedHandler;
+        static int OpenRestrictedHandler(IntPtr path, int flags, IntPtr data) 
+        {
+            int fd = Libc.open(path, (OpenFlags)flags);
+            Debug.Print("[Input] Opening '{0}' with flags {1}. fd:{2}",
+                Marshal.PtrToStringAnsi(path), (OpenFlags)flags, fd);
+
+            return fd;
+        }
+
+        void ProcessEvents()
+        {
+            PollFD poll_fd = new PollFD();
+            poll_fd.fd = fd;
+            poll_fd.events = PollFlags.In;
+
+            while (Interlocked.Read(ref exit) == 0)
+            {
+                int ret = Libc.poll(ref poll_fd, 1, -1);
+                if (ret > 0 && (poll_fd.revents & PollFlags.In) != 0)
+                {
+                    // Data available
+                    ret = LibInput.Dispatch(input_context);
+                    if (ret != 0)
+                    {
+                        Debug.Print("[Input] LibInput.Dispatch({0:x}) failed. Error: {1}",
+                            input_context, ret);
+                        continue;
+                    }
+
+                    IntPtr pevent = LibInput.GetEvent(input_context);
+                    if (pevent == IntPtr.Zero)
+                    {
+                        Debug.Print("[Input] LibInput.GetEvent({0:x}) failed.",
+                            input_context);
+                        continue;
+                    }
+
+                    InputEventType type = LibInput.GetEventType(pevent);
+                    Debug.Print(type.ToString());
+
+                    LibInput.DestroyEvent(pevent);
+                }
+                else if (ret < 0)
+                {
+                    // An error has occurred
+                    Debug.Print("[Input] Exiting input thread {0} [ret:{1} events:{2}]",
+                        input_thread.ManagedThreadId, ret, poll_fd.revents);
+                    Interlocked.Increment(ref exit);
+                }
+            }
+        }
+
+        #endregion
+
+        #region IKeyboardDriver2 implementation
+
+        KeyboardState IKeyboardDriver2.GetState()
+        {
+            return new KeyboardState();
+        }
+
+        KeyboardState IKeyboardDriver2.GetState(int index)
+        {
+            return new KeyboardState();
+        }
+
+        string IKeyboardDriver2.GetDeviceName(int index)
+        {
+            return String.Empty;
+        }
+
+        #endregion
+
+        #region IMouseDriver2 implementation
+
+        MouseState IMouseDriver2.GetState()
+        {
+            throw new NotImplementedException();
+        }
+
+        MouseState IMouseDriver2.GetState(int index)
+        {
+            throw new NotImplementedException();
+        }
+
+        void IMouseDriver2.SetPosition(double x, double y)
+        {
+            throw new NotImplementedException();
+        }
+
+        MouseState IMouseDriver2.GetCursorState()
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion
+
+        #region IDisposable implementation
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (input_context != IntPtr.Zero)
+                {
+                    LibInput.Suspend(input_context);
+                    Interlocked.Increment(ref exit);
+
+                    LibInput.DestroyContext(input_context);
+                    input_context = IntPtr.Zero;
+                }
+
+                if (udev != IntPtr.Zero)
+                {
+                    Udev.Destroy(udev);
+                    udev = IntPtr.Zero;
+                }
+
+                input_interface = null;
+            }
+        }
+
+        ~LinuxInput()
+        {
+            Dispose(false);
+        }
+
+        #endregion
+    }
+}
+
diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs
deleted file mode 100644 (file)
index d81ceb6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-#region License
-//
-// LinuxKeyboardLibInput.cs
-//
-// Author:
-//       thefiddler <stapostol@gmail.com>
-//
-// Copyright (c) 2006-2014 
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-#endregion
-
-using System;
-using System.Diagnostics;
-using OpenTK.Input;
-
-namespace OpenTK.Platform.Linux
-{
-    class LinuxKeyboardLibInput : IKeyboardDriver2
-    {
-        IntPtr udev;
-        IntPtr input_context;
-        InputInterface input_interface = InputInterface.Default;
-
-        public LinuxKeyboardLibInput()
-        {
-            // Todo: add static path fallback when udev is not installed.
-            udev = Udev.New();
-            if (udev == IntPtr.Zero)
-                throw new NotSupportedException("[Input] Udev.New() failed.");
-
-            input_context = LibInput.CreateContext(input_interface,
-                IntPtr.Zero, udev, "seat0");
-            if (input_context == IntPtr.Zero)
-                throw new NotSupportedException("[Input] Udev.New() failed.");
-
-
-        }
-
-        #region IKeyboardDriver2 implementation
-
-        public KeyboardState GetState()
-        {
-            throw new NotImplementedException();
-        }
-
-        public KeyboardState GetState(int index)
-        {
-            throw new NotImplementedException();
-        }
-
-        public string GetDeviceName(int index)
-        {
-            throw new NotImplementedException();
-        }
-
-        #endregion
-    }
-}
-
index 2d3c848..1ba8d14 100644 (file)
 #endregion
 
 using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
 using OpenTK.Input;
 
 namespace OpenTK.Platform.Linux
 {
-    class LinuxKeyboardTTY : IKeyboardDriver2
+    class LinuxKeyboardTTY : IKeyboardDriver2, IDisposable
     {
+        const int stdin = 0; // STDIN_FILENO
+        readonly object sync = new object();
+        Thread input_thread;
+        long exit;
+        KeyboardState state;
+
+        TerminalState original_state;
+        TerminalState current_state;
+
+        IntPtr original_mode = new IntPtr(-1);
+
         public LinuxKeyboardTTY()
         {
+            if (!SetupTTY(stdin))
+            {
+                throw new NotSupportedException();
+            }
+
+            input_thread = new Thread(ProcessEvents);
+            input_thread.IsBackground = true;
+            input_thread.Start();
+        }
+
+        #region Private Members
+
+        bool SetupTTY(int stdin)
+        {
+            int ret = Terminal.GetAttributes(stdin, out original_state);
+            if (ret < 0)
+            {
+                Debug.Print("[Linux] Terminal.GetAttributes({0}) failed. Error: {1}",
+                    stdin, ret);
+                return false;
+            }
+
+            // Retrieve current keyboard mode
+            ret = Libc.ioctl(stdin, KeyboardIoctlCode.GetMode, ref original_mode);
+            if (ret != 0)
+            {
+                Debug.Print("[Linux] Libc.ioctl({0}, KeyboardIoctlCode.GetMode) failed. Error: {1}",
+                    stdin, ret);
+                return false;
+            }
+
+            // Update terminal state
+            current_state = original_state;
+            current_state.LocalMode &= ~(LocalFlags.ECHO | LocalFlags.ICANON | LocalFlags.ISIG);
+            current_state.InputMode &= ~(
+                InputFlags.ISTRIP | InputFlags.IGNCR | InputFlags.ICRNL |
+                InputFlags.INLCR | InputFlags.IXOFF | InputFlags.IXON);
+            current_state.ControlCharacters.VMIN = 0;
+            current_state.ControlCharacters.VTIME = 0;
+            Terminal.SetAttributes(stdin, OptionalActions.FLUSH, ref current_state);
+
+            // Request keycodes
+            int mode = 0x02; // K_MEDIUMRAW
+            ret = Libc.ioctl(stdin, KeyboardIoctlCode.SetMode, mode);
+            if (ret != 0)
+            {
+                Debug.Print("[Linux] Libc.ioctl({0}, KeyboardIoctlCode.SetMode, {1}) failed. Error: {2}",
+                    stdin, mode, ret);
+                ExitTTY(this, EventArgs.Empty);
+                return false;
+            }
+
+            // Ensure we reset the original keyboard/terminal state on exit,
+            // even if we crash.
+            HookEvents();
+
+            return true;
+        }
+
+        void ExitTTY(object sender, EventArgs e)
+        {
+            if (original_mode != new IntPtr(-1))
+            {
+                Libc.ioctl(stdin, KeyboardIoctlCode.SetMode, ref original_mode);
+                Terminal.SetAttributes(stdin, OptionalActions.FLUSH, ref original_state);
+                original_mode = new IntPtr(-1);
+
+                UnhookEvents();
+            }
+        }
+
+        void HookEvents()
+        {
+            Process.GetCurrentProcess().Exited += ExitTTY;
+            Console.CancelKeyPress += ExitTTY;
         }
 
+        void UnhookEvents()
+        {
+            Process.GetCurrentProcess().Exited -= ExitTTY;
+            Console.CancelKeyPress -= ExitTTY;
+        }
+
+        void ProcessEvents()
+        {
+            state.SetIsConnected(true);
+
+            while (Interlocked.Read(ref exit) == 0)
+            {
+                byte scancode;
+                short extended;
+
+                while (Libc.read(stdin, out scancode) > 0)
+                {
+                    bool pressed = (scancode & 0x80) == 0;
+                    int key = scancode & ~0x80;
+                    KeyModifiers mods;
+                    Debug.Print("{0}:{1} is {2}", key, (int)TranslateKey(key, out mods), pressed);
+
+                    if (key == 0)
+                    {
+                        // This is an extended scancode, ignore
+                        Libc.read(stdin, out extended);
+                    }
+                    else
+                    {
+                        lock (sync)
+                        {
+                            state[(Key)key] = pressed;
+                        }
+                    }
+
+                }
+            }
+
+            input_thread = null;
+        }
+
+        Key TranslateKey(int key, out KeyModifiers mods)
+        {
+            int k = MathHelper.Clamp((int)key, 0, KeyMap.Length);
+            Key result = KeyMap[k];
+            mods = 0;
+            mods |= (result == Key.AltLeft || result == Key.AltRight) ? KeyModifiers.Alt : 0;
+            mods |= (result == Key.ControlLeft || result == Key.ControlRight) ? KeyModifiers.Control : 0;
+            mods |= (result == Key.ShiftLeft || result == Key.ShiftRight) ? KeyModifiers.Shift : 0;
+            return KeyMap[k];
+        }
+
+        #region KeyMap
+
+        static readonly Key[] KeyMap = new Key[]
+        {
+            // 0-7
+            Key.Unknown,
+            Key.Escape,
+            Key.Number1,
+            Key.Number2,
+            Key.Number3,
+            Key.Number4,
+            Key.Number5,
+            Key.Number6,
+            // 8-15
+            Key.Number7,
+            Key.Number8,
+            Key.Number9,
+            Key.Number0,
+            Key.Minus,
+            Key.Plus,
+            Key.BackSpace,
+            Key.Tab,
+            // 16-23
+            Key.Q,
+            Key.W,
+            Key.E,
+            Key.R,
+            Key.T,
+            Key.Y,
+            Key.U,
+            Key.I,
+            // 24-31
+            Key.O,
+            Key.P,
+            Key.BracketLeft,
+            Key.BracketRight,
+            Key.Enter,
+            Key.ControlLeft,
+            Key.A,
+            Key.S,
+            // 32-39
+            Key.D,
+            Key.F,
+            Key.G,
+            Key.H,
+            Key.J,
+            Key.K,
+            Key.L,
+            Key.Semicolon,
+            // 40-47
+            Key.Quote,
+            Key.Tilde,
+            Key.ShiftLeft,
+            Key.BackSlash, //Key.Execute,
+            Key.Z,
+            Key.X,
+            Key.C,
+            Key.V, //Key.Help,
+            // 48-55
+            Key.B,
+            Key.N,
+            Key.M,
+            Key.Comma,
+            Key.Period,
+            Key.Slash,
+            Key.ShiftRight,
+            Key.KeypadMultiply,
+            // 56-63
+            Key.AltLeft,
+            Key.Space,
+            Key.CapsLock,
+            Key.F1,
+            Key.F2,
+            Key.F3,
+            Key.F4,
+            Key.F5,
+            // 64-71
+            Key.F6,
+            Key.F7,
+            Key.F8,
+            Key.F9,
+            Key.F10,
+            Key.NumLock,
+            Key.ScrollLock,
+            Key.Keypad7,
+            // 72-79
+            Key.Keypad8,
+            Key.Keypad9,
+            Key.KeypadSubtract,
+            Key.Keypad4,
+            Key.Keypad5,
+            Key.Keypad6,
+            Key.KeypadPlus,
+            Key.Keypad1,
+            // 80-87
+            Key.Keypad2,
+            Key.Keypad3,
+            Key.Keypad0,
+            Key.KeypadPeriod,
+            Key.Unknown,
+            Key.Unknown, // Zzenkakuhankaku
+            Key.Unknown, // 102ND
+            Key.F11,
+            // 88-95
+            Key.F12,
+            Key.Unknown, // ro
+            Key.Unknown, // katakana
+            Key.Unknown, // hiragana
+            Key.Unknown, // henkan
+            Key.Unknown, // katakanahiragana
+            Key.Unknown, // muhenkan
+            Key.Unknown, // kpjpcomma
+            // 96-103
+            Key.KeypadEnter,
+            Key.ControlRight,
+            Key.KeypadDivide,
+            Key.Unknown, // sysrq
+            Key.AltRight,
+            Key.Unknown, // linefeed
+            Key.Home,
+            Key.Up,
+            // 104-111
+            Key.PageUp,
+            Key.Left,
+            Key.Right,
+            Key.End,
+            Key.Down,
+            Key.PageDown,
+            Key.Insert,
+            Key.Delete,
+            // 112-119
+            Key.Unknown, // macro
+            Key.Unknown, // mute
+            Key.Unknown, // volumedown
+            Key.Unknown, // volumeup
+            Key.Unknown, // power
+            Key.Unknown, // kpequal
+            Key.Unknown, // kpplusminus
+            Key.Pause,
+            // 120-127
+            Key.Unknown, // scale
+            Key.Unknown, // kpcomma
+            Key.Unknown, // hangeul / hanguel
+            Key.Unknown, // hanja
+            Key.Unknown, // yen
+            Key.WinLeft,
+            Key.WinRight,
+            Key.Unknown, // compose
+            // 128-135
+            Key.Unknown, // stop
+            Key.Unknown, // again
+            Key.Unknown, // props
+            Key.Unknown, // undo
+            Key.Unknown, // front
+            Key.Unknown, // copy
+            Key.Unknown, // open
+            Key.Unknown, // paste
+            // 136-143
+            Key.Unknown, // find
+            Key.Unknown, // cut
+            Key.Unknown, // help
+            Key.Unknown, // menu
+            Key.Unknown, // calc
+            Key.Unknown, // setup
+            Key.Unknown, // sleep
+            Key.Unknown, // wakeup
+            // 144-151
+            Key.Unknown, // file
+            Key.Unknown, // send file
+            Key.Unknown, // delete file
+            Key.Unknown, // xfer
+            Key.Unknown, // prog1
+            Key.Unknown, // prog2
+            Key.Unknown, // www
+            Key.Unknown, // msdos
+            // 152-159
+            Key.Unknown, // coffee / screenlock
+            Key.Unknown, // direction
+            Key.Unknown, // cycle windows
+            Key.Unknown, // mail
+            Key.Unknown, // bookmarks
+            Key.Unknown, // computer
+            Key.Back,
+            Key.Unknown, // forward
+            // 160-167
+            Key.Unknown, // close cd
+            Key.Unknown, // eject cd
+            Key.Unknown, // eject/close cd
+            Key.Unknown, // next song
+            Key.Unknown, // play/pause
+            Key.Unknown, // previous song
+            Key.Unknown, // stop cd
+            Key.Unknown, // record
+            // 168-175
+            Key.Unknown, // rewind
+            Key.Unknown, // phone
+            Key.Unknown, // iso
+            Key.Unknown, // config
+            Key.Unknown, // homepage
+            Key.Unknown, // refresh
+            Key.Unknown, // exit
+            Key.Unknown, // move,
+            // 176-183
+            Key.Unknown, // edit,
+            Key.Unknown, // scroll up,
+            Key.Unknown, // scroll down,
+            Key.Unknown, // kp left paren,
+            Key.Unknown, // kp right paren,
+            Key.Unknown, // new,
+            Key.Unknown, // redo,
+            Key.F13,
+            // 184-191
+            Key.F14,
+            Key.F15,
+            Key.F16,
+            Key.F17,
+            Key.F18,
+            Key.F19,
+            Key.F20,
+            Key.F21,
+            // 192-199
+            Key.F22,
+            Key.F23,
+            Key.F24,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            // 200-207
+            Key.Unknown, // play cd
+            Key.Unknown, // pause cd
+            Key.Unknown, // prog3
+            Key.Unknown, // prog4
+            Key.Unknown, // dashboard
+            Key.Unknown, // suspend
+            Key.Unknown, // close
+            Key.Unknown, // play
+            // 208-215
+            Key.Unknown, // fast forward
+            Key.Unknown, // bass boost
+            Key.Unknown, // print
+            Key.Unknown, // hp
+            Key.Unknown, // camera
+            Key.Unknown, // sound
+            Key.Unknown, // question
+            Key.Unknown, // email
+            // 216-223
+            Key.Unknown, // chat
+            Key.Unknown, // search
+            Key.Unknown, // connect
+            Key.Unknown, // finance
+            Key.Unknown, // sport
+            Key.Unknown, // shop
+            Key.Unknown, // alt erase
+            Key.Unknown, // cancel
+            // 224-231
+            Key.Unknown, // brightness down
+            Key.Unknown, // brightness up
+            Key.Unknown, // media
+            Key.Unknown, // switch video mode
+            Key.Unknown, // dillum toggle
+            Key.Unknown, // dillum down
+            Key.Unknown, // dillum up
+            Key.Unknown, // send
+            // 232-239
+            Key.Unknown, // reply
+            Key.Unknown, // forward email
+            Key.Unknown, // save
+            Key.Unknown, // documents
+            Key.Unknown, // battery
+            Key.Unknown, // bluetooth
+            Key.Unknown, // wlan
+            Key.Unknown, // uwb
+            // 240-247
+            Key.Unknown,
+            Key.Unknown, // video next
+            Key.Unknown, // video prev
+            Key.Unknown, // brightness cycle
+            Key.Unknown, // brightness zero
+            Key.Unknown, // display off
+            Key.Unknown, // wwan / wimax
+            Key.Unknown, // rfkill
+            // 248-255
+            Key.Unknown, // mic mute
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown,
+            Key.Unknown, // reserved
+        };
+
+        #endregion
+
+        #endregion
+
         #region IKeyboardDriver2 implementation
 
         public KeyboardState GetState()
         {
-            throw new NotImplementedException();
+            lock (this)
+            {
+                return state;
+            }
         }
 
         public KeyboardState GetState(int index)
         {
-            throw new NotImplementedException();
+            lock (this)
+            {
+                if (index == 0)
+                    return state;
+                else
+                    return new KeyboardState();
+            }
         }
 
         public string GetDeviceName(int index)
         {
-            throw new NotImplementedException();
+            if (index == 0)
+                return "Standard Input";
+            else
+                return String.Empty;
         }
 
         #endregion
 
+        #region IDisposable Implementation
+
+        public void Dispose()
+        {
+            Dispose(true);
+            GC.SuppressFinalize(this);
+        }
+
+        void Dispose(bool disposing)
+        {
+            Interlocked.Increment(ref exit);
+
+            if (disposing)
+            {
+                ExitTTY(this, EventArgs.Empty);
+            }
+            else
+            {
+                Debug.Print("{0} leaked, did you forget to call Dispose()?", typeof(LinuxKeyboardTTY).FullName);
+            }
+        }
 
+        ~LinuxKeyboardTTY()
+        {
+            Dispose(false);
+        }
 
+        #endregion
     }
 }