65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 RawInput API 处理多点触控数字化仪

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (8投票s)

2012年5月9日

CPOL

3分钟阅读

viewsIcon

55476

downloadIcon

3025

演示如何使用 Windows 的 RawInput API 手动处理多点触摸数据。

RawInput API to Process MultiTouch Digitizers

引言

Windows 7 的 多点触摸 API 相当令人印象深刻,并且易于使用。但是,在某些情况下,API 会受到限制。例如,某些触摸屏具有更高的精度等功能,而开发人员无法使用这些功能,或者如果您的应用程序需要支持不包含平板电脑支持的 Windows 7 版本。同样,由于较低的延迟,RAWINPUT API 优先用于 DirectX 游戏;但是,使用 RAWINPUT 的多点触摸设备在开箱即用时完全不受支持。因此,本示例演示了如何直接解释 HID 数据来处理多点触摸设备。

解决方案

当与 Windows 上的 USB 设备通信时,您会立即想到使用 CreateFile API。但是,对于键盘和鼠标等设备,Windows 会独占锁定文件句柄,阻止应用程序从这些设备发送和接收数据。不幸的是,Windows 对数字化仪(包括多点触摸设备)的处理方式相同。但还有希望 - 输入 RAWINPUT API,它允许解析原始 HID 数据。

不幸的是,您得到的正是原始数据。因此,在编写任何代码之前,您需要查找 HID 数据结构的规范。这些数据排列是特定于供应商和产品的,有些甚至受到 NDA 的保护,这使得发布终极解决方案成为不可能。好消息是 Linux 支持最常见的多点触摸数字化仪,而 Linux 是开源的。因此,在大多数情况下,您可以简单地逆向工程 Linux 的 触摸屏驱动程序以生成适当的结构。在其他情况下,您通常可以在供应商的网站上搜索产品规格,或直接联系他们以获取解析 HID 数据所需的文件。或者,您可以嗅探 HID 数据包,但这并不总是像查看十六进制编辑器中的值那么容易。为了减少 USB 带宽,HID 数据是在每位级别编码的。因此,不能保证数据是字节对齐的。因此,逆向工程非常耗时,但对于那些足够硬核的人来说,仍然是可能的。事实上,可以像在设备管理器中打开设备一样轻松地获取供应商 ID 和产品 ID。对于本例,我们使用类似于(但已更改足够多以不侵犯任何版权)常见实现的虚拟布局

// 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;
};

定义了特定于供应商的数据后,我们可以开始深入研究 RAWDATA API。首先需要验证我们要支持的多点触摸设备是否已连接。我们通过迭代每个连接的 HID 并匹配供应商和产品 ID 来实现这一点

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

如果设备已连接,我们就可以注册多点触摸 HID 消息,以通过我们的窗口的 WM_INPUT 事件接收

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)
{
 // handle the error gracefully
 printError ();
}

现在是等待游戏... 当从多点触摸数字化仪接收到输入时,将触发 WM_INPUT 事件。通过 RAWINPUT API 检索数据是一个两步过程 - 首先,您查询数据的大小;然后,在分配了适当的内存量后,您实际上会收到数据的副本。RAWDATA API 支持将数据包分组在一起;因此,为确保完全兼容性和响应性,应解析标头中指示的每个数据包。如果结构定义正确,数据包中的数据本身可以重新解释为支持的 HID 结构。虽然我们的示例仅注册单个特定的多点触摸设备,但函数 GetRawInputDeviceInfo 可用于确定哪个数字化仪专门生成了 WM_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)
       {
          // handle the error gracefully
          printError ();
          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) 
       {
          // 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)
             {
                // insert touch handler code here
             }
          }
       }
    } while (0);
    // the application must call DefWindowProc so the system can perform the cleanup
    result = DefWindowProc (window_handle, message, w_param, l_param);
}
break;
© . All rights reserved.