If you support keyboard input in your XNA game, one issue you need to be aware of is alternate keyboard layouts. Due to the way the XNA Framework handles keyboard input, if you create your game while using the QWERTY keyboard layout, and a customer runs your game while using the DVORAK keyboard layout, he will have to press different keys on his keyboard. At first, this might seem logical – but consider the typical case:
A customer downloads and installs your game. He’s running it on a Windows PC. He has his keyboard layout set to DVORAK, because he heard it helps reduce wrist strain. His keyboard still has QWERTY labels printed on it, so he presses ‘S’ to type an ‘O’.
He starts your game up, and let’s say the splash screen says ‘Press S to continue’. He presses S on his keyboard.
Nothing happens.
This is because the OS is remapping the S keystroke into an O. For your XNA game to see an S keystroke, he will have to press the semicolon key (;:) instead.
While users with alternate keyboard layouts are a comparative minority compared to people using the QWERTY keyboard layout, that’s no excuse for ignoring them. As it turns out, the solution to this problem is pretty straightforward.
What you need to do is find a way to map a keystroke in your keyboard layout – let’s say QWERTY – to whatever keyboard layout your customer is running at the time. The Win32 API actually makes this quite simple, so as long as you’re willing to include some P/Invoke code in windows builds of your game, you can accomplish this in a couple dozen lines of code.
To accomplish this, I use the following helper struct:
uusing System;
using Microsoft.Xna.Framework.Input;
using System.Runtime.InteropServices;
namespace Labyrinth.Framework {
public struct LocalizedKeyboardState {
internal enum MAPVK : uint {
VK_TO_VSC = 0,
VSC_TO_VK = 1,
VK_TO_CHAR = 2
}
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
internal extern static uint MapVirtualKeyEx (uint key, MAPVK mappingType, IntPtr keyboardLayout);
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
internal extern static IntPtr LoadKeyboardLayout (string keyboardLayoutID, uint flags);
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
internal extern static bool UnloadKeyboardLayout (IntPtr handle);
[DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)]
internal extern static IntPtr GetKeyboardLayout (IntPtr threadId);
internal const uint KLF_NOTELLSHELL = 0x00000080;
public struct KeyboardLayout : IDisposable {
public readonly IntPtr Handle;
public KeyboardLayout (IntPtr handle) : this() {
Handle = handle;
}
public KeyboardLayout (string keyboardLayoutID)
: this(LoadKeyboardLayout(keyboardLayoutID, KLF_NOTELLSHELL)) {
}
public bool IsDisposed {
get;
private set;
}
public void Dispose () {
if (IsDisposed)
return;
UnloadKeyboardLayout(Handle);
IsDisposed = true;
}
public static KeyboardLayout US_English = new KeyboardLayout("00000409");
public static KeyboardLayout Active {
get {
return new KeyboardLayout(GetKeyboardLayout(IntPtr.Zero));
}
}
}
public readonly KeyboardState Native;
public LocalizedKeyboardState (KeyboardState keyboardState) {
Native = keyboardState;
}
public bool IsKeyDown (Keys key, bool isLocalKey) {
if (!isLocalKey)
key = USEnglishToLocal(key);
return Native.IsKeyDown(key);
}
public bool IsKeyUp (Keys key, bool isLocalKey) {
if (!isLocalKey)
key = USEnglishToLocal(key);
return Native.IsKeyDown(key);
}
public bool IsKeyDown (Keys key) {
return IsKeyDown(key, false);
}
public bool IsKeyUp (Keys key) {
return IsKeyDown(key, false);
}
// Maps a localized character like 'S' to the virtual scan code
// for that key on the user's keyboard ('O' in dvorak, for example)
public static Keys USEnglishToLocal (Keys key) {
var activeScanCode = MapVirtualKeyEx((uint)key, MAPVK.VK_TO_VSC, KeyboardLayout.US_English.Handle);
var nativeVirtualCode = MapVirtualKeyEx(activeScanCode, MAPVK.VSC_TO_VK, KeyboardLayout.Active.Handle);
return (Keys)nativeVirtualCode;
}
}
}
Here’s how it works: First, at startup, we ask the Win32 API to load up a specific keyboard layout; US English QWERTY. From then on, we can ask the Win32 API to convert a ‘virtual key’ from that keyboard layout into a scan code. Once we have a scan code, we can then ask the Win32 API to convert that scan code into the equivalent virtual key for the end-user’s keyboard layout. Since the XNA Framework uses virtual keys (the Keys enumeration contains virtual key values), this allows us to apply this technique to existing keyboard input code without any significant changes.
So, with this helper struct, you can add support for alternate keyboard layouts like DVORAK with only a couple changes:
- First, add the helper struct to your game code somewhere so you have access to it.
- Replace any uses of the XNA KeyboardState struct with the LocalizedKeyboardState helper struct. If you use some of the more obscure KeyboardState helper methods, you may need to do some work to add them to LocalizedKeyboardState; it only provides IsKeyUp and IsKeyDown.
- Figure out whether any of the keys you’re using should not be remapped based on the current keyboard layout. For example, it’s common practice for some kinds of games to expose a ‘console’ that allows the user to view log messages and enter commands. In a lot of games, you press the tilde (~) key to open the console. This is a convenient key since it’s near the top left corner of a QWERTY keyboard. However, if you allow the keystroke to be remapped to the current layout, non-QWERTY typists will find they have to press some random key on their keyboard to open the console. In this case, you’ll want to pass a value of true for the second, optional parameter to the IsKeyDown/IsKeyUp helper methods:
if (ks.IsKeyDown(Keys.OemTilde, true))
ShowConsole();


#1 by FutureMillennium on September 6, 2009 - 11:10 am
Quote
Works great! Thanks for the code!
By the way, might be a good idea to mention how you assign it in the article, since it’s different from KeyboardState:
LocalizedKeyboardState ks = new LocalizedKeyboardState(Keyboard.GetState());
#2 by Matthew Weigel on September 18, 2009 - 11:00 pm
Quote
I’m finally getting around to visiting your blog post-AGDC; as a Dvorak user I’m kind of happy to see this. It’s not clear from your description, but it sounds like with this approach you would display “press S to continue” and then the user is expected (if they’re using Dvorak) to press O.
If I have that wrong I apologize; but it seems like a poor user interface. I think a better approach (assuming you aren’t going to provide full key-mapping functionality, which may be out of scope) would be to convert the QWERTY keys you are interested in at the beginning to the keymappings the user has in place, and then use those mappings to display messages (and look for those keypresses). E.g., modify the string “Press %c to continue” based on what character is generated when the user presses the key two to the right of the CapsLock key.
#3 by Mike on September 29, 2009 - 5:29 am
Quote
Good work.
I don’t think it’s poor user interface at all. When you see “Press S” on the screen, you should understand it as “Press the key that produces S”. If you chose to change the mapping, that’s your responsibility to find that key.
Also, the whole DVORAK superiority thing is complete myth…
http://www.reason.com/news/show/29944.html