diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj
index b8a47635..76514e64 100644
--- a/Source/OpenTK/OpenTK.csproj
+++ b/Source/OpenTK/OpenTK.csproj
@@ -817,8 +817,9 @@
-
+
+
diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs
index aadf4aaf..b5cdd9ef 100644
--- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs
+++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs
@@ -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
+ ///
+ /// Signals the end of a set of touchpoints at one device sample
+ /// time. This event has no coordinate information attached.
+ ///
+ 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
}
}
diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs
index fc77953d..264bab2a 100644
--- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs
+++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs
@@ -46,6 +46,12 @@ namespace OpenTK.Platform.Linux
[DllImport(lib)]
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);
@@ -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
{
diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs
index 67963695..a048398d 100644
--- a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs
+++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs
@@ -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
index 00000000..8ed7257c
--- /dev/null
+++ b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs
@@ -0,0 +1,166 @@
+#region License
+//
+// Terminal.cs
+//
+// Author:
+// Stefanos A.
+//
+// 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;
+
+ }
+}
+
diff --git a/Source/OpenTK/Platform/Linux/Bindings/Udev.cs b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs
index 0f674f5a..4c341b70 100644
--- a/Source/OpenTK/Platform/Linux/Bindings/Udev.cs
+++ b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs
@@ -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);
}
}
diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs
index f9d9c0a9..8d0d8e27 100644
--- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs
+++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs
@@ -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
index 00000000..19081c9e
--- /dev/null
+++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs
@@ -0,0 +1,226 @@
+#region License
+//
+// LinuxKeyboardLibInput.cs
+//
+// Author:
+// Stefanos A.
+//
+// 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
index d81ceb66..00000000
--- a/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs
+++ /dev/null
@@ -1,77 +0,0 @@
-#region License
-//
-// LinuxKeyboardLibInput.cs
-//
-// Author:
-// thefiddler
-//
-// 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
- }
-}
-
diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs
index 2d3c848b..1ba8d14a 100644
--- a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs
+++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs
@@ -28,37 +28,514 @@
#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
}
}