Merge branch 'xkeyfix' of https://github.com/thefiddler/opentk into develop

This commit is contained in:
thefiddler 2014-05-19 10:52:37 +02:00
commit ade8e61625
12 changed files with 1155 additions and 211 deletions

View file

@ -697,7 +697,6 @@
<Compile Include="Platform\X11\X11Keyboard.cs" />
<Compile Include="Platform\X11\X11Mouse.cs" />
<Compile Include="Input\ButtonState.cs" />
<Compile Include="Platform\X11\XI2Mouse.cs" />
<EmbeddedResource Include="OpenTK.dll.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
@ -798,7 +797,11 @@
<Compile Include="Platform\MacOS\Quartz\CoreFoundation.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Platform\X11\Bindings\Xkb.cs" />
<Compile Include="Input\MouseScroll.cs" />
<Compile Include="Platform\X11\X11Input.cs" />
<Compile Include="Platform\X11\XI2Input.cs" />
<Compile Include="Platform\X11\XI2MouseKeyboard.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View file

@ -44,6 +44,9 @@ namespace OpenTK.Platform.X11
{
const string lib = "libXi";
internal const int XIAllDevices = 0;
internal const int XIAllMasterDevices = 1;
// mouse
internal static readonly IntPtr ButtonLeft = Functions.XInternAtom(API.DefaultDisplay, "Button Left", false);
internal static readonly IntPtr ButtonMiddle = Functions.XInternAtom(API.DefaultDisplay, "Button Middle", false);
@ -124,4 +127,13 @@ namespace OpenTK.Platform.X11
[DllImport(lib, EntryPoint = "XIQueryVersion")]
internal static extern Status QueryVersion(Display display, ref int major, ref int minor);
}
enum XIDeviceType
{
MasterPointer = 1,
MasterKeyboard = 2,
SlavePointer = 3,
SlaveKeyboard = 4,
FloatingSlave = 5,
}
}

View file

@ -0,0 +1,302 @@
#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;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace OpenTK.Platform.X11
{
using Atom = IntPtr;
using KeyCode = Byte;
using XkbControlsPtr = IntPtr;
using XkbServerMapPtr = IntPtr;
using XkbClientMapPtr = IntPtr;
using XkbIndicatorPtr = IntPtr;
using XkbCompatMapPtr = IntPtr;
using XkbGeometryPtr = IntPtr;
class Xkb
{
const string lib = "libX11";
internal const int KeyNameLength = 4;
internal const int NumModifiers = 8;
internal const int NumVirtualMods = 16;
internal const int NumIndicators = 32;
internal const int NumKbdGroups = 4;
internal const int UseCoreKeyboard = 0x0100;
[DllImport(lib, EntryPoint = "XkbFreeKeyboard")]
unsafe internal extern static void FreeKeyboard(XkbDesc* descr, int which, bool free);
[DllImport(lib, EntryPoint = "XkbAllocKeyboard")]
unsafe internal extern static XkbDesc* AllocKeyboard(IntPtr display);
[DllImport(lib, EntryPoint = "XkbGetKeyboard")]
internal extern static IntPtr GetKeyboard(IntPtr display, XkbKeyboardMask which, int device_id);
[DllImport(lib, EntryPoint = "XkbGetMap")]
internal extern static IntPtr GetMap(IntPtr display, XkbKeyboardMask which, int device_spec);
[DllImport(lib, EntryPoint = "XkbGetNames")]
unsafe internal extern static IntPtr GetNames(IntPtr display, XkbNamesMask which, XkbDesc* xkb);
[DllImport(lib, EntryPoint = "XkbKeycodeToKeysym")]
internal extern static XKey KeycodeToKeysym(IntPtr display, int keycode, int group, int level);
[DllImport(lib, EntryPoint = "XkbQueryExtension")]
internal extern static bool QueryExtension(IntPtr display, out int opcode_rtrn, out int event_rtrn,
out int error_rtrn, ref int major_in_out, ref int minor_in_out);
[DllImport(lib, EntryPoint = "XkbSetDetectableAutoRepeat")]
internal extern static bool SetDetectableAutoRepeat(IntPtr display, bool detectable, out bool supported);
internal static bool IsSupported(IntPtr display)
{
// The XkbQueryExtension manpage says that we cannot
// use XQueryExtension with XKB.
int opcode, error, ev;
int major = 1;
int minor = 0;
bool supported = QueryExtension(display, out opcode, out ev, out error, ref major, ref minor);
Debug.Print("XKB extension is {0}.", supported ? "supported" : "not supported");
if (supported)
{
Debug.Print("XKB version is {0}.{1}", major, minor);
}
return supported;
}
}
[Flags]
enum XkbKeyboardMask
{
Controls = 1 << 0,
ServerMap = 1 << 1,
IClientMap = 1 << 2,
IndicatorMap = 1 << 3,
Names = 1 << 4,
CompatibilityMap = 1 << 5,
Geometry = 1 << 6,
AllComponents = 1 << 7,
}
[Flags]
enum XkbNamesMask
{
Keycodes = 1 << 0,
Geometry = 1 << 1,
Symbols = 1 << 2,
PhysSymbols = 1 << 3,
Types = 1 << 4,
Compat = 1 << 5,
KeyType = 1 << 6,
KTLevel = 1 << 7,
Indicator = 1 << 8,
Key = 1 << 9,
KeyAliasesMask = 1 << 10,
VirtualMod = 1 << 11,
Group = 1 << 12,
RG = 1 << 13,
Component = 0x3f,
All = 0x3fff
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XkbDesc
{
internal IntPtr dpy;
internal ushort flags;
internal ushort device_spec;
internal KeyCode min_key_code;
internal KeyCode max_key_code;
internal XkbControlsPtr ctrls;
internal XkbServerMapPtr server;
internal XkbClientMapPtr map;
internal XkbIndicatorPtr indicators;
internal XkbNames* names;
internal XkbCompatMapPtr compat;
internal XkbGeometryPtr geom;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XkbKeyAlias
{
internal fixed byte real[Xkb.KeyNameLength];
internal fixed byte alias[Xkb.KeyNameLength];
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XkbKeyName
{
internal fixed byte name[Xkb.KeyNameLength];
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XkbNames
{
#region Structs
[StructLayout(LayoutKind.Sequential)]
internal struct Groups
{
Atom groups0;
Atom groups1;
Atom groups2;
Atom groups3;
internal Atom this[int i]
{
get
{
if (i < 0 || i > 3)
throw new IndexOutOfRangeException();
unsafe
{
fixed (Atom* ptr = &groups0)
{
return *(ptr + i);
}
}
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct Indicators
{
Atom indicators0;
Atom indicators1;
Atom indicators2;
Atom indicators3;
Atom indicators4;
Atom indicators5;
Atom indicators6;
Atom indicators7;
Atom indicators8;
Atom indicators9;
Atom indicators10;
Atom indicators11;
Atom indicators12;
Atom indicators13;
Atom indicators14;
Atom indicators15;
Atom indicators16;
Atom indicators17;
Atom indicators18;
Atom indicators19;
Atom indicators20;
Atom indicators21;
Atom indicators22;
Atom indicators23;
Atom indicators24;
Atom indicators25;
Atom indicators26;
Atom indicators27;
Atom indicators28;
Atom indicators29;
Atom indicators30;
Atom indicators31;
internal Atom this[int i]
{
get
{
if (i < 0 || i > 31)
throw new IndexOutOfRangeException();
unsafe
{
fixed (Atom* ptr = &indicators0)
{
return *(ptr + i);
} }
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct VMods
{
Atom vmods0;
Atom vmods1;
Atom vmods2;
Atom vmods3;
Atom vmods4;
Atom vmods5;
Atom vmods6;
Atom vmods7;
Atom vmods8;
Atom vmods9;
Atom vmods10;
Atom vmods11;
Atom vmods12;
Atom vmods13;
Atom vmods14;
Atom vmods15;
internal Atom this[int i]
{
get
{
if (i < 0 || i > 15)
throw new IndexOutOfRangeException();
unsafe
{
fixed (Atom* ptr = &vmods0)
{
return *(ptr + i);
}
}
}
}
}
#endregion
internal Atom keycodes;
internal Atom geometry;
internal Atom symbols;
internal Atom types;
internal Atom compat;
internal VMods vmods;
internal Indicators indicators;
internal Groups groups;
internal XkbKeyName* keys;
internal XkbKeyAlias* key_aliases;
internal Atom *radio_groups;
internal Atom phys_symbols;
internal byte num_keys;
internal byte num_key_aliases;
internal byte num_rg;
}
}

View file

@ -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);

View file

@ -945,7 +945,7 @@ namespace OpenTK.Platform.X11
public byte pad;
}
internal enum Atom
internal enum AtomName
{
AnyPropertyType = 0,
XA_PRIMARY = 1,
@ -1688,7 +1688,7 @@ namespace OpenTK.Platform.X11
{
public int deviceid;
public IntPtr name; // byte*
public int use;
public XIDeviceType use;
public int attachment;
public Bool enabled;
public int num_classes;

View file

@ -34,7 +34,25 @@ namespace OpenTK.Platform.X11
{
class X11Factory : PlatformFactoryBase
{
static IMouseDriver2 MouseDriver;
IInputDriver2 input_driver;
IInputDriver2 InputDriver
{
get
{
if (input_driver == null)
{
if (XI2MouseKeyboard.IsSupported(IntPtr.Zero))
{
input_driver = new XI2Input();
}
else
{
input_driver = new X11Input();
}
}
return input_driver;
}
}
#region Constructors
@ -83,25 +101,17 @@ namespace OpenTK.Platform.X11
public override IKeyboardDriver2 CreateKeyboardDriver()
{
return new X11Keyboard();
return InputDriver.KeyboardDriver;
}
public override IMouseDriver2 CreateMouseDriver()
{
lock (this)
{
MouseDriver =
MouseDriver ??
(XI2Mouse.IsSupported(IntPtr.Zero) ?
(IMouseDriver2)new XI2Mouse() : // Requires xorg 1.7 or higher.
(IMouseDriver2)new X11Mouse()); // Always supported.
return MouseDriver;
}
return InputDriver.MouseDriver;
}
public override IJoystickDriver2 CreateJoystickDriver()
{
return new X11Joystick();
return InputDriver.JoystickDriver;
}
#endregion
@ -109,12 +119,12 @@ namespace OpenTK.Platform.X11
protected override void Dispose(bool manual)
{
base.Dispose(manual);
if (manual && MouseDriver != null)
if (manual)
{
if (MouseDriver is IDisposable)
if (input_driver is IDisposable)
{
(MouseDriver as IDisposable).Dispose();
MouseDriver = null;
(input_driver as IDisposable).Dispose();
input_driver = null;
}
}
}

View file

@ -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
@ -230,20 +231,26 @@ 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.
xi2_supported = XI2Mouse.IsSupported(window.Display);
xi2_supported = XI2MouseKeyboard.IsSupported(window.Display);
if (xi2_supported)
{
xi2_opcode = XI2Mouse.XIOpCode;
xi2_version = XI2Mouse.XIVersion;
xi2_opcode = XI2MouseKeyboard.XIOpCode;
xi2_version = XI2MouseKeyboard.XIVersion;
}
exists = true;
@ -270,6 +277,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,
@ -658,7 +666,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))
@ -851,7 +859,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)
{
@ -986,6 +994,7 @@ namespace OpenTK.Platform.X11
{
Debug.Print("keybard mapping refreshed");
Functions.XRefreshKeyboardMapping(ref e.MappingEvent);
KeyMap.RefreshKeycodes(window.Display);
}
break;
@ -1719,7 +1728,6 @@ namespace OpenTK.Platform.X11
}
window.Dispose();
window = null;
}
}
else

View file

@ -0,0 +1,97 @@
#region License
//
// X11Input.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 OpenTK.Input;
namespace OpenTK.Platform.X11
{
class X11Input : IInputDriver2
{
readonly X11Mouse mouse = new X11Mouse();
readonly X11Keyboard keyboard = new X11Keyboard();
readonly X11Joystick joystick = new X11Joystick();
readonly IGamePadDriver gamepad = new MappedGamePadDriver();
internal X11Input()
{
Debug.WriteLine("Using X11 core input driver.");
Debug.WriteLine("[Warning] Mouse functionality will be significantly reduced.");
Debug.WriteLine("[Warning] Copy OpenTK.dll.config to use the XI2 input driver instead.");
}
#region IInputDriver2 Members
public IMouseDriver2 MouseDriver
{
get
{
return mouse;
}
}
public IKeyboardDriver2 KeyboardDriver
{
get
{
return keyboard;
}
}
public IGamePadDriver GamePadDriver
{
get
{
return gamepad;
}
}
public IJoystickDriver2 JoystickDriver
{
get
{
return joystick;
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
joystick.Dispose();
}
#endregion
}
}

View file

@ -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
//
// X11KeyMap.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 @@ 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,6 +642,59 @@ namespace OpenTK.Platform.X11
return key != Key.Unknown;
}
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 float wheelx, out float wheely)
{
wheelx = 0;

View file

@ -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;
}
}
}

View file

@ -0,0 +1,95 @@
#region License
//
// XI2Input.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 OpenTK.Input;
namespace OpenTK.Platform.X11
{
class XI2Input : IInputDriver2
{
readonly XI2MouseKeyboard mouse_keyboard = new XI2MouseKeyboard();
readonly X11Joystick joystick = new X11Joystick();
readonly IGamePadDriver gamepad = new MappedGamePadDriver();
internal XI2Input()
{
Debug.WriteLine("Using XI2 input driver.");
}
#region IInputDriver2 Members
public IMouseDriver2 MouseDriver
{
get
{
return mouse_keyboard;
}
}
public IKeyboardDriver2 KeyboardDriver
{
get
{
return mouse_keyboard;
}
}
public IGamePadDriver GamePadDriver
{
get
{
return gamepad;
}
}
public IJoystickDriver2 JoystickDriver
{
get
{
return joystick;
}
}
#endregion
#region IDisposable Members
public void Dispose()
{
mouse_keyboard.Dispose();
joystick.Dispose();
}
#endregion
}
}

View file

@ -34,10 +34,12 @@ using OpenTK.Input;
namespace OpenTK.Platform.X11
{
sealed class XI2Mouse : IMouseDriver2, IDisposable
sealed class XI2MouseKeyboard : IKeyboardDriver2, IMouseDriver2, IDisposable
{
const XEventName ExitEvent = XEventName.LASTEvent + 1;
readonly object Sync = new object();
readonly Thread ProcessingThread;
readonly X11KeyMap KeyMap;
bool disposed;
class XIMouse
@ -48,6 +50,14 @@ namespace OpenTK.Platform.X11
public XIScrollClassInfo ScrollY = new XIScrollClassInfo { number = -1 };
public XIValuatorClassInfo MotionX = new XIValuatorClassInfo { number = -1 };
public XIValuatorClassInfo MotionY = new XIValuatorClassInfo { number = -1 };
public string Name;
}
class XIKeyboard
{
public KeyboardState State;
public XIDeviceInfo DeviceInfo;
public string Name;
}
// Atoms
@ -66,9 +76,12 @@ namespace OpenTK.Platform.X11
//static readonly IntPtr RelVertWheel;
long cursor_x, cursor_y; // For GetCursorState()
List<XIMouse> devices = new List<XIMouse>(); // List of connected mice
List<XIMouse> devices = new List<XIMouse>(); // list of connected mice
Dictionary<int, int> rawids = new Dictionary<int, int>(); // maps hardware device ids to XIMouse ids
List<XIKeyboard> keyboards = new List<XIKeyboard>(); // list of connected keybords
Dictionary<int, int> keyboard_ids = new Dictionary<int, int>(); // maps hardware device ids to XIKeyboard ids
internal readonly X11WindowInfo window;
internal static int XIOpCode { get; private set; }
internal static int XIVersion { get; private set; }
@ -76,7 +89,7 @@ namespace OpenTK.Platform.X11
static readonly Functions.EventPredicate PredicateImpl = IsEventValid;
readonly IntPtr Predicate = Marshal.GetFunctionPointerForDelegate(PredicateImpl);
static XI2Mouse()
static XI2MouseKeyboard()
{
using (new XLock(API.DefaultDisplay))
{
@ -106,9 +119,8 @@ namespace OpenTK.Platform.X11
}
}
public XI2Mouse()
public XI2MouseKeyboard()
{
Debug.WriteLine("Using XI2Mouse.");
window = new X11WindowInfo();
window.Display = Functions.XOpenDisplay(IntPtr.Zero);
@ -117,6 +129,8 @@ namespace OpenTK.Platform.X11
window.Screen = Functions.XDefaultScreen(window.Display);
window.RootWindow = Functions.XRootWindow(window.Display, window.Screen);
window.Handle = window.RootWindow;
KeyMap = new X11KeyMap(window.Display);
}
if (!IsSupported(window.Display))
@ -124,6 +138,8 @@ namespace OpenTK.Platform.X11
using (new XLock(window.Display))
using (XIEventMask mask = new XIEventMask(1,
XIEventMasks.RawKeyPressMask |
XIEventMasks.RawKeyReleaseMask |
XIEventMasks.RawButtonPressMask |
XIEventMasks.RawButtonReleaseMask |
XIEventMasks.RawMotionMask |
@ -132,10 +148,9 @@ namespace OpenTK.Platform.X11
(XIEventMasks)(1 << (int)ExitEvent)))
{
XI.SelectEvents(window.Display, window.Handle, mask);
UpdateDevices();
}
UpdateDevices();
ProcessingThread = new Thread(ProcessEvents);
ProcessingThread.IsBackground = true;
ProcessingThread.Start();
@ -181,127 +196,95 @@ namespace OpenTK.Platform.X11
return false;
}
void UpdateDevices()
#region IKeyboardDriver2 Members
KeyboardState IKeyboardDriver2.GetState()
{
int count;
unsafe
lock (Sync)
{
XIDeviceInfo* list = (XIDeviceInfo*)XI.QueryDevice(window.Display, 1, out count);
Debug.Print("Refreshing mouse device list");
Debug.Print("{0} mouse devices detected", count);
for (int i = 0; i < count; i++)
KeyboardState state = new KeyboardState();
foreach (XIKeyboard k in keyboards)
{
if (devices.Count <= i)
{
devices.Add(new XIMouse());
}
XIMouse d = devices[i];
d.DeviceInfo = *(list + i);
d.State.SetIsConnected(d.DeviceInfo.enabled);
Debug.Print("Device {0} is {1} and has:",
i, d.DeviceInfo.enabled ? "enabled" : "disabled");
// Decode the XIDeviceInfo to axes, buttons and scroll types
for (int j = 0; j < d.DeviceInfo.num_classes; j++)
{
XIAnyClassInfo* class_info = *((XIAnyClassInfo**)d.DeviceInfo.classes + j);
switch (class_info->type)
{
case XIClassType.Button:
{
XIButtonClassInfo* button = (XIButtonClassInfo*)class_info;
Debug.Print("\t{0} buttons", button->num_buttons);
}
break;
case XIClassType.Scroll:
{
XIScrollClassInfo* scroll = (XIScrollClassInfo*)class_info;
switch (scroll->scroll_type)
{
case XIScrollType.Vertical:
Debug.WriteLine("\tSmooth vertical scrolling");
d.ScrollY = *scroll;
break;
case XIScrollType.Horizontal:
Debug.WriteLine("\tSmooth horizontal scrolling");
d.ScrollX = *scroll;
break;
default:
Debug.Print("\tUnknown scrolling type {0}", scroll->scroll_type);
break;
}
}
break;
case XIClassType.Valuator:
{
XIValuatorClassInfo* valuator = (XIValuatorClassInfo*)class_info;
if (valuator->label == RelX)
{
Debug.WriteLine("\tRelative X movement");
d.MotionX = *valuator;
}
else if (valuator->label == RelY)
{
Debug.WriteLine("\tRelative Y movement");
d.MotionY = *valuator;
}
}
break;
}
}
// Map the hardware device id to the current XIMouse id
if (!rawids.ContainsKey(d.DeviceInfo.deviceid))
{
rawids.Add(d.DeviceInfo.deviceid, 0);
}
rawids[d.DeviceInfo.deviceid] = i;
state.MergeBits(k.State);
}
XI.FreeDeviceInfo((IntPtr)list);
return state;
}
}
KeyboardState IKeyboardDriver2.GetState(int index)
{
lock (Sync)
{
if (index >= 0 && index < keyboards.Count)
{
return keyboards[index].State;
}
return new KeyboardState();
}
}
string IKeyboardDriver2.GetDeviceName(int index)
{
lock (Sync)
{
if (index >= 0 && index < keyboards.Count)
{
return keyboards[index].Name;
}
return String.Empty;
}
}
#endregion
#region IMouseDriver2 Members
public MouseState GetState()
MouseState IMouseDriver2.GetState()
{
MouseState master = new MouseState();
foreach (var d in devices)
lock (Sync)
{
master.MergeBits(d.State);
MouseState master = new MouseState();
foreach (var d in devices)
{
master.MergeBits(d.State);
}
return master;
}
return master;
}
public MouseState GetState(int index)
MouseState IMouseDriver2.GetState(int index)
{
if (devices.Count > index)
return devices[index].State;
else
lock (Sync)
{
if (index >= 0 && index < devices.Count)
{
return devices[index].State;
}
return new MouseState();
}
}
public MouseState GetCursorState()
MouseState IMouseDriver2.GetCursorState()
{
MouseState master = GetState();
master.X = (int)Interlocked.Read(ref cursor_x);
master.Y = (int)Interlocked.Read(ref cursor_y);
return master;
lock (Sync)
{
MouseState master = (this as IMouseDriver2).GetState();
master.X = (int)Interlocked.Read(ref cursor_x);
master.Y = (int)Interlocked.Read(ref cursor_y);
return master;
}
}
public void SetPosition(double x, double y)
void IMouseDriver2.SetPosition(double x, double y)
{
// Note: we cannot use window.Display here, because
// that will deadlock the input thread, which is
// blocking inside XIfEvent
using (new XLock(API.DefaultDisplay))
{
Functions.XWarpPointer(API.DefaultDisplay,
IntPtr.Zero, window.RootWindow, 0, 0, 0, 0, (int)x, (int)y);
Functions.XFlush(API.DefaultDisplay);
Interlocked.Exchange(ref cursor_x, (long)x);
Interlocked.Exchange(ref cursor_y, (long)y);
}
@ -309,6 +292,127 @@ namespace OpenTK.Platform.X11
#endregion
#region Private Members
void UpdateDevices()
{
lock (Sync)
{
devices.Clear();
keyboards.Clear();
int count;
unsafe
{
XIDeviceInfo* list = (XIDeviceInfo*)XI.QueryDevice(window.Display,
XI.XIAllDevices, out count);
Debug.Print("Refreshing input device list");
Debug.Print("{0} input devices detected", count);
for (int i = 0; i < count; i++)
{
switch ((list + i)->use)
{
case XIDeviceType.MasterKeyboard:
//case XIDeviceType.SlaveKeyboard:
{
XIKeyboard k = new XIKeyboard();
k.DeviceInfo = *(list + i);
k.State.SetIsConnected(k.DeviceInfo.enabled);
k.Name = Marshal.PtrToStringAnsi(k.DeviceInfo.name);
int id = k.DeviceInfo.deviceid;
if (!keyboard_ids.ContainsKey(id))
{
keyboard_ids.Add(k.DeviceInfo.deviceid, 0);
}
keyboard_ids[id] = keyboards.Count;
keyboards.Add(k);
}
break;
case XIDeviceType.MasterPointer:
//case XIDeviceType.SlavePointer:
case XIDeviceType.FloatingSlave:
{
XIMouse d = new XIMouse();
d.DeviceInfo = *(list + i);
d.State.SetIsConnected(d.DeviceInfo.enabled);
d.Name = Marshal.PtrToStringAnsi(d.DeviceInfo.name);
Debug.Print("Device {0} \"{1}\" is {2} and has:",
i, d.Name, d.DeviceInfo.enabled ? "enabled" : "disabled");
// Decode the XIDeviceInfo to axes, buttons and scroll types
for (int j = 0; j < d.DeviceInfo.num_classes; j++)
{
XIAnyClassInfo* class_info = *((XIAnyClassInfo**)d.DeviceInfo.classes + j);
switch (class_info->type)
{
case XIClassType.Button:
{
XIButtonClassInfo* button = (XIButtonClassInfo*)class_info;
Debug.Print("\t{0} buttons", button->num_buttons);
}
break;
case XIClassType.Scroll:
{
XIScrollClassInfo* scroll = (XIScrollClassInfo*)class_info;
switch (scroll->scroll_type)
{
case XIScrollType.Vertical:
Debug.WriteLine("\tSmooth vertical scrolling");
d.ScrollY = *scroll;
break;
case XIScrollType.Horizontal:
Debug.WriteLine("\tSmooth horizontal scrolling");
d.ScrollX = *scroll;
break;
default:
Debug.Print("\tUnknown scrolling type {0}", scroll->scroll_type);
break;
}
}
break;
case XIClassType.Valuator:
{
XIValuatorClassInfo* valuator = (XIValuatorClassInfo*)class_info;
if (valuator->label == RelX)
{
Debug.WriteLine("\tRelative X movement");
d.MotionX = *valuator;
}
else if (valuator->label == RelY)
{
Debug.WriteLine("\tRelative Y movement");
d.MotionY = *valuator;
}
}
break;
}
}
// Map the hardware device id to the current XIMouse id
int id = d.DeviceInfo.deviceid;
if (!rawids.ContainsKey(id))
{
rawids.Add(id, 0);
}
rawids[id] = devices.Count;
devices.Add(d);
}
break;
}
}
XI.FreeDeviceInfo((IntPtr)list);
}
}
}
void ProcessEvents()
{
while (!disposed)
@ -341,6 +445,8 @@ namespace OpenTK.Platform.X11
// Nothing to do
break;
case XIEventType.RawKeyPress:
case XIEventType.RawKeyRelease:
case XIEventType.RawMotion:
case XIEventType.RawButtonPress:
case XIEventType.RawButtonRelease:
@ -360,59 +466,73 @@ namespace OpenTK.Platform.X11
void ProcessRawEvent(ref XGenericEventCookie cookie)
{
unsafe
lock (Sync)
{
XIRawEvent raw = *(XIRawEvent*)cookie.data;
if (!rawids.ContainsKey(raw.deviceid))
unsafe
{
Debug.Print("Unknown mouse device {0} encountered, ignoring.", raw.deviceid);
return;
}
XIRawEvent raw = *(XIRawEvent*)cookie.data;
XIMouse mouse;
XIKeyboard keyboard;
var d = devices[rawids[raw.deviceid]];
switch (raw.evtype)
{
case XIEventType.RawMotion:
if (GetMouseDevice(raw.deviceid, out mouse))
{
ProcessRawMotion(mouse, ref raw);
}
break;
switch (raw.evtype)
{
case XIEventType.RawMotion:
ProcessRawMotion(d, ref raw);
break;
case XIEventType.RawButtonPress:
case XIEventType.RawButtonRelease:
if (GetMouseDevice(raw.deviceid, out mouse))
{
float dx, dy;
MouseButton button = X11KeyMap.TranslateButton(raw.detail, out dx, out dy);
mouse.State[button] = raw.evtype == XIEventType.RawButtonPress;
}
break;
case XIEventType.RawButtonPress:
switch (raw.detail)
{
case 1: d.State.EnableBit((int)MouseButton.Left); break;
case 2: d.State.EnableBit((int)MouseButton.Middle); break;
case 3: d.State.EnableBit((int)MouseButton.Right); break;
case 8: d.State.EnableBit((int)MouseButton.Button1); break;
case 9: d.State.EnableBit((int)MouseButton.Button2); break;
case 10: d.State.EnableBit((int)MouseButton.Button3); break;
case 11: d.State.EnableBit((int)MouseButton.Button4); break;
case 12: d.State.EnableBit((int)MouseButton.Button5); break;
case 13: d.State.EnableBit((int)MouseButton.Button6); break;
case 14: d.State.EnableBit((int)MouseButton.Button7); break;
}
break;
case XIEventType.RawButtonRelease:
switch (raw.detail)
{
case 1: d.State.DisableBit((int)MouseButton.Left); break;
case 2: d.State.DisableBit((int)MouseButton.Middle); break;
case 3: d.State.DisableBit((int)MouseButton.Right); break;
case 8: d.State.DisableBit((int)MouseButton.Button1); break;
case 9: d.State.DisableBit((int)MouseButton.Button2); break;
case 10: d.State.DisableBit((int)MouseButton.Button3); break;
case 11: d.State.DisableBit((int)MouseButton.Button4); break;
case 12: d.State.DisableBit((int)MouseButton.Button5); break;
case 13: d.State.DisableBit((int)MouseButton.Button6); break;
case 14: d.State.DisableBit((int)MouseButton.Button7); break;
}
break;
case XIEventType.RawKeyPress:
case XIEventType.RawKeyRelease:
if (GetKeyboardDevice(raw.deviceid, out keyboard))
{
Key key;
if (KeyMap.TranslateKey(raw.detail, out key))
{
keyboard.State[key] = raw.evtype == XIEventType.RawKeyPress;
}
}
break;
}
}
}
}
bool GetMouseDevice(int deviceid, out XIMouse mouse)
{
if (!rawids.ContainsKey(deviceid))
{
Debug.Print("Unknown mouse device {0} encountered, ignoring.", deviceid);
mouse = null;
return false;
}
mouse = devices[rawids[deviceid]];
return true;
}
bool GetKeyboardDevice(int deviceid, out XIKeyboard keyboard)
{
if (!keyboard_ids.ContainsKey(deviceid))
{
Debug.Print("Unknown keyboard device {0} encountered, ignoring.", deviceid);
keyboard = null;
return false;
}
keyboard = keyboards[keyboard_ids[deviceid]];
return true;
}
unsafe static void ProcessRawMotion(XIMouse d, ref XIRawEvent raw)
{
// Note: we use the raw values here, without pointer
@ -453,6 +573,8 @@ namespace OpenTK.Platform.X11
bool valid = false;
if ((long)e.GenericEventCookie.extension == arg.ToInt64())
{
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawKeyPress;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawKeyRelease;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawMotion;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonPress;
valid |= e.GenericEventCookie.evtype == (int)XIEventType.RawButtonRelease;
@ -470,6 +592,8 @@ namespace OpenTK.Platform.X11
}
}
#endregion
#region IDisposable Members
public void Dispose()
@ -496,7 +620,7 @@ namespace OpenTK.Platform.X11
}
}
~XI2Mouse()
~XI2MouseKeyboard()
{
Dispose(false);
}