Click here to Skip to main content
15,898,222 members
Articles / Programming Languages / C++

Using the RawInput API to Process MultiTouch Digitizers

Rate me:
Please Sign up or sign in to vote.
4.58/5 (8 votes)
9 May 2012CPOL3 min read 53.5K   3K   22  
A demonstration of how to use Windows' RawInput API to manually process MultiTouch data.
#include <sstream>
#include <string>
#include <vector>
#include <Windows.h>
using namespace std;

// name of our class when registering a window
const TCHAR CLASS_NAME [] = TEXT ("Test");

// evil global variable to keep everyone on their toes:
// cached window size (as float)
float window_width = 640.0f;
float window_height = 480.0f;

// colors to use for each touch
const COLORREF colors [] = 
{
   RGB (0xFF, 0x00, 0x00),
   RGB (0x00, 0xFF, 0x00),
   RGB (0xFF, 0xFF, 0x00),
   RGB (0x00, 0x00, 0xFF),
   RGB (0xFF, 0x00, 0xFF),
   RGB (0x00, 0xFF, 0xFF),
   RGB (0x80, 0x00, 0x00),
   RGB (0x00, 0x80, 0x00),
   RGB (0x80, 0x80, 0x00),
   RGB (0x00, 0x00, 0x80),
   RGB (0x80, 0x00, 0x80),
   RGB (0x00, 0x80, 0x80),
   RGB (0x80, 0x80, 0x80)
};

// given a window handle, draw a pixel of a specified color at the specified coordinates
void setPixel (HWND window_handle, int x, int y, COLORREF color)
{
   if (window_handle != NULL)
   {
      HDC device_context (GetDC (window_handle));
      if (device_context != NULL)
      {
         SetPixel (device_context, x, y, color);
         ReleaseDC (window_handle, device_context);
      }
   }
}

// format an error returned from GetLastError and print it to the debug console window
void printError ()
{
   // retrieve the system error message for the last-error code
   DWORD error_code (GetLastError ());
   LPVOID message_buffer (0);
   FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                  NULL, error_code, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &message_buffer,
                  0, NULL );
   OutputDebugString ((LPCSTR)message_buffer);
}

// the raw HID data for multi-touch digitizers are vendor specific, and some of the
// documents containing the layouts are protected under NDAs. as such, this demo 
// does not specifically include any.
// luckily, Linux has support for the most common ones for you to reverse engineer:
// http://lxr.free-electrons.com/source/drivers/input/touchscreen/
//
// the vendor ID for our supported touchscreen; this and the product ID can be found
// when selecting the touchscreen in Device Manager
#define VENDOR_ID    0x0001
// the product ID for our supported touchscreen
#define PRODUCT_ID   0x0001

// a sample structure containing interpreted HID data for a touch instance;
// as stated earlier, you'll have to roll your own and then use the device's
// vendor and product IDs for each device supported
struct TouchData
{
   // the good news is that bit packing makes reinterpreting the data fairly simple,
   // even in crazy cases where the data is half-aligned such as our interleaved
   // X and Y position
   BYTE status : 1;
   BYTE in_range : 1;
   BYTE padding : 1;
   BYTE touch_id : 5;
   BYTE x_position_lsbyte;
   BYTE x_position_msbyte : 4;
   BYTE y_position_lsbyte : 4;
   BYTE y_position_msbyte;
   // the exception is when data is stored across bytes, especially with
   // partial bits involed. in these cases, helper functions make things
   // easy to work with
   // helper function to get the X value from the multi-byte parameter
   int X ()
   {
      return (((x_position_msbyte & 0x0F) << 8) + x_position_lsbyte);
   }
   // helper function to get the Y value from the multi-byte parameter
   int Y ()
   {
      return (((y_position_msbyte & 0x0F) << 4) + y_position_lsbyte);
   }
};

// a sample structure mapping the entire HID data for a touch event;
// again, these are vendor specific, so you will need to either acquire the data sheets
// for the device or reverse engineer the Linux drivers
struct DigitizerData
{
   BYTE usb_report_id;
   TouchData touch [8];
   BYTE active_touch_count;
};

// multi-touch digitizer coordinates are normalized to the device;
// these device specific constants normalize the coordinates to be within 0-1
#define VENDOR_SPECIFIC_MAX_X 1024.0f
#define VENDOR_SPECIFIC_MAX_Y 1024.0f

// our window's event handler; pay specific attention to WM_INPUT, as that is where the
// RAWDATA is received
LRESULT CALLBACK WindowProc (HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
   LRESULT result (0);

   switch (message)
   {
      case WM_DESTROY:
         PostQuitMessage (0); 
         break;

      // cache the current size of the window
      case WM_SIZE:
         {
            switch (w_param)
            {
               case SIZE_MAXIMIZED:
               case SIZE_RESTORED:
                  {
                     window_width = (float)(LOWORD (l_param));
                     window_height = (float)(HIWORD (l_param));
                  }
                  break;
            }
         }
         break;

      // handle HID raw input
      case WM_INPUT:
         {
            do
            {
               // determine the size of the input data
               UINT data_size (0);
               GetRawInputData ((HRAWINPUT)l_param, RID_INPUT, NULL, &data_size, sizeof (RAWINPUTHEADER));
               // preallocate our buffer
               vector<BYTE> data;
               data.resize (data_size);
               // and then read the input data in
               if (GetRawInputData ((HRAWINPUT)l_param, RID_INPUT, &data [0], &data_size, sizeof(RAWINPUTHEADER)) != data_size)
               {
                  OutputDebugString (TEXT("GetRawInputData does not return correct size !\n")); 
                  break;
               }
               // the RAWINPUT structure starts at the beginning of our data array
               RAWINPUT* raw = (RAWINPUT*)(&data [0]);
               // make sure keyboard/mouse HID data didn't somehow sneak its way in here
               if (raw->header.dwType == RIM_TYPEHID) 
               {
                  std::ostringstream stream;
                  // for each packet received..
                  for (DWORD index (0); index < raw->data.hid.dwCount; ++index)
                  {
                     // reinterpret the data as our nicely formatted digitizer-specific structure
                     DigitizerData* result ((DigitizerData*)&raw->data.hid.bRawData [raw->data.hid.dwSizeHid * index]);
                     // for each touch registered...
                     for (BYTE touch_index (0); touch_index < result->active_touch_count; ++touch_index)
                     {
                        // get the touch coordinates in device space
                        int touch_x (result->touch [touch_index].X ());
                        int touch_y (result->touch [touch_index].Y ());
                        // add the coordinates to our debug output stream
                        stream << (int)result->touch [touch_index].touch_id << " - X: " << touch_x << "\tY: " << touch_y << endl;
                        // normalize the coordinates
                        float x ((float)touch_x);
                        x /= VENDOR_SPECIFIC_MAX_X;
                        x = std::max<float> (0.0f, std::min<float> (x, 1.0f));
                        float y ((float)touch_y);
                        y /= VENDOR_SPECIFIC_MAX_Y;
                        y = std::max<float> (0.0f, std::min<float> (y, 1.0f));
                        // then convert them into window locations
                        x *= window_width;
                        y *= window_height;
                        // round to the nearest integer
                        x += 0.5f;
                        y += 0.5f;
                        // pick a unique color by wrapping around the colors array
                        int color_index = result->touch [touch_index].touch_id % (sizeof (colors) / sizeof (COLORREF));
                        // then set a pixel on the window using the new color
                        setPixel (window_handle, (int)x, (int)y, colors [color_index]);                        
                     }
                  }
                  stream << endl;
                  // print out the touch events received in this packet to the debug console window
                  OutputDebugString (stream.str ().c_str ());
               }
            } while (0);
            // the application must call DefWindowProc so the system can perform the cleanup
            result = DefWindowProc (window_handle, message, w_param, l_param);
         }
         break;

      default: 
         result = DefWindowProc (window_handle, message, w_param, l_param);
         break;
   }
   return result;
}

// our application's entry point
int CALLBACK WinMain (__in  HINSTANCE hInstance, __in  HINSTANCE hPrevInstance,
                      __in  LPSTR lpCmdLine, __in  int nCmdShow)
{
   do
   {
      // step 1: make sure that we support the multitouch device
      bool supported_touchscreen_present (false);
      // get the number of HID input devices
      UINT input_device_count (0);
      if (GetRawInputDeviceList (NULL, &input_device_count, sizeof (RAWINPUTDEVICELIST)) != 0) 
      {
         break;
      }
      // allocate memory for their structures
      vector<RAWINPUTDEVICELIST> input_devices;
      input_devices.resize (input_device_count);
      // then populate the device list
      if (GetRawInputDeviceList (&input_devices [0], &input_device_count, sizeof (RAWINPUTDEVICELIST)) == (UINT)-1)
      {
         break;
      }
      // for each device...
      RID_DEVICE_INFO device_info;
      for (vector<RAWINPUTDEVICELIST>::iterator device_iterator (input_devices.begin ());
           device_iterator != input_devices.end ();
           ++device_iterator)
      {
         UINT info_size (sizeof (RID_DEVICE_INFO));
         if (GetRawInputDeviceInfo (device_iterator->hDevice, RIDI_DEVICEINFO, (LPVOID)&device_info, &info_size) == info_size)
         {
            // non-keyboard, non-mouse HID device?
            if (device_info.dwType == RIM_TYPEHID)
            {
               if ((device_info.hid.dwVendorId == VENDOR_ID)
                && (device_info.hid.dwProductId == PRODUCT_ID))
               {
                  supported_touchscreen_present = true;
                  break;
               }
            }
         }
      }
      // if no touch screens were found, exit gracefully
      if (supported_touchscreen_present == false)
      {
         OutputDebugString (TEXT("No supported touchscreens found"));
         break;
      }

      // step 2: create our window to handle the multitouch device's input
      // register the class
      HINSTANCE h_instance (GetModuleHandle (0));
      WNDCLASSEX wcex;
      wcex.cbSize        = sizeof (WNDCLASSEX);
      wcex.style         = CS_HREDRAW | CS_VREDRAW;
      wcex.lpfnWndProc   = WindowProc;
      wcex.cbClsExtra    = 0;
      wcex.cbWndExtra    = 0;
      wcex.hInstance     = h_instance;
      wcex.hIcon         = LoadIcon (h_instance, MAKEINTRESOURCE (IDI_APPLICATION));
      wcex.hCursor       = LoadCursor (NULL, IDC_ARROW);
      wcex.hbrBackground = (HBRUSH)GetStockObject (BLACK_BRUSH);
      wcex.lpszMenuName  = 0;
      wcex.lpszClassName = CLASS_NAME;
      wcex.hIconSm       = LoadIcon (wcex.hInstance, MAKEINTRESOURCE (IDI_APPLICATION));
      RegisterClassEx (&wcex);
      // create the window
      HWND hwnd = CreateWindowEx (0, CLASS_NAME, CLASS_NAME, WS_VISIBLE | WS_CAPTION | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU, 
                                  0, 0, (int)window_width, (int)window_height, NULL, NULL, GetModuleHandle (0), NULL);
      if (hwnd == NULL)
      {
         // something went wrong, error gracefully
         printError ();
         break;
      }

      // step 3: register for multi-touch HID messages
      RAWINPUTDEVICE raw_input_device [1];
      // Starting with Windows 7, multitouch digitizers appear as HID touch digitizers (page 0x0D, usage 0x04), 
      // but they also contain the contact ID usage in their report descriptor (page 0x0D, usage 0x51).
      raw_input_device [0].usUsagePage = 0x0D; 
      // RIDEV_PAGEONLY specifies all devices whose top level collection is from the specified usUsagePage.
      // Note that usUsage must be zero.
      raw_input_device [0].dwFlags = RIDEV_INPUTSINK | RIDEV_PAGEONLY;
      raw_input_device [0].usUsage = 0x00;
      // route the RAWINPUT messages to our window; this is required for the RIDEV_INPUTSINK option
      raw_input_device [0].hwndTarget = hwnd;
      // listen to digitizer events
      if (RegisterRawInputDevices (raw_input_device, 1, sizeof (raw_input_device [0])) == FALSE)
      {
         // something went wrong, error gracefully
         printError ();
         DestroyWindow (hwnd);
         break;
      }
      // from here it's just standard show the window and process all messages code...
      ShowWindow (hwnd, SW_SHOW);
      while (true)
      {
         MSG msg; 
         BOOL ret (GetMessage (&msg, 0, 0, 0));
         if (ret > 0)
         {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
         }
         else
            break;
      }
   } while (0);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions