Rolling One's Own Keyboard/Input system in C/C++

10,963

Solution 1

Update: Here is a library I wrote for handling keyboard input. It uses the FreeBSD license. I've even tagged it as v1.0, so I consider it to be "release-quality".

https://github.com/depp/keycode

I worked very hard recently to get this "just right" for gaming, and I'm not done yet. I'll share what I know.

Key codes

For games, key codes are usually what you want.

When you press a key on your keyboard, the OS first translates the button press into a key code. The key code specifies the physical location of the key on the keyboard. For example code 4 might correspond to the key labeled A on US keyboards (even if that key has a different label in France or Russia). Each platform has a different set of key codes, or possibly multiple sets. You may know them by a different name, such as scan codes or virtual key codes.

  • Windows uses virtual key codes (MSDN documentation). They are stable across various hardware and software configurations. You can find the definitions in the <Winuser.h> header file. On Windows, if you press the leftmost home row key (A in the US, Q in France), you get code 65.

  • Mac OS X has key codes which have been stable since the 80s. They are defined in <Carbon/Events.h>. You don't actually need to link to Carbon to use the key codes, but you need the header. On OS X, if you press the leftmost home row key, you get code 4.

  • Linux has several divergent set of key codes. So on Linux, you have a few options. You can either use key syms (which have drawbacks which I'll explain below), you can assume the user is using a specific input driver (Evdev is a very good guess these days), or you can somehow figure out which input driver the machine uses. In order to get keycodes, you have to read the keyboard definition files. For example, look at /usr/share/X11/xkb/keycodes/evdev for Evdev key codes. With Evdev, if you press the leftmost home row key, you get code 38.

Of course, it would be too easy if the key code were the same across platforms. You can either use the platform-specific key code or translate it into a platform-independent value. I suggest using the USB HID codes (pdf) as platform-independent codes, since a number of smart people already went through the trouble of agreeing on what to call each key.

The library I've posted above has tables for each platform such as WIN_NATIVE_TO_HID for translating key codes to USB HID codes.

The hard part is communicating to the user what button they should press, but at least people from other countries can play your game.

Character codes

You don't want to use character codes, even though they're easier to use if you live in the US and your audience also lives in the US.

After translating the button press into a key code, the OS then translates the key code into a character code. The character code is affected by the current keyboard layout, and often affected by modifier keys as well.

So if you press A on your keyboard, you'll get key codes 65, 4, or 38, depending on what platform you're on. But you'll get the character code 'a' or 'A' depending on whether the shift key is down, or you might get 'Q' if the keyboard layout is set to French, or 'Ф' if the keyboard layout is set to Russian. So, if you code WASD into your game and use character codes, input will be totally broken when someone from another country plays your game. You'd have to use ZQSD in France, ЦФЫВ in Russia, and pretty soon you have a headache.

I use a non-QWERTY layout myself (Dvorak) and most games are utterly broken. In the place of the W key is ,, which becomes < if you have the shift key down, and some games don't recognize that as the same key. For example, I'd press , to move forward, but if I'd release the button while the shift key was down the game would think that I had released the < and think that the , was still down, so I'd keep moving forward. Most games that use SDL are broken on the Mac for me even if I switch to the US keyboard layout (I think this is a glitch in SDL).

LibSDL

SDL 2.0 provides platform-independent key codes, called scan codes. Use the "SDL_scancode.h" header. The SDL developers came to the same conclusion that the scan codes should be translated back to USB HID codes, so the SDL scan codes are completely compatible with the library I posted above (see keycode.h and SDL_scancode.h, the numeric values are identical).

For this and other reasons, if you are using SDL 1.2, I strongly recommend upgrading to 2.0.

Solution 2

What follows is only related to processing input on Windows.

As mentioned by capr in the comment to Dietrich Epp's answer, VK_ codes change based on the language of the keyboard layout you're using. So if your keyboard layout is french and you press 'Q' on your QWERTY-keyboard, the virtual key for 'A' is produced. If your layout is e.g. en-US, the virtual key for 'Q' is produced, as expected.

Since actual scan codes are hardware dependent (and thus not usable), I cirumvented this problem of layout dependence by translating the generated scan code to a virtual key corresponding to the en-US keyboard layout. This can then be translated to the USB HID codes in the manner Dietrich Epp described. This produces the same expected USB HID code regardless of the language of the layout.

The way to translate scan code to VK of en-US,

// Get and store the name of the currently used locale
wchar_t defaultLayoutName[KL_NAMELENGTH];
GetKeyboardLayoutName(defaultLayoutName)

// Load and store a handle to the locale for en-US ("00000409")
HKL defaultEnUSInputLocale = LoadKeyboardLayout(TEXT("00000409"), KLF_NOTELLSHELL);

// Load the locale that was in use before so as not to change the keyboard layout of the user
LoadKeyboardLayout(defaultLayoutName, KLF_ACTIVATE);

--- (at windowProc) ---
case WM_INPUT:
    // Use the handle to the en-US locale to translate the scan codes to VK_.
    virtualKey = MapVirtualKeyExW(scanCode, MAPVK_VSC_TO_VK_EX, defaultEnUSInputLocale);

    // translate virtualKey to USB HID by using e.g. the codes supplied by Dietrich Epp
    int8_t usbHIDkey = VK_TO_HID[virtualKey];

Note that there are some additional hurdles related to virtual keys on Windows. I found this site to be of excellent help with the matter: https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/

Share:
10,963
zeboidlund
Author by

zeboidlund

Updated on June 06, 2022

Comments

  • zeboidlund
    zeboidlund almost 2 years

    Question:

    What kind of resources are needed to learn how to create one's own Input/Output system?

    My Own Understanding:

    I know it's very operating system dependent, so let's split up both Linux and Windows and list the resources for both operating systems (if possible). For Linux, I'm guessing a fair knowledge of the X Window system is required. For Windows, I'm guessing the win32 API. Still, I'm guessing there is more to it than simply knowing these, as if possible I would rather write the input system in C++.

    Reason for asking:

    I tried reading the OIS source code (as this will be written in either C or C++), and just didn't like the way it was written. Therefore, I've taken it upon myself to learn how to write my own keyboard input/output system for a simple pong game (written in C++).

  • zeboidlund
    zeboidlund over 12 years
    One of the best answers I've ever received in a question. You deserve more than just a +1 and an accept. Unfortunately, that's all I'm able to give.
  • Admin
    Admin over 8 years
    actually this answer is wrong for Windows, VK_ codes are hardware-independent but they are not layout-independent, try switching your layout to french and see what key responds with VK_Q. What you need to use for Windows is scan codes.