From 7cce215a4b05340847a951aa4a3497bd2d15ec71 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 15 May 2014 00:33:50 +0200 Subject: [PATCH] [X11] Use XKB for layout-independent input The code will fall back to core X11 if XKB is not available. --- Source/OpenTK/Platform/X11/Functions.cs | 3 - Source/OpenTK/Platform/X11/Structs.cs | 2 +- Source/OpenTK/Platform/X11/X11GLNative.cs | 28 +- Source/OpenTK/Platform/X11/X11KeyMap.cs | 332 +++++++++++++++++++++- Source/OpenTK/Platform/X11/X11Keyboard.cs | 25 +- 5 files changed, 346 insertions(+), 44 deletions(-) diff --git a/Source/OpenTK/Platform/X11/Functions.cs b/Source/OpenTK/Platform/X11/Functions.cs index 4198a87a..1439e01b 100644 --- a/Source/OpenTK/Platform/X11/Functions.cs +++ b/Source/OpenTK/Platform/X11/Functions.cs @@ -439,9 +439,6 @@ namespace OpenTK.Platform.X11 [DllImport("libX11", EntryPoint = "XFilterEvent")] public extern static bool XFilterEvent(ref XEvent xevent, IntPtr window); - [DllImport("libX11")] - public extern static bool XkbSetDetectableAutoRepeat(IntPtr display, bool detectable, out bool supported); - [DllImport("libX11")] public extern static void XPeekEvent(IntPtr display, ref XEvent xevent); diff --git a/Source/OpenTK/Platform/X11/Structs.cs b/Source/OpenTK/Platform/X11/Structs.cs index 5b339570..18e876ec 100644 --- a/Source/OpenTK/Platform/X11/Structs.cs +++ b/Source/OpenTK/Platform/X11/Structs.cs @@ -945,7 +945,7 @@ namespace OpenTK.Platform.X11 public byte pad; } - internal enum Atom + internal enum AtomName { AnyPropertyType = 0, XA_PRIMARY = 1, diff --git a/Source/OpenTK/Platform/X11/X11GLNative.cs b/Source/OpenTK/Platform/X11/X11GLNative.cs index e2cd6e24..e51b6f5e 100644 --- a/Source/OpenTK/Platform/X11/X11GLNative.cs +++ b/Source/OpenTK/Platform/X11/X11GLNative.cs @@ -55,7 +55,8 @@ namespace OpenTK.Platform.X11 const int _min_width = 30, _min_height = 30; - X11WindowInfo window = new X11WindowInfo(); + readonly X11WindowInfo window = new X11WindowInfo(); + readonly X11KeyMap KeyMap; // Window manager hints for fullscreen windows. // Not used right now (the code is written, but is not 64bit-correct), but could be useful for older WMs which @@ -231,12 +232,18 @@ namespace OpenTK.Platform.X11 Debug.WriteLine(String.Format("X11GLNative window created successfully (id: {0}).", Handle)); Debug.Unindent(); - // Request that auto-repeat is only set on devices that support it physically. - // This typically means that it's turned off for keyboards (which is what we want). - // We prefer this method over XAutoRepeatOff/On, because the latter needs to - // be reset before the program exits. - bool supported; - Functions.XkbSetDetectableAutoRepeat(window.Display, true, out supported); + using (new XLock(window.Display)) + { + // Request that auto-repeat is only set on devices that support it physically. + // This typically means that it's turned off for keyboards (which is what we want). + // We prefer this method over XAutoRepeatOff/On, because the latter needs to + // be reset before the program exits. + if (Xkb.IsSupported(window.Display)) + { + bool supported; + Xkb.SetDetectableAutoRepeat(window.Display, true, out supported); + } + } // The XInput2 extension makes keyboard and mouse handling much easier. // Check whether it is available. @@ -271,6 +278,7 @@ namespace OpenTK.Platform.X11 { window.Screen = Functions.XDefaultScreen(window.Display); //API.DefaultScreen; window.RootWindow = Functions.XRootWindow(window.Display, window.Screen); // API.RootWindow; + KeyMap = new X11KeyMap(window.Display); } Debug.Print("Display: {0}, Screen {1}, Root window: {2}", window.Display, window.Screen, @@ -677,7 +685,7 @@ namespace OpenTK.Platform.X11 { Functions.XGetWindowProperty(window.Display, window.Handle, _atom_net_frame_extents, IntPtr.Zero, new IntPtr(16), false, - (IntPtr)Atom.XA_CARDINAL, out atom, out format, out nitems, out bytes_after, ref prop); + (IntPtr)AtomName.XA_CARDINAL, out atom, out format, out nitems, out bytes_after, ref prop); } if ((prop != IntPtr.Zero)) @@ -870,7 +878,7 @@ namespace OpenTK.Platform.X11 case XEventName.KeyRelease: bool pressed = e.type == XEventName.KeyPress; Key key; - if (X11KeyMap.TranslateKey(ref e.KeyEvent, out key)) + if (KeyMap.TranslateKey(ref e.KeyEvent, out key)) { if (pressed) { @@ -1005,6 +1013,7 @@ namespace OpenTK.Platform.X11 { Debug.Print("keybard mapping refreshed"); Functions.XRefreshKeyboardMapping(ref e.MappingEvent); + KeyMap.RefreshKeycodes(window.Display); } break; @@ -1738,7 +1747,6 @@ namespace OpenTK.Platform.X11 } window.Dispose(); - window = null; } } else diff --git a/Source/OpenTK/Platform/X11/X11KeyMap.cs b/Source/OpenTK/Platform/X11/X11KeyMap.cs index 216671e8..9d3b3d40 100644 --- a/Source/OpenTK/Platform/X11/X11KeyMap.cs +++ b/Source/OpenTK/Platform/X11/X11KeyMap.cs @@ -1,9 +1,30 @@ -#region --- License --- -/* Licensed under the MIT/X11 license. - * Copyright (c) 2006-2008 the OpenTK team. - * This notice may not be removed. - * See license.txt for licensing detailed licensing details. - */ +#region License +// +// Xkb.cs +// +// Author: +// Stefanos Apostolopoulos +// +// 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; @@ -14,9 +35,230 @@ using OpenTK.Input; namespace OpenTK.Platform.X11 { - static class X11KeyMap + class X11KeyMap { - public static Key GetKey(XKey key) + // Keycode lookup table for current layout + readonly Key[] keycodes = new Key[256]; + readonly bool xkb_supported; + + internal X11KeyMap(IntPtr display) + { + xkb_supported = Xkb.IsSupported(display); + if (xkb_supported) + { + RefreshKeycodes(display); + } + } + + // Refreshes the keycodes lookup table based + // on the current keyboard layout. + internal void RefreshKeycodes(IntPtr display) + { + // Approach inspired by GLFW: http://www.glfw.org/ + // Huge props to the GLFW team! + if (xkb_supported) + { + unsafe + { + // Xkb.GetKeyboard appears to be broken (multiple bug reports across distros) + // so use Xkb.AllocKeyboard with Xkb.GetNames instead. + XkbDesc* keyboard = Xkb.AllocKeyboard(display); + if (keyboard != null) + { + Xkb.GetNames(display, XkbNamesMask.All, keyboard); + + for (int i = 0; i < keycodes.Length; i++) + { + keycodes[i] = Key.Unknown; + } + + // Map all alphanumeric keys of this layout + // Symbols are handled in GetKey() instead. + for (int i = keyboard->min_key_code; i <= keyboard->max_key_code; ++i) + { + string name = new string((sbyte*)keyboard->names->keys[i].name, 0, Xkb.KeyNameLength); + + Key key = Key.Unknown; + switch (name) + { + case "TLDE": + key = Key.Tilde; + break; + case "AE01": + key = Key.Number1; + break; + case "AE02": + key = Key.Number2; + break; + case "AE03": + key = Key.Number3; + break; + case "AE04": + key = Key.Number4; + break; + case "AE05": + key = Key.Number5; + break; + case "AE06": + key = Key.Number6; + break; + case "AE07": + key = Key.Number7; + break; + case "AE08": + key = Key.Number8; + break; + case "AE09": + key = Key.Number9; + break; + case "AE10": + key = Key.Number0; + break; + case "AE11": + key = Key.Minus; + break; + case "AE12": + key = Key.Plus; + break; + case "AD01": + key = Key.Q; + break; + case "AD02": + key = Key.W; + break; + case "AD03": + key = Key.E; + break; + case "AD04": + key = Key.R; + break; + case "AD05": + key = Key.T; + break; + case "AD06": + key = Key.Y; + break; + case "AD07": + key = Key.U; + break; + case "AD08": + key = Key.I; + break; + case "AD09": + key = Key.O; + break; + case "AD10": + key = Key.P; + break; + case "AD11": + key = Key.BracketLeft; + break; + case "AD12": + key = Key.BracketRight; + break; + case "AC01": + key = Key.A; + break; + case "AC02": + key = Key.S; + break; + case "AC03": + key = Key.D; + break; + case "AC04": + key = Key.F; + break; + case "AC05": + key = Key.G; + break; + case "AC06": + key = Key.H; + break; + case "AC07": + key = Key.J; + break; + case "AC08": + key = Key.K; + break; + case "AC09": + key = Key.L; + break; + case "AC10": + key = Key.Semicolon; + break; + case "AC11": + key = Key.Quote; + break; + case "AB01": + key = Key.Z; + break; + case "AB02": + key = Key.X; + break; + case "AB03": + key = Key.C; + break; + case "AB04": + key = Key.V; + break; + case "AB05": + key = Key.B; + break; + case "AB06": + key = Key.N; + break; + case "AB07": + key = Key.M; + break; + case "AB08": + key = Key.Comma; + break; + case "AB09": + key = Key.Period; + break; + case "AB10": + key = Key.Slash; + break; + case "BKSL": + key = Key.BackSlash; + break; + case "LSGT": + key = Key.Unknown; + break; + default: + key = Key.Unknown; + break; + } + + keycodes[i] = key; + } + + Xkb.FreeKeyboard(keyboard, 0, true); + } + } + } + + // Translate unknown keys (and symbols) using + // regular layout-dependent GetKey() + for (int i = 0; i < 256; i++) + { + if (keycodes[i] == Key.Unknown) + { + // TranslateKeyCode expects a XKeyEvent structure + // Make one up + XKeyEvent e = new XKeyEvent(); + e.display = display; + e.keycode = i; + Key key = Key.Unknown; + if (TranslateKeyEvent(ref e, out key)) + { + keycodes[i] = key; + } + } + } + } + + static Key TranslateXKey(XKey key) { switch (key) { @@ -371,14 +613,26 @@ namespace OpenTK.Platform.X11 } } - internal static bool TranslateKey(ref XKeyEvent e, out Key key) + bool TranslateKeyEvent(ref XKeyEvent e, out Key key) + { + if (xkb_supported) + { + return TranslateKeyXkb(e.display, e.keycode, out key); + } + else + { + return TranslateKeyX11(ref e, out key); + } + } + + bool TranslateKeyX11(ref XKeyEvent e, out Key key) { XKey keysym = (XKey)API.LookupKeysym(ref e, 0); XKey keysym2 = (XKey)API.LookupKeysym(ref e, 1); - key = X11KeyMap.GetKey(keysym); + key = X11KeyMap.TranslateXKey(keysym); if (key == Key.Unknown) { - key = X11KeyMap.GetKey(keysym2); + key = X11KeyMap.TranslateXKey(keysym2); } if (key == Key.Unknown) { @@ -388,7 +642,61 @@ namespace OpenTK.Platform.X11 return key != Key.Unknown; } - internal static MouseButton TranslateButton(int button, out float wheelx, out float wheely) + bool TranslateKeyXkb(IntPtr display, int keycode, out Key key) + { + if (keycode < 8 || keycode > 255) + { + key = Key.Unknown; + return false; + } + + // Translate the numeric keypad using the secondary group + // (equivalent to NumLock = on) + XKey keysym = Xkb.KeycodeToKeysym(display, keycode, 0, 1); + switch (keysym) + { + case XKey.KP_0: key = Key.Keypad0; return true; + case XKey.KP_1: key = Key.Keypad1; return true; + case XKey.KP_2: key = Key.Keypad2; return true; + case XKey.KP_3: key = Key.Keypad3; return true; + case XKey.KP_4: key = Key.Keypad4; return true; + case XKey.KP_5: key = Key.Keypad5; return true; + case XKey.KP_6: key = Key.Keypad6; return true; + case XKey.KP_7: key = Key.Keypad7; return true; + case XKey.KP_8: key = Key.Keypad8; return true; + case XKey.KP_9: key = Key.Keypad9; return true; + case XKey.KP_Separator: + case XKey.KP_Decimal: key = Key.KeypadDecimal; return true; + case XKey.KP_Equal: key = Key.Unknown; return false; // Todo: fixme + case XKey.KP_Enter: key = Key.KeypadEnter; return true; + } + + // Translate non-alphanumeric keys using the primary group + keysym = Xkb.KeycodeToKeysym(display, keycode, 0, 0); + key = TranslateXKey(keysym); + return key != Key.Unknown; + } + + internal bool TranslateKey(int keycode, out Key key) + { + if (keycode < 0 || keycode > 255) + { + key = Key.Unknown; + } + else + { + key = keycodes[keycode]; + } + return key != Key.Unknown; + } + + internal bool TranslateKey(ref XKeyEvent e, out Key key) + { + return TranslateKey(e.keycode, out key); + + } + + internal static MouseButton TranslateButton(int button, out int wheelx, out int wheely) { wheelx = 0; wheely = 0; diff --git a/Source/OpenTK/Platform/X11/X11Keyboard.cs b/Source/OpenTK/Platform/X11/X11Keyboard.cs index 3031aef7..288fba05 100644 --- a/Source/OpenTK/Platform/X11/X11Keyboard.cs +++ b/Source/OpenTK/Platform/X11/X11Keyboard.cs @@ -39,6 +39,7 @@ namespace OpenTK.Platform.X11 readonly static string name = "Core X11 keyboard"; readonly byte[] keys = new byte[32]; readonly int KeysymsPerKeycode; + readonly X11KeyMap KeyMap; KeyboardState state = new KeyboardState(); public X11Keyboard() @@ -59,17 +60,15 @@ namespace OpenTK.Platform.X11 ref KeysymsPerKeycode); Functions.XFree(keysym_ptr); - try + if (Xkb.IsSupported(display)) { // Request that auto-repeat is only set on devices that support it physically. // This typically means that it's turned off for keyboards what we want). // We prefer this method over XAutoRepeatOff/On, because the latter needs to // be reset before the program exits. bool supported; - Functions.XkbSetDetectableAutoRepeat(display, true, out supported); - } - catch - { + Xkb.SetDetectableAutoRepeat(display, true, out supported); + KeyMap = new X11KeyMap(display); } } } @@ -104,23 +103,13 @@ namespace OpenTK.Platform.X11 using (new XLock(display)) { Functions.XQueryKeymap(display, keys); - for (int keycode = 8; keycode < 256; keycode++) + for (int keycode = 0; keycode < 256; keycode++) { bool pressed = (keys[keycode >> 3] >> (keycode & 0x07) & 0x01) != 0; Key key; - - for (int mod = 0; mod < KeysymsPerKeycode; mod++) + if (KeyMap.TranslateKey(keycode, out key)) { - IntPtr keysym = Functions.XKeycodeToKeysym(display, (byte)keycode, mod); - if (keysym != IntPtr.Zero) - { - key = X11KeyMap.GetKey((XKey)keysym); - if (pressed) - state.EnableBit((int)key); - else - state.DisableBit((int)key); - break; - } + state[key] = pressed; } } }