From 9e73358dd7904717f594c730ca413903fcba1d1a Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 14 Jul 2014 15:42:17 +0000 Subject: [PATCH] [Linux] Implemented TTY and libinput keyboard drivers --- Source/OpenTK/OpenTK.csproj | 3 +- .../Platform/Linux/Bindings/LibInput.cs | 77 ++- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 34 ++ Source/OpenTK/Platform/Linux/Bindings/Poll.cs | 2 +- .../Platform/Linux/Bindings/Terminal.cs | 166 ++++++ Source/OpenTK/Platform/Linux/Bindings/Udev.cs | 5 +- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 2 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 226 ++++++++ .../Platform/Linux/LinuxKeyboardLibInput.cs | 77 --- .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 485 +++++++++++++++++- 10 files changed, 970 insertions(+), 107 deletions(-) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Terminal.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxInput.cs delete mode 100644 Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs 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 } }