Skip to content
Browse files

[X11] Use XKB for layout-independent input

The code will fall back to core X11 if XKB is not available.
  • Loading branch information...
1 parent e8176ef commit 7cce215a4b05340847a951aa4a3497bd2d15ec71 @thefiddler thefiddler committed May 15, 2014
View
3 Source/OpenTK/Platform/X11/Functions.cs
@@ -440,9 +440,6 @@ public static int XSendEvent(IntPtr display, IntPtr window, bool propagate, Even
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);
[DllImport("libX11", EntryPoint = "XGetVisualInfo")]
View
2 Source/OpenTK/Platform/X11/Structs.cs
@@ -945,7 +945,7 @@ internal struct XColor
public byte pad;
}
- internal enum Atom
+ internal enum AtomName
{
AnyPropertyType = 0,
XA_PRIMARY = 1,
View
28 Source/OpenTK/Platform/X11/X11GLNative.cs
@@ -55,7 +55,8 @@ internal sealed class X11GLNative : NativeWindowBase
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 @@ internal sealed class X11GLNative : NativeWindowBase
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 @@ public X11GLNative()
{
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 @@ bool RefreshWindowBorders()
{
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 @@ public override void ProcessEvents()
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 @@ public override void ProcessEvents()
{
Debug.Print("keybard mapping refreshed");
Functions.XRefreshKeyboardMapping(ref e.MappingEvent);
+ KeyMap.RefreshKeycodes(window.Display);
}
break;
@@ -1738,7 +1747,6 @@ protected override void Dispose(bool manuallyCalled)
}
window.Dispose();
- window = null;
}
}
else
View
332 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 <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;
@@ -14,9 +35,230 @@
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 @@ public static Key GetKey(XKey key)
}
}
- 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 @@ internal static bool TranslateKey(ref XKeyEvent e, out Key key)
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;
View
25 Source/OpenTK/Platform/X11/X11Keyboard.cs
@@ -39,6 +39,7 @@ sealed class X11Keyboard : IKeyboardDriver2
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 @@ public X11Keyboard()
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 @@ void ProcessEvents()
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;
}
}
}

0 comments on commit 7cce215

Please sign in to comment.
Something went wrong with that request. Please try again.