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.
- 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).
- Rid[0].usUsage = 0x06; We specify that our device is a keyboard
- Rid[0].dwFlags = RIDEV_INPUTSINK; This means that our application will retrieve input even if it loses focus.
- 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.
if (RegisterRawDataInputDevices(Rid,1,sizeof(Rid[0])) == false) { //Error}
The RegisterRawDataInputDevices() function returns false if the device registration fails.
Its arguments are the following:
- 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)
- uiNumDevices Number of RAWINPUTDEVICE structures we have inside our RAWINPUTDEVICE array
- cbSize Size in bytes of a RAWINPUTDEVICE structure.
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));
.....
- hRawInput Handle to the RAWINPUT structure containing the input data we want to process. This is provided by the lParam in WM_INPUT message.
- 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.
- 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.
- pcbSize A variable that returns or specifies the size of the data pointed by pData.
- 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)
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)
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;
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: