Wednesday, November 18, 2009

Raw Input Introduction Part 1: Keyboard Input

Since I've found very little information about Raw Input API on the net, I thought it could be of some help to write an article as an introduction to it. So here it is.
Let's get started!

Registering Raw Input Devices
The first thing we need to do is registering the input device we'd like our application to monitor.
The main and probably most important difference of Raw Input from DirectInput and standard Win32 messages, is that Raw Input can retrieve which device has sent the input and not just the input.
That means that if you have two keyboards connected, Raw Input will be able to recognise the input and which one of the two keyboards has sent it.
That's why we need to register our input devices.
Let's make a header file called RawInputUtils.h and write the following code:

void InitRawInput(HWND hWnd)
{
RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x06;
Rid[0].dwFlags = RIDEV_INPUTSINK;
Rid[0].hwndTarget = hWnd;

if (RegisterRawDataInputDevices(Rid,1,sizeof(Rid[0])) == false)
{
  MessageBox(NULL, "Device Registration failed", "Error",MB_OK);
  return;
}
}

RAWINPUTDEVICE is a structure used to describe the input device we want to monitor. Its parameters are the following:
  • usUsagePage Top level HID usage page. For most HIDs the value is 0x01
  • usUsage Number indicating which type of device should be monitored. For keyboard the value is 0x06, for mouse it's 0x02. Here you can find a complete list of the possible values.
  • dwFlags Determine how the data should be handled. Here a complete list of the possible values.
  • hwndTarget Handle to the window which will monitor this device.
So what we have done here is:
  1. RAWINPUTDEVICE Rid[1]; We have declared an array of a single element that will hold a RawInputDevice structure that describes our device (in our case the keyboard).
  2. Rid[0].usUsage = 0x06; We specify that our device is a keyboard
  3. Rid[0].dwFlags = RIDEV_INPUTSINK; This means that our application will retrieve input even if it loses focus.
  4. Rid[0].hwndTarget = hWnd; We specifies the handle of the application window that will monitor our device. In our case the one we pass to the InitRawInput function.
After we have described our devices we need to register it:

if (RegisterRawDataInputDevices(Rid,1,sizeof(Rid[0])) == false) { //Error}

The RegisterRawDataInputDevices() function returns false if the device registration fails.
Its arguments are the following:
  1. pRawInputDevices Pointer to an array of RAWINPUTDEVICE structures that represent the devices we want to register (in our case we have just a single element, the keyboard)
  2. uiNumDevices Number of RAWINPUTDEVICE structures we have inside our RAWINPUTDEVICE array
  3. cbSize Size in bytes of a RAWINPUTDEVICE structure.
The next step is to call our InitRawInput() function somewhere inside our WinMain() functon so we can register our device for our application.
First of all, let's add an include to our RawInputUtils header we have just wrote.
Then add this code in the WinMain() function just after the ShowWindow() function call (don't worry if you don't get where you have to put it, you can find the complete source code at the bottom):


InitRawInput(hWnd);


The InitRawInput() call should have described our device and registered it. If nothing went wrong we can proceed further.

Retrieving raw input
Now, inside our WndProc() function we need to add the WM_INPUT case inside our switch(msg) statement:

case WM_INPUT:
{
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
.....

GetRawInputData() accept the following arguments:
  1. hRawInput Handle to the RAWINPUT structure containing the input data we want to process. This is provided by the lParam in WM_INPUT message.
  2. uiCommand Flag that sets whether to retrieve the input data or the header information from the RAWINPUT structure. Possible valures are RID_INPUT or RID_HEADER.
  3. pData  If we set it to NULL, the size of the buffer required to contain the data is returned in the pcbSize variable. Otherwise, pData must be a pointer to allocated memory that can hold the RAWINPUT structure provided by the WM_INPUT message.
  4. pcbSize A variable that returns or specifies the size of the data pointed by pData.
  5. cbSizeHeader Size of a RAWINPUTHEADER structure.
Once we called this function, our dwSize variable will correspond to the exact number of bytes needed to store the raw input data.
Now, we declare a pointer to the right amount of memory to store the rawinput data:

lpb = new BYTE[dwSize];

Next we call the GetRawInputData() function for the second time to ensure that lpb points to a right amount of memory to store the raw input data of dwSize size.

if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize,sizeof(RAWINPUTHEADER)) != dwSize)
{
//Error
}


Now we need to "cast" our lpb pointer to RAWINPUT structure which gives easy access to the data various members.

raw = (RAWINPUT*)lpb;

Processing raw input
First we check if the raw input data is of keyboard type:

if (raw->header.dwType == RIM_TYPEKEYBOARD)
{ 

Next we ensure to retrieve both system and non-system key down events:

if (raw->data.keyboard.Message == WM_KEYDOWN || raw->data.keyboard.Message == WM_SYSKEYDOWN)
{ 

Then we returns in the window title the key code of the key we press.

USHORT usKey;
usKey = raw->data.keyboard.VKey;
CHAR szTest[4];
_itoa_s((int)usKey,szTest,10);
SetWindowText(hWnd,szTest);
}
}
delete[] lpb;

This little example should show you the three main steps to follow to implement Raw Input inside your application.
Comments and questions are welcome.
Hope this article will be useful. Sorry for my bad style of coding ^^.
Until next time, cheers!

Source code:



1 comments:

frankc said...

I just read your excellent article on Windows C++ RawInput. I was wondering if you could provide a list of all linker libraries and their additional include directories.
Have you used the Windows 7 control panel Windows Controller Utility? When I press the button on the VM Joystick, I observe ***** no buttons **** lighting up on the Windows' Controllers Test Screen. Would you know why that happens and how I could fix that?

Post a Comment