Windows 7's multi-touch API is pretty impressive
and easy to use. However, there are a few occasions where the API is limited. For example, some touch-screens have features like higher precision that would otherwise
be unavailable to the developer, or if your application needs to support versions of Windows 7 not containing tablet support.
RAWINPUT API is preferred for DirectX games due to lower latency; however, multi-touch devices using
RAWINPUT are completely unsupported out of the box.
Thus, this example demonstrates how to directly interpret the HID data to handle multi-touch devices.
When communicating with USB devices on Windows, you immediately think of using the
CreateFile API. However, for devices such as keyboards and mice,
Windows has an exclusive lock on the file handle, preventing applications from sending and receiving data from these devices.
Unfortunately, Windows treats digitizers, including multi-touch devices, the same. But there is hope - Enter
RAWINPUT API, which allows parsing of raw HID data.
Unfortunately, raw data is exactly what you get. So before you write any code, you need to track down the specifications for how the HID data is structured.
These data arrangements are vendor and product specific, and some are even protected under NDAs, making it impossible to release end-all be-all solutions.
The good news is that Linux supports the most common multi-touch digitizers, and Linux is
Open Source. Thus, in most cases you can simply reverse engineer Linux's touchscreen drivers to generate the appropriate structures. In other cases,
you can usually search the vendor's website for product specifications or contact them directly to obtain the documentation needed to parse HID data.
Alternatively, you can sniff HID packets, but this is not always as easy as looking at the values in a hex editor. In order to reduce USB bandwidth,
HID data is encoded on a per-bit level. Thus, there is no guarantee that the data is byte aligned. As a result, reverse engineering is fairly time consuming,
but still possible for those hardcore enough to try. In fact, the vendor IDs and product IDs can be obtained as easily as opening up the device in Device Manager.
For the case of this example, we use a dummy layout similar (but changed enough to not violate any copyrights) to a common implementation:
#define VENDOR_ID 0x0001
#define PRODUCT_ID 0x0001
BYTE status : 1;
BYTE in_range : 1;
BYTE padding : 1;
BYTE touch_id : 5;
BYTE x_position_msbyte : 4;
BYTE y_position_lsbyte : 4;
int X ()
return (((x_position_msbyte & 0x0F) << 8) + x_position_lsbyte);
int Y ()
return (((y_position_msbyte & 0x0F) << 4) + y_position_lsbyte);
TouchData touch ;
With the vendor specific data defined, we can begin digging into the
RAWDATA API. The first thing that is needed is to verify that the multi-touch
device we are trying to support is attached. We do this by iterating through each HID attached and matching both the vendor and product IDs:
bool supported_touchscreen_present (false);
UINT input_device_count (0);
if (GetRawInputDeviceList (NULL, &input_device_count, sizeof (RAWINPUTDEVICELIST)) != 0)
if (GetRawInputDeviceList (&input_devices , &input_device_count,
sizeof (RAWINPUTDEVICELIST)) == (UINT)-1)
for (vector<RAWINPUTDEVICELIST>::iterator device_iterator (input_devices.begin ());
device_iterator != input_devices.end ();
UINT info_size (sizeof (RID_DEVICE_INFO));
if (GetRawInputDeviceInfo (device_iterator->hDevice,
RIDI_DEVICEINFO, (LPVOID)&device_info, &info_size) == info_size)
if (device_info.dwType == RIM_TYPEHID)
if ((device_info.hid.dwVendorId == VENDOR_ID)
&& (device_info.hid.dwProductId == PRODUCT_ID))
supported_touchscreen_present = true;
If the device is attached, we can then register for multi-touch HID messages to be received by our window's message put through
RAWINPUTDEVICE raw_input_device ;
raw_input_device .usUsagePage = 0x0D;
raw_input_device .dwFlags = RIDEV_INPUTSINK | RIDEV_PAGEONLY;
raw_input_device .usUsage = 0x00;
raw_input_device .hwndTarget = hwnd;
if (RegisterRawInputDevices (raw_input_device, 1, sizeof (raw_input_device )) == FALSE)
And now the waiting game... When input from the multi-touch digitizer is received, the
WM_INPUT event will fire. Retrieving data through the
is a two step process - first, you query the size of the data; then, after allocating the appropriate amount of memory, you actually receive a copy of the data.
RAWDATA API supports grouping packets together; thus, to ensure full compatibility and responsiveness, each packet indicated in the header should be parsed.
If the structures are defined correctly, the data in the packets themselves can just be reinterpreted casted to the supported HID structure.
While our example only registers for a single specific multi-touch device, the function
GetRawInputDeviceInfo can be used
to determine which digitizer specifically generated the
UINT data_size (0);
GetRawInputData ((HRAWINPUT)l_param, RID_INPUT, NULL,
&data_size, sizeof (RAWINPUTHEADER));
if (GetRawInputData ((HRAWINPUT)l_param, RID_INPUT, &data ,
&data_size, sizeof(RAWINPUTHEADER)) != data_size)
RAWINPUT* raw = (RAWINPUT*)(&data );
if (raw->header.dwType == RIM_TYPEHID)
for (DWORD index (0); index < raw->data.hid.dwCount; ++index)
DigitizerData* result ((DigitizerData*)&raw->data.hid.bRawData [raw->data.hid.dwSizeHid * index]);
for (BYTE touch_index (0); touch_index < result->active_touch_count; ++touch_index)
} while (0);
result = DefWindowProc (window_handle, message, w_param, l_param);