Opentk/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs

542 lines
16 KiB
C#

#region License
//
// LinuxKeyboardTTY.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 System.IO;
using System.Threading;
using OpenTK.Input;
namespace OpenTK.Platform.Linux
{
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()
{
lock (this)
{
return state;
}
}
public KeyboardState GetState(int index)
{
lock (this)
{
if (index == 0)
return state;
else
return new KeyboardState();
}
}
public string GetDeviceName(int index)
{
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
}
}