From b3a0b7845493f11d76516bfcd4ebcdee0ed17844 Mon Sep 17 00:00:00 2001 From: "Stefanos A." Date: Thu, 3 Oct 2013 20:53:34 +0200 Subject: [PATCH] Use locale-independent scancodes This significantly improves the usability of keyboard input, by allowing a common input scheme to be used on different keyboard layouts. Text input will be facilitated through a new API. --- Source/OpenTK/Platform/Windows/WinGLNative.cs | 62 +--- Source/OpenTK/Platform/Windows/WinKeyMap.cs | 270 +++++++++++++----- .../OpenTK/Platform/Windows/WinRawKeyboard.cs | 41 +-- 3 files changed, 213 insertions(+), 160 deletions(-) diff --git a/Source/OpenTK/Platform/Windows/WinGLNative.cs b/Source/OpenTK/Platform/Windows/WinGLNative.cs index 5aad5218..e1d3f948 100644 --- a/Source/OpenTK/Platform/Windows/WinGLNative.cs +++ b/Source/OpenTK/Platform/Windows/WinGLNative.cs @@ -375,64 +375,16 @@ namespace OpenTK.Platform.Windows // In this case, both keys will be reported as pressed. bool extended = (lParam.ToInt64() & ExtendedBit) != 0; - uint scancode = (uint)((lParam.ToInt64() >> 16) & 0xFF); - Key key = Key.Unknown; - switch ((VirtualKeys)wParam) + short scancode = (short)((lParam.ToInt64() >> 16) & 0xFF); + VirtualKeys vkey = (VirtualKeys)wParam; + bool is_valid; + Key key = KeyMap.TranslateKey(scancode, vkey, extended, false, out is_valid); + + if (is_valid) { - case VirtualKeys.SHIFT: - // The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit - // to distinguish between left and right keys. Moreover, pressing both keys and releasing one - // may result in both keys being held down (but not always). - // The only reliable way to solve this was reported by BlueMonkMN at the forums: we should - // check the scancodes. It looks like GLFW does the same thing, so it should be reliable. - - // Note: we release both keys when either shift is released. - // Otherwise, the state of one key might be stuck to pressed. - if (ShiftRightScanCode != 0 && pressed) - { - if (scancode == ShiftRightScanCode) - key = Input.Key.ShiftRight; - else - key = Input.Key.ShiftLeft; - } - else - { - // Windows 9x and NT4.0 or key release event. - keyboard.SetKey(Input.Key.ShiftLeft, ShiftLeftScanCode, pressed); - keyboard.SetKey(Input.Key.ShiftRight, ShiftRightScanCode, pressed); - } - break; - - case VirtualKeys.CONTROL: - if (extended) - key = Input.Key.ControlRight; - else - key = Input.Key.ControlLeft; - break; - - case VirtualKeys.MENU: - if (extended) - key = Input.Key.AltRight; - else - key = Input.Key.AltLeft; - break; - - case VirtualKeys.RETURN: - if (extended) - key = Key.KeypadEnter; - else - key = Key.Enter; - break; - - default: - if (!KeyMap.ContainsKey((VirtualKeys)wParam)) - Debug.Print("Virtual key {0} ({1}) not mapped.", (VirtualKeys)wParam, (long)lParam); - else - key = KeyMap[(VirtualKeys)wParam]; - break; + keyboard.SetKey(key, (byte)scancode, pressed); } - keyboard.SetKey(key, scancode, pressed); return IntPtr.Zero; case WindowMessage.SYSCHAR: diff --git a/Source/OpenTK/Platform/Windows/WinKeyMap.cs b/Source/OpenTK/Platform/Windows/WinKeyMap.cs index 93ab2bcf..7c5bedf6 100644 --- a/Source/OpenTK/Platform/Windows/WinKeyMap.cs +++ b/Source/OpenTK/Platform/Windows/WinKeyMap.cs @@ -27,96 +27,214 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using OpenTK.Input; namespace OpenTK.Platform.Windows { - class WinKeyMap : Dictionary + class WinKeyMap { - /// - /// Initializes the map between VirtualKeys and OpenTK.Key - /// + readonly Dictionary ScanMap = new Dictionary(); + public WinKeyMap() { - this.Add(VirtualKeys.ESCAPE, Key.Escape); + // 0 - 15 + Append(Key.Unknown); + Append(Key.Escape); - // Function keys - for (int i = 0; i < 24; i++) + for (int i = 0; i < 9; i++) + Append(Key.Number1 + i); + Append(Key.Number0); + + Append(Key.Minus); + Append(Key.Plus); + Append(Key.BackSpace); + Append(Key.Tab); + + // 16-31 + Append(Key.Q); + Append(Key.W); + Append(Key.E); + Append(Key.R); + Append(Key.T); + Append(Key.Y); + Append(Key.U); + Append(Key.I); + Append(Key.O); + Append(Key.P); + Append(Key.BracketLeft); + Append(Key.BracketRight); + Append(Key.Enter); + Append(Key.ControlLeft); + Append(Key.A); + Append(Key.S); + + // 32 - 47 + Append(Key.D); + Append(Key.F); + Append(Key.G); + Append(Key.H); + Append(Key.J); + Append(Key.K); + Append(Key.L); + Append(Key.Semicolon); + Append(Key.Quote); + Append(Key.Grave); + Append(Key.ShiftLeft); + Append(Key.BackSlash); + Append(Key.Z); + Append(Key.X); + Append(Key.C); + Append(Key.V); + + // 48 - 63 + Append(Key.B); + Append(Key.N); + Append(Key.M); + Append(Key.Comma); + Append(Key.Period); + Append(Key.Slash); + Append(Key.ShiftRight); + Append(Key.PrintScreen); + Append(Key.AltLeft); + Append(Key.Space); + Append(Key.CapsLock); + Append(Key.F1); + Append(Key.F2); + Append(Key.F3); + Append(Key.F4); + Append(Key.F5); + + // 64 - 79 + Append(Key.F6); + Append(Key.F7); + Append(Key.F8); + Append(Key.F9); + Append(Key.F10); + Append(Key.NumLock); + Append(Key.ScrollLock); + Append(Key.Home); + Append(Key.Up); + Append(Key.PageUp); + Append(Key.KeypadMinus); + Append(Key.Left); + Append(Key.Keypad5); + Append(Key.Right); + Append(Key.KeypadPlus); + Append(Key.End); + + // 80 - 95 + Append(Key.Down); + Append(Key.PageDown); + Append(Key.Insert); + Append(Key.Delete); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.NonUSBackSlash); + Append(Key.F11); + Append(Key.F12); + Append(Key.Pause); + Append(Key.Unknown); + Append(Key.WinLeft); + Append(Key.WinRight); + Append(Key.Menu); + Append(Key.Unknown); + Append(Key.Unknown); + + // 96 - 111 + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.F13); + Append(Key.F14); + Append(Key.F15); + Append(Key.F16); + Append(Key.F17); + Append(Key.F18); + Append(Key.F19); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + + // 112 - 127 + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + Append(Key.Unknown); + } + + void Append(Key key) + { + ScanMap.Add(ScanMap.Count, key); + } + + public Key TranslateKey(short scancode, VirtualKeys vkey, bool extended0, bool extended1, out bool is_valid) + { + is_valid = true; + + Key key; + ScanMap.TryGetValue(scancode, out key); + + if (!extended0) { - this.Add((VirtualKeys)((int)VirtualKeys.F1 + i), Key.F1 + i); + switch (key) + { + case Key.Insert: key = Key.Keypad0; break; + case Key.End: key = Key.Keypad1; break; + case Key.Down: key = Key.Keypad2; break; + case Key.PageDown: key = Key.Keypad3; break; + case Key.Left: key = Key.Keypad4; break; + case Key.Right: key = Key.Keypad6; break; + case Key.Home: key = Key.Keypad7; break; + case Key.Up: key = Key.Keypad8; break; + case Key.PageUp: key = Key.Keypad9; break; + case Key.PrintScreen: key = Key.KeypadMultiply; break; + case Key.Delete: key = Key.KeypadDecimal; break; + case Key.NumLock: + if (vkey == VirtualKeys.Last) + is_valid = false; + else if (vkey == VirtualKeys.PAUSE) + key = Key.Pause; + break; + } + } + else + { + switch (key) + { + case Key.Enter: key = Key.KeypadEnter; break; + case Key.AltLeft: key = Key.AltRight; break; + case Key.AltRight: key = Key.AltLeft; break; + case Key.ControlLeft: key = Key.ControlRight; break; + case Key.ControlRight: key = Key.ControlLeft; break; + case Key.ShiftLeft: is_valid = false; break; + } } - // Number keys (0-9) - for (int i = 0; i <= 9; i++) + if (extended1) { - this.Add((VirtualKeys)(0x30 + i), Key.Number0 + i); + switch (key) + { + case Key.ControlLeft: key = Key.Pause; break; + } } - // Letters (A-Z) - for (int i = 0; i < 26; i++) - { - this.Add((VirtualKeys)(0x41 + i), Key.A + i); - } - - this.Add(VirtualKeys.TAB, Key.Tab); - this.Add(VirtualKeys.CAPITAL, Key.CapsLock); - this.Add(VirtualKeys.LCONTROL, Key.ControlLeft); - this.Add(VirtualKeys.LSHIFT, Key.ShiftLeft); - this.Add(VirtualKeys.LWIN, Key.WinLeft); - this.Add(VirtualKeys.LMENU, Key.AltLeft); - this.Add(VirtualKeys.SPACE, Key.Space); - this.Add(VirtualKeys.RMENU, Key.AltRight); - this.Add(VirtualKeys.RWIN, Key.WinRight); - this.Add(VirtualKeys.APPS, Key.Menu); - this.Add(VirtualKeys.RCONTROL, Key.ControlRight); - this.Add(VirtualKeys.RSHIFT, Key.ShiftRight); - this.Add(VirtualKeys.RETURN, Key.Enter); - this.Add(VirtualKeys.BACK, Key.BackSpace); - - this.Add(VirtualKeys.OEM_1, Key.Semicolon); // Varies by keyboard, ;: on Win2K/US - this.Add(VirtualKeys.OEM_2, Key.Slash); // Varies by keyboard, /? on Win2K/US - this.Add(VirtualKeys.OEM_3, Key.Tilde); // Varies by keyboard, `~ on Win2K/US - this.Add(VirtualKeys.OEM_4, Key.BracketLeft); // Varies by keyboard, [{ on Win2K/US - this.Add(VirtualKeys.OEM_5, Key.BackSlash); // Varies by keyboard, \| on Win2K/US - this.Add(VirtualKeys.OEM_6, Key.BracketRight); // Varies by keyboard, ]} on Win2K/US - this.Add(VirtualKeys.OEM_7, Key.Quote); // Varies by keyboard, '" on Win2K/US - this.Add(VirtualKeys.OEM_PLUS, Key.Plus); // Invariant: + - this.Add(VirtualKeys.OEM_COMMA, Key.Comma); // Invariant: , - this.Add(VirtualKeys.OEM_MINUS, Key.Minus); // Invariant: - - this.Add(VirtualKeys.OEM_PERIOD, Key.Period); // Invariant: . - - this.Add(VirtualKeys.HOME, Key.Home); - this.Add(VirtualKeys.END, Key.End); - this.Add(VirtualKeys.DELETE, Key.Delete); - this.Add(VirtualKeys.PRIOR, Key.PageUp); - this.Add(VirtualKeys.NEXT, Key.PageDown); - this.Add(VirtualKeys.PRINT, Key.PrintScreen); - this.Add(VirtualKeys.PAUSE, Key.Pause); - this.Add(VirtualKeys.NUMLOCK, Key.NumLock); - - this.Add(VirtualKeys.SCROLL, Key.ScrollLock); - this.Add(VirtualKeys.SNAPSHOT, Key.PrintScreen); - this.Add(VirtualKeys.CLEAR, Key.Clear); - this.Add(VirtualKeys.INSERT, Key.Insert); - - this.Add(VirtualKeys.SLEEP, Key.Sleep); - - // Keypad - for (int i = 0; i <= 9; i++) - { - this.Add((VirtualKeys)((int)VirtualKeys.NUMPAD0 + i), Key.Keypad0 + i); - } - this.Add(VirtualKeys.DECIMAL, Key.KeypadDecimal); - this.Add(VirtualKeys.ADD, Key.KeypadAdd); - this.Add(VirtualKeys.SUBTRACT, Key.KeypadSubtract); - this.Add(VirtualKeys.DIVIDE, Key.KeypadDivide); - this.Add(VirtualKeys.MULTIPLY, Key.KeypadMultiply); - - // Navigation - this.Add(VirtualKeys.UP, Key.Up); - this.Add(VirtualKeys.DOWN, Key.Down); - this.Add(VirtualKeys.LEFT, Key.Left); - this.Add(VirtualKeys.RIGHT, Key.Right); + return key; } } } diff --git a/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs b/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs index f439a361..413ce3ee 100644 --- a/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs +++ b/Source/OpenTK/Platform/Windows/WinRawKeyboard.cs @@ -157,6 +157,13 @@ namespace OpenTK.Platform.Windows bool pressed = rin.Data.Keyboard.Message == (int)WindowMessage.KEYDOWN || rin.Data.Keyboard.Message == (int)WindowMessage.SYSKEYDOWN; + var scancode = rin.Data.Keyboard.MakeCode; + var vkey = rin.Data.Keyboard.VKey; + + bool extended0 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E0) != 0; + bool extended1 = (int)(rin.Data.Keyboard.Flags & RawInputKeyboardDataFlags.E1) != 0; + + bool is_valid = true; ContextHandle handle = new ContextHandle(rin.Header.Device); KeyboardState keyboard; @@ -175,36 +182,12 @@ namespace OpenTK.Platform.Windows int keyboard_handle = rawids.ContainsKey(handle) ? rawids[handle] : 0; keyboard = keyboards[keyboard_handle]; - // Generic control, shift, alt keys may be sent instead of left/right. - // It seems you have to explicitly register left/right events. - switch (rin.Data.Keyboard.VKey) + Key key = KeyMap.TranslateKey(scancode, vkey, extended0, extended1, out is_valid); + + if (is_valid) { - case VirtualKeys.SHIFT: - keyboard.SetKeyState(Key.ShiftLeft, (byte)WinGLNative.ShiftLeftScanCode, pressed); - keyboard.SetKeyState(Key.ShiftRight, (byte)WinGLNative.ShiftRightScanCode, pressed); - processed = true; - break; - - case VirtualKeys.CONTROL: - keyboard.SetKeyState(Key.ControlLeft, (byte)WinGLNative.ControlLeftScanCode, pressed); - keyboard.SetKeyState(Key.ControlRight, (byte)WinGLNative.ControlRightScanCode, pressed); - processed = true; - break; - - case VirtualKeys.MENU: - keyboard.SetKeyState(Key.AltLeft, (byte)WinGLNative.AltLeftScanCode, pressed); - keyboard.SetKeyState(Key.AltRight, (byte)WinGLNative.AltRightScanCode, pressed); - processed = true; - break; - - default: - Key key; - KeyMap.TryGetValue(rin.Data.Keyboard.VKey, out key); - if (key == Key.Unknown) - Debug.Print("Virtual key {0} ({1}) not mapped.", rin.Data.Keyboard.VKey, (int)rin.Data.Keyboard.VKey); - keyboard.SetKeyState(key, BitConverter.GetBytes(rin.Data.Keyboard.MakeCode)[0], pressed); - processed = true; - break; + keyboard.SetKeyState(key, (byte)scancode, pressed); + processed = true; } lock (UpdateLock)