BlueMonkMN's picture

OpenTK.Input.Key Numeric Keypad Enter?

It appears that there is no enumeration member to represent the enter key on the numeric keypad. Is this true? Is it intentional? Do some platforms not distinguish between numeric keypad enter and the other enter key?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
BlueMonkMN's picture

I created the enum by pressing keys on my keyboard and mapping the resulting number to an enum value name that I associated with that key. It's totally empirical.

It looks like XGetKeyboardMapping is what I was missing. That would probably map these hardware-dependent values to standard values. I'll try that when I get a chance. Is there any way you can implement this in OpenTK in a way that doesn't rely on GameWindow to read the keyboard? (So I could use it if I were just using the control?)

the Fiddler's picture
BlueMonkMN wrote:

It looks like XGetKeyboardMapping is what I was missing. That would probably map these hardware-dependent values to standard values. I'll try that when I get a chance. Is there any way you can implement this in OpenTK in a way that doesn't rely on GameWindow to read the keyboard? (So I could use it if I were just using the control?)

I'm not sure I follow you 100% here, but if you check X11Input.cs (which uses XGetKeyboardMapping), you'll see that it's not bound to the GameWindow, so maybe you could start by modifying that? AFAICS, you should be able to create an X11Input instance for a System.Windows.Forms.Control and it would work (but the Control would swallow some of your keyboard events with the current implementation).

BlueMonkMN's picture

You said you were about to add keyboard support to the new GameWindow, so I was worried that whatever you were doing was going to tie keyboard input to GameWindow, making it unavailable to other access methods that don't use GameWindow.

BlueMonkMN's picture

Does it seem odd to you that XGetKeyboardMapping expects a a first_keycode parameter of type KeyCode (= CARD8 = byte) which must be between min_keycodes and max_keycodes returned by XDisplayKeyCodes, but the types of those values are int?

the Fiddler's picture

A little bit, yeah. I guess it's that way for future extensibility: X11 only supports 8bit keycodes right now, but IIRC there is a google summer-of-code proposal for removing the limit in xorg. No idea if that will get anywhere, but at least the devs are aware of it.

BlueMonkMN's picture

OK, I updated the code to get the key map and things should be more reliable across keyboards now:

enum KeyCodes
{
	None = 0,
	Space = 0x20,
	Apostrophe = 0x27,
	Comma = 0x2c,
	Minus = 0x2d,
	Period = 0x2e,
	Slash = 0x2f,
	Digit0 = 0x30,
	Digit1,
	Digit2,
	Digit3,
	Digit4,
	Digit5,
	Digit6,
	Digit7,
	Digit8,
	Digit9,
	SemiColon = 0x3b,
	Equal = 0x3d,
	A = 0x41,
	B,
	C,
	D,
	E,
	F,
	G,
	H,
	I,
	J,
	K,
	L,
	M,
	N,
	O,
	P,
	Q,
	R,
	S,
	T,
	U,
	V,
	W,
	X,
	Y,
	Z,
	LeftBracket = 0x5b,
	Backslash,
	RightBracket,
	Grave = 0x60,
	PrintScreen = 0xfd1d,
	Backspace = 0xff08,
	Tab = 0xff09,
	Enter = 0xff0d,
	Pause = 0xff13,
	ScrollLock = 0xff14,
	SysRq = 0xff15,
	Escape = 0xff1b,
	Home = 0xff50,
	Left,
	Up,
	Right,
	Down,
	PageUp,
	PageDown,
	End,
	Insert = 0xff63,
	Menu = 0xff67,
	Break = 0xff6b,
	NumLock = 0xff7f,
	KeyPadEnter = 0xff8d,
	KeyPadAsterisk = 0xffaa,
	KeyPadPlus = 0xffab,
	KeyPadMinus = 0xffad,
	KeyPadDot = 0xffae,
	KeyPadSlash = 0xffaf,
	KeyPad0 = 0xffb0,
	KeyPad1,
	KeyPad2,
	KeyPad3,
	KeyPad4,
	KeyPad5,
	KeyPad6,
	KeyPad7,
	KeyPad8,
	KeyPad9,
	F1 = 0xffbe,
	F2,
	F3,
	F4,
	F5,
	F6,
	F7,
	F8,
	F9,
	F10,
	F11,
	F12,
	LeftShift = 0xffe1,
	RightShift = 0xffe2,
	LeftCtrl = 0xffe3,
	RightCtrl = 0xffe4,
	CapsLock = 0xffe5,
	LeftAlt = 0xffe9,
	RightAlt = 0xffea,
	LeftSuper = 0xffeb,
	RightSuper = 0xffec,
	Delete = 0xffff
}
 
class MainClass
{
	[DllImport("libX11")] public static extern IntPtr XOpenDisplay(string display_name); 
	[DllImport("libX11")] public static extern void XQueryKeymap(IntPtr display, System.UInt32[] keys); 
	[DllImport("libX11")] public static extern IntPtr XGetKeyboardMapping(IntPtr display, byte first_keycode, int keycode_count, out int keysyms_per_keycode); 
	[DllImport("libX11")] public static extern void XDisplayKeycodes(IntPtr display, out int min_keycodes, out int max_keycodes); 
	[DllImport("libX11")] public static extern IntPtr XFree(IntPtr data); 		
 
	public static System.Collections.Generic.Dictionary<byte, KeyCodes> keyMap;
 
	public static void Main(string[] args)
	{
		GetKeyboardMap();
		XStuff();
	}
 
	public static void GetKeyboardMap()
	{
		IntPtr display = XOpenDisplay(null);
		int minkey, maxkey;
		int keysyms_per_keycode;
		XDisplayKeycodes(display, out minkey, out maxkey);		
		int count = maxkey - minkey + 1;
		IntPtr kmap_ptr = XGetKeyboardMapping(display, (byte)minkey, count, out keysyms_per_keycode);
		int[] kmap = new int[keysyms_per_keycode * count];
		System.Runtime.InteropServices.Marshal.Copy(kmap_ptr, kmap, 0, count * keysyms_per_keycode); 
		XFree(kmap_ptr);
 
		keyMap = new System.Collections.Generic.Dictionary<byte,KeyCodes>();
 
		for (int i = 0; i < count * keysyms_per_keycode; i++)
		{
			byte keyCode = (byte)(i / keysyms_per_keycode + minkey);
			int keySym = kmap[i];
			if (!keyMap.ContainsKey(keyCode) &&
			    (System.Enum.IsDefined(typeof(KeyCodes), keySym)))
				keyMap[keyCode] = (KeyCodes)keySym;
		}
	}
 
	public static void XStuff()
	{
		IntPtr display = XOpenDisplay(null);
		System.UInt32[] keys = new System.UInt32[8];
		System.UInt32[] keysOld = new System.UInt32[8];
		System.UInt32[] keysDiff = new System.UInt32[8];
		byte downKey;
		KeyCodes downKeyCode = KeyCodes.None;
		do
		{
			XQueryKeymap(display, keys);
			for (int i=0; i < keys.Length; i++)
				keysDiff[i] = keysOld[i] ^ keys[i];
			keys.CopyTo(keysOld, 0);
			downKey = (byte)GetFirstBit(keysDiff);
			if (keyMap.ContainsKey(downKey))
				downKeyCode = keyMap[downKey];
			else
				downKeyCode = KeyCodes.None;
			if (downKey > 0)
			{
				if (keyMap.ContainsKey(downKey))
					Console.WriteLine("{0}={1} ", downKey, downKeyCode.ToString());
				else
					Console.WriteLine("{0}=? ", downKey);
			}
			System.Threading.Thread.Sleep(100);
		} while(downKeyCode != KeyCodes.Escape);
	}
 
	private static int GetFirstBit(System.UInt32[] data)
	{
		for(int arrayIdx=0; arrayIdx<data.Length; arrayIdx++)
		{
			if(data[arrayIdx] != 0)
			{
				for(int bitIdx = 0; bitIdx < 32; bitIdx++)
				{
					if((data[arrayIdx] & (1u << bitIdx)) != 0)
					{
						return (arrayIdx << 5) + bitIdx;
					}
				}
			}
		}
		return 0;
	}
}
BlueMonkMN's picture

Before I get too far down the path I'm on (which I may have just done, now that I think about it), I have a question about multi-platform .NET builds. What's the best way to handle different code for different platforms? I think in OpenTK, you compile all the code for all platforms and switch between them at runtime, right? So I started doing that in my keyboard implementation, but then I remembered I am also using FMOD, and I have code like this, intending that I define the "LINUX" constant when building the project under Linux:

    public class VERSION
 
    {
#if LINUX 
 
        public const int    number = 0x00042201;
 
        public const string dll    = "libfmodex-4.22.01.so";
#else
        public const int    number = 0x00042006;
 
        public const string dll    = "fmodex.dll";
#endif
 
    }

Is there a better way to handle that?

the Fiddler's picture

It depends on how these values are used. For P/Invoke you can define the name of the Windows library (fmodex.dll) and use a dll.config file to map this value to the correct linux .so (check out OpenTK.dll.config).

For other things you could define a common interface with different platform-specific implementations. Then at runtime, you instantiate the correct implementation for the platform you are on.

BlueMonkMN's picture

OK, I have completed the Linux portion of class that supports keyboard input for Linux and Windows (as odd as that sounds), switching at runtime based on Environment.OSVersion.Platform. There's code for the Windows keyboard handling, but it currently requires that windows messages be passed to it and I intend to convert it (back) to the polling method of reading the keyboard (after I reboot into Windows). I'll attach what I have. I don't know if you have your own solution going or are still looking for this kind of input.

Did you get Joystick support fully integrated for all platforms? I think that's all I need now before my runtime framework is completely cross platform.

BTW, I was able to eliminate the compile time distinction by using a config file as you suggested (for FMOD).

AttachmentSize
KeyboardState.cs14.82 KB