#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);
}