20 hours ago, trill41 said:
Microsoft suggests to use the default Windows messages
Even if Microsoft suggest this, it isn't the practise we really want to do. XInput is driving the same route these days catching on driver level while WM-Messages are quite looped through all the messages a window is receiving from the OS, including repaints and whatever so dont do this!
I was fighting with that some time too because I wanted to also grab HID Gamepad inputs from the OS without linking to the Media library. So HID was the only route to not have to worm with XInput.
You first need the Windows Driver SDK or define the bindings yourself and just lookup at initialization when needed. These are:
#ifndef INITGUID
#define INITGUID
#endif
#ifdef interface
#undef interface
#endif
#include <Windows.h>
#include <Dbt.h>
typedef struct _HIDD_ATTRIBUTES
{
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
}
HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
typedef USHORT USAGE;
typedef struct _HIDP_CAPS
{
USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
}
HIDP_CAPS, *PHIDP_CAPS;
typedef enum _HIDP_REPORT_TYPE
{
HidP_Input,
HidP_Output,
HidP_Feature
}
HIDP_REPORT_TYPE;
typedef struct _USAGE_AND_PAGE
{
USAGE Usage;
USAGE UsagePage;
}
USAGE_AND_PAGE, *PUSAGE_AND_PAGE;
typedef struct _HIDP_BUTTON_CAPS
{
USAGE UsagePage;
UCHAR ReportID;
BOOLEAN IsAlias;
USHORT BitField;
USHORT LinkCollection; // A unique internal index pointer
USAGE LinkUsage;
USAGE LinkUsagePage;
BOOLEAN IsRange;
BOOLEAN IsStringRange;
BOOLEAN IsDesignatorRange;
BOOLEAN IsAbsolute;
ULONG Reserved[10];
union {
struct {
USAGE UsageMin, UsageMax;
USHORT StringMin, StringMax;
USHORT DesignatorMin, DesignatorMax;
USHORT DataIndexMin, DataIndexMax;
} Range;
struct {
USAGE Usage, Reserved1;
USHORT StringIndex, Reserved2;
USHORT DesignatorIndex, Reserved3;
USHORT DataIndex, Reserved4;
} NotRange;
};
}
HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS;
typedef struct _HIDP_VALUE_CAPS
{
USAGE UsagePage;
UCHAR ReportID;
BOOLEAN IsAlias;
USHORT BitField;
USHORT LinkCollection;
USAGE LinkUsage;
USAGE LinkUsagePage;
BOOLEAN IsRange;
BOOLEAN IsStringRange;
BOOLEAN IsDesignatorRange;
BOOLEAN IsAbsolute;
BOOLEAN HasNull;
UCHAR Reserved;
USHORT BitSize;
USHORT ReportCount;
USHORT Reserved2[5];
ULONG UnitsExp;
ULONG Units;
LONG LogicalMin, LogicalMax;
LONG PhysicalMin, PhysicalMax;
union {
struct {
USAGE UsageMin, UsageMax;
USHORT StringMin, StringMax;
USHORT DesignatorMin, DesignatorMax;
USHORT DataIndexMin, DataIndexMax;
} Range;
struct {
USAGE Usage, Reserved1;
USHORT StringIndex, Reserved2;
USHORT DesignatorIndex, Reserved3;
USHORT DataIndex, Reserved4;
} NotRange;
};
}
HIDP_VALUE_CAPS, *PHIDP_VALUE_CAPS;
typedef struct _HIDP_DATA
{
USHORT DataIndex;
USHORT Reserved;
union {
ULONG RawValue;
BOOLEAN On;
};
}
HIDP_DATA, *PHIDP_DATA;
typedef void* PHIDP_PREPARSED_DATA;
#define HIDP_STATUS_SUCCESS 0x110000
BOOLEAN (__stdcall *HidD_GetAttributes)(HANDLE device, PHIDD_ATTRIBUTES attrib) = 0;
BOOLEAN (__stdcall *HidD_GetSerialNumberString)(HANDLE device, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetManufacturerString)(HANDLE handle, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetProductString)(HANDLE handle, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_SetFeature)(HANDLE handle, PVOID data, ULONG length) = 0;
BOOLEAN (__stdcall *HidD_GetFeature)(HANDLE handle, PVOID data, ULONG length) = 0;
BOOLEAN (__stdcall *HidD_GetIndexedString)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len) = 0;
BOOLEAN (__stdcall *HidD_GetPreparsedData)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data) = 0;
BOOLEAN (__stdcall *HidD_FreePreparsedData)(PHIDP_PREPARSED_DATA preparsed_data) = 0;
BOOLEAN (__stdcall *HidD_SetNumInputBuffers)(HANDLE handle, ULONG number_buffers) = 0;
NTSTATUS (__stdcall *HidP_GetCaps)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps) = 0;
NTSTATUS (__stdcall *HidP_GetValueCaps) (HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData) = 0;
NTSTATUS (__stdcall *HidP_GetButtonCaps) (HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData) = 0;
NTSTATUS (__stdcall *HidP_GetData) (HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength) = 0;
ULONG (__stdcall *HidP_MaxDataListLength) (HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData) = 0;
#include <string>
#undef INITGUID
#ifdef UNICODE
typedef struct _DEV_BROADCAST_HID
{
DWORD dbcc_size;
DWORD dbcc_devicetype;
DWORD dbcc_reserved;
GUID dbcc_classguid;
wchar_t dbcc_name[200];
}DEV_BROADCAST_HID;
#else
typedef struct _DEV_BROADCAST_HID
{
DWORD dbcc_size;
DWORD dbcc_devicetype;
DWORD dbcc_reserved;
GUID dbcc_classguid;
char dbcc_name[200];
}DEV_BROADCAST_HID;
#endif
I encapsulated this into conditional defined files to keep the whole thing multiplatform (Windows, Linux, Android). Defining those bindings by myself was neccessary because of the Windows Driver SDK just for working with that I and everyone else in my team needed to download and install this complicated to get piece of memory just to grab some header files.
You then need to first enumerate your devices using
uint32 count = 0;
GetRawInputDeviceList(0, (PUINT)&count, sizeof(RAWINPUTDEVICELIST));
if(count <= 0) return false;
Array<RAWINPUTDEVICELIST> devices(count, allocator);
if((count = GetRawInputDeviceList((PRAWINPUTDEVICELIST)&devices[0], (PUINT)&count, sizeof(RAWINPUTDEVICELIST))) <= 0)
return false;
To know the targets for
RID_DEVICE_INFO _deviceInfo;
_deviceInfo.cbSize = sizeof( RID_DEVICE_INFO );
uint32 val_size = _deviceInfo.cbSize;
GetRawInputDeviceInfo(device.handle, RIDI_DEVICEINFO, &_deviceInfo, (PUINT)&val_size));
and
RAWINPUTDEVICE rdv = RAWINPUTDEVICE();
rdv.hwndTarget = (HWND)handle;
rdv.usUsage = static_cast<USHORT>(target);
rdv.usUsagePage = static_cast<USHORT>(extra);
rdv.dwFlags = 0;
return boolean_cast(RegisterRawInputDevices(&rdv, 1, sizeof(RAWINPUTDEVICE)));
To enable your window to get input events for the specified device(s).
Before you could handle what arrives in WM_INPUT, you need to find the protocol definition of your device. HID is a general tehcnic that routes information from the device to your program and vice versa so you need to know hot to communicate with your device. This a kind of protocol which defines the meaning of each single byte in your HID-Input-Report.
An XBox Gamepad has different alignment than one from Logitech so the protocol bytes handle that properly.
int32 val_size = 0; GetRawInputDeviceInfoA((HANDLE)*device, RIDI_DEVICENAME, 0, (PUINT)&val_size );
if(val_size)
{
string path(device.GetAllocator());
path.Resize(val_size);
GetRawInputDeviceInfoA((HANDLE)*device, RIDI_DEVICENAME, path.Begin(), (PUINT)&val_size );
*(path.Begin() + 1) = '\\';
return CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
}
if(!HidD_GetPreparsedData(ntHandle, (PHIDP_PREPARSED_DATA*)&ind.protocol) && ind.Type == InputDevice::HID)
{
CloseHandle(ntHandle);
continue;
}
CloseHandle(ntHandle);
First captures the size of the device path in driver cache and then grabs that path to open the HID-Protocl file. Those paths usually are in the form of HID\VID_046D&PID_C046\6&78d1573&0&0000 (my USB Mouse for example)
You will then obtain the protocol file using the GetPreparsedData call and anything is fine. Last step is getting input from the WM_INPUT message.
uint32 val_size = sizeof(RAWINPUT);
RAWINPUT report; Drough::memset(&report, 0, sizeof(report));
if(GetRawInputData((HRAWINPUT)data, RID_INPUT, &report, (PUINT)&val_size, sizeof(RAWINPUTHEADER)) < signed_cast(-1))
{
switch(report.header.dwType)
{
...
case HID:
{
uint16 size = static_cast<uint16>(report.data.hid.dwSizeHid * report.data.hid.dwCount);
if(raw.Size() != size) raw.Resize(size);
Drough::memcpy(raw.Begin(), report.data.hid.bRawData, size);
}
}
}
This fills the fixed size data buffer (it is dynamic array in my case so don't wonder about raw.Resize()) from the msg.lParam part of WM_INPUT message.
Finally get the real HID input from the preparsed data and the protocol
HIDP_DATA* dta = (HIDP_DATA*)(raw.Begin() + raw.Offset());
ULONG length = (ULONG)raw.Offset() / sizeof(HIDP_DATA);
if(HidP_GetData((HIDP_REPORT_TYPE)0, dta, &length, (PHIDP_PREPARSED_DATA)device.Protocol(), (PCHAR)raw.Begin(), (ULONG)raw.Size()) != HIDP_STATUS_SUCCESS || length == 0)
return false;
And if anything was successfull, you'll end up with an array of HIDP_DATA objects that each contain information about the kind of input like Button/Axis and it's state [On/Off]/Delta.
Depending on how often you resize data, you'll end up with input latency of a few ms.
Hope this brief overview helps