The raw input API is built into Windows and allows access to the raw data from HIDs (Human Interface Devices). It is the recommended method of obtaining input from mouse and keyboard and is preferred to DirectInput. Before reading these notes you need to be familiar with creating a window and receiving windows messages. See the Windows API notes for details.
Ultimately we will be trapping WM_INPUT messages in our windows procedure (see WndProc notes) and then getting raw data from the input devices. However you will not get any messages until you register to receive messages from a device. To register we need to fill out a structure with the id of the device. You need one per device. The easiest way to explain all this is via an example and then an explanation. Below is an example showing how to register to receive messages from keyboard and mouse:
Rid.usUsagePage = 1;
Rid.usUsage = 6;
Rid.dwFlags = 0;
Rid.usUsagePage = 1;
Rid.usUsage = 2;
Rid.dwFlags = 0;
- The usUsagePage is a value for the type of device. Normally we will use 1 here as that stands for “generic desktop controls” and covers all the usual input devices (see the tables below).
- The usUsage value specifies the device within the “generic desktop controls” group. 2 is mouse, 4 is joystick, 6 is keyboard (for more see tables below)
- dwFlags allows various flags to be specified. One useful one is RIDEV_NOLEGACY. If you specify this for the keyboard your program will no longer receive any messages like WM_KEYDOWN (see WndProc notes). If you specify it for the mouse you will no longer get messages like WM_LBUTTONDOWN. This falg can be useful when writing a game running full screen however if you are testing in a window I would suggest not to use them otherwise all menus etc. will not work. Another potentially useful flag is RIDEV_DEVNOTIFY that provides notifications to your window (via WM_INPUT_DEVICE_CHANGE messages) when a device is hot plugged / removed.
- hwndTarget can be used to restrict messages to a particular window. If this is NULL then the current window focus is used.
// Handle error
The above function call does the actual registering of the devices. You must supply an array of devices, the number of devices and the size of the structure. If this returns false an error has occurred. You need to register your devices after the window has been created but before the first update. So either do it straight after calling CreateWindow in your WinMain or trap the WM_CREATE message in your window procedure and do it there.
You are now ready to start trapping raw input messages.
Trapping WM_INPUT messages
Once you have registered one of more devices your windows procedure will start to receive WM_INPUT messages to your WndProc when a device has input to read.
// this is where we read the device data
The function we want to use to read this data is GetRawInputData. This function fills a buffer you provide with data from the device. There is a problem though and that is knowing the size of the buffer to provide to the device as it varies between devices. Therefore you can call the function twice, once with no buffer to simply find out how big the buffer should be and then with a buffer created to the correct size. For example:
// Determine how big the buffer should be
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &bufferSize, sizeof (RAWINPUTHEADER));
// Create a buffer of the correct size – but see note below
BYTE *buffer=new BYTE[bufferSize];
// Call the function again, this time with the buffer to get the data
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, (LPVOID)buffer, &bufferSize, sizeof (RAWINPUTHEADER));
This function takes a handle to the RAWINPUT structure from the device. This handle is provided for us in the lParam of the message so we just need to pass it on. The second parameter is either RID_INPUT to get the raw device data (which we want) or RID_HEADER to get the header information for the data. The third parameter is a void pointer to the buffer to be filled. If we want to just get the size needed for a buffer we pass NULL in here. The next parameter is the address of a variable that will be filled by the function with the required size of the buffer. The final parameter is always the same and is the size of the RAWINPUTHEADER structure. The function returns the number of bytes written into the structure so as a sanity check you can make sure this value matches with the bufferSize you expected to get.
Note that creating a dynamic buffer every time you trap a WM_INPUT message is not a very good idea for a game where you want to avoid dynamic memory allocation during the game loop as much as possible. Therefore you are better to provide an already allocated fixed sized buffer. How big to make it though? Well the mouse size is 40 and the keyboard 32 so in the case of trapping mouse and keyboard IÂ make a buffer of size 40 but for safety I still call the function with no buffer first to make sure the buffer size returned is not greater than 40.
Now that we have a buffer filled with data from the device we need to interpret it based on what device it came from. We do this by casting it to a RAWINPUT structure and then reading the type from the header data e.g.
RAWINPUT *raw = (RAWINPUT*) buffer;
if (raw->header.dwType== RIM_TYPEMOUSE)
// read the mouse data
if (raw->header.dwType== RIM_TYPEKEYBOARD)
// read the keyboard data
The only other type is RIM_TYPEHID which is for other Human Interface Devices and needs further interpretation of data contained in the RAWHID structure specific to the device.
The RAWINPUT structure has members of type RAWMOUSE, RAWKEYBOARD and RAWHID. We take our values from the relevant member depending on which device the data has come from. For example the mouse:
if (raw->header.dwType == RIM_TYPEMOUSE)
Â Â // Get values from the mouse member (of type RAWMOUSE)
Â Â long mx = raw->data.mouse.lLastX;
Â Â long my = raw->data.mouse.lLastY;
Note that the values returned are not screen space values like you get from the WM_MOUSEMOVE message. This is raw data we are getting so the values need interpreting. The values are normally relative to the last position and hence indicate motion. To be sure you can check the usFlags in the RAWMOUSE structure.
Mouse button values are returned in the usButtonFlags member of the RAWMOUSE structure. There is a whole set of possible flags including:
- …..all the way to RI_MOUSE_BUTTON_5_UP
- RI_MOUSE_WHEEL – if you get this message then you should also read the usButtonData value to get the change in mouse wheel position
Note that these are not exclusive i.e. you may have more than one of them set at a time. Each one corresponds to a bit in the flag. Therefore you need to do a bitwise & (and) in order to determine a value. E.g.
bool buttonDown=raw->data.mouse.ulButtons & RI_MOUSE_LEFT_BUTTON_DOWN;
The flag is only set once when the button is pressed and so to determine if the mouse button is being held down you need to record it as down until you get a RI_MOUSE_LEFT_BUTTON_UP flag.
The RAWINPUT structure also has a member called keyboard of type RAWKEYBOARD which, if the header type is RIM_TYPEKEYBOARD, will be filled with key information e.g.
if (raw->header.dwType == RIM_TYPEKEYBOARD)
// Get key value from the keyboard member (of type RAWKEYBOARD)
The virtual key code returned corresponds to a key on the keyboard. Microsoft provide a set of #defines that can be used to interpret this value; for example:
// move player left
// move player right etc.
There are a whole host of these codes for all the keys on the keyboard including special keys like the launch app buttons on some keyboards (for a full list see here). If you want to interpret the value as a standard alphanumeric value the codes for the letters and numbers fortunately match with the ASCII code of a character so to detect the T key being pressed we could do this:
// T key was pressed
Note that you must use the capital letter and that the quotes around the letter are single quotes (for a character). To detect a shift key you would trap VK_SHIFT first – see below. It also works for the number keys along the top of the keyboard but not for the numpad keys (for those you need to use special VK codes like VK_NUMPAD2).
Key up or down?
When a key is held down the raw->data.heyboard.Flags has the RI_KEY_MAKEÂ bit set (actually none as the value is 0) and when the key is released theÂ RI_KEY_BREAKÂ bit is set (value is 1). For example when the shift key is held the virtual key is VK_SHIFT and the Flags value is 0. When it is released you get the same data but with the Flags value equal to 1. However do not compare just on values as some keys set additional bits e.g. the up arrow sets bit 2 (flag RI_KEY_E0) so instead do a bitwise &(and) e.g.
bool keyUp=raw->data.keyboard.Flags & RI_KEY_BREAK;
Note: the structure member MakeCode is the key scancode,. This is the raw key code prior to translation into the local language setting of the keyboard and is the same as that returned by DirectInput.
Key repeat delay problems
If you just register when a key is pressed down your game will end up suffering from the Windows keyboard repeat delays i.e. the time after pressing a key for it to repeat,. The way to get around this in your game is to record when a key is pressed and consider it to be continuously pressed until you get a key release. So you will need to remember when a key (or better still an action is current) and only reset it when you get a key up. I normally do this with a structure of actions. For example one member would be a ‘fire’ member and I would set that to true when I get a key down e.g. the enter key. Each time the world is updated I would check this flag and if it is set I would fire. The flag would only be set back to false when I get a message saying the key has been released.
There is another function to get raw data from a device called GetRawInputBuffer. This differs from GetRawInputData in that it returns an array of RAWINPUTÂ buffers instead of one at a time. This means any data is collected in a buffer until such time as you want to access it. This can be useful for devices that send a lot of information.
There are many combinations of usUsagePage and usUsage values and if you want to see them all I suggest you check out this (very large) list here: http://www.usb.org/developers/devclass_docs/Hut1_12.pdf. The main ones though are:
1 for generic desktop controls
2 for simulation controls
3 for vr
4 for sport
5 for game
6 for generic device
7 for keyboard
8 for leds
usUsage values when usUsagePage is 1
0 – undefined
1 – pointer
2 – mouse
3 – reserved
4 – joystick
5 – game pad
6 – keyboard
7 – keypad
8 – multi-axis controller
9 – Tablet PC controls