Skip to content

Instantly share code, notes, and snippets.

@luser
Created July 3, 2025 19:41
Show Gist options
  • Save luser/f170804c8c2e2c9debea940e23edda6d to your computer and use it in GitHub Desktop.
Save luser/f170804c8c2e2c9debea940e23edda6d to your computer and use it in GitHub Desktop.
// RawInputTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
using std::vector;
const struct {
int usagePage;
int usage;
} kUsagePages[] = {
{ 1, 4 }, // Joystick
{ 1, 5 } // Gamepad
};
const int kUsageDpad = 0x39; // USB HID usage tables, page 1 (Hat switch)
const unsigned kFirstAxis = 0x30; // USB HID usage tables, page 1, 0x30 = X
const unsigned kDesktopUsagePage = 0x1;
const unsigned kButtonUsagePage = 0x9;
const unsigned kMaxButtons = 32;
const unsigned kMaxAxes = 32;
struct RawGamepad {
HANDLE handle;
char name[128];
int vendorId;
int productId;
unsigned numButtons;
unsigned numAxes;
bool hasDpad;
HIDP_VALUE_CAPS dpadCaps;
bool buttons[kMaxButtons];
struct {
HIDP_VALUE_CAPS caps;
double value;
} axes[kMaxAxes];
};
vector<RawGamepad> gamepads;
bool GetPreparsedData(HANDLE handle, vector<uint8_t>& data)
{
UINT size;
if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0)
return false;
data.resize(size);
return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, &data[0], &size) > 0;
}
double ScaleAxis(ULONG value, LONG min, LONG max)
{
return 2.0 * (value - min) / (max - min) - 1.0;
}
static bool
HandleRawInput(HRAWINPUT handle) {
// First, get data from the handle
UINT size;
GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
vector<uint8_t> data(size);
if (GetRawInputData(handle, RID_INPUT, &data[0], &size, sizeof(RAWINPUTHEADER)) < 0)
return false;
PRAWINPUT raw = reinterpret_cast<PRAWINPUT>(&data[0]);
RawGamepad* gamepad = nullptr;
for (unsigned i = 0; i < gamepads.size(); i++) {
if (gamepads[i].handle == raw->header.hDevice) {
gamepad = &gamepads[i];
break;
}
}
if (gamepad == nullptr)
return false;
// Second, get the preparsed data
vector<uint8_t> parsedbytes;
if (!GetPreparsedData(raw->header.hDevice, parsedbytes))
return false;
PHIDP_PREPARSED_DATA parsed = reinterpret_cast<PHIDP_PREPARSED_DATA>(&parsedbytes[0]);
vector<USAGE> usages(gamepad->numButtons);
ULONG usageLength = gamepad->numButtons;
if (HidP_GetUsages(HidP_Input, kButtonUsagePage, 0, &usages[0], &usageLength, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS)
return false;
ZeroMemory(gamepad->buttons, sizeof(gamepad->buttons));
usageLength = min(usageLength, kMaxButtons);
for (unsigned i = 0; i < usageLength; i++) {
gamepad->buttons[usages[i] - 1] = true;
}
/*
printf("buttons[");
for (unsigned i = 0; i < gamepad->numButtons; i++) {
printf("%d", int(gamepad->buttons[i]));
}
printf("] ");
*/
printf("axes[");
for (unsigned i = 0; i < gamepad->numAxes; i++) {
if (gamepad->axes[i].caps.LogicalMin < 0) {
LONG value;
if (HidP_GetScaledUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0, gamepad->axes[i].caps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS)
continue;
gamepad->axes[i].value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, gamepad->axes[i].caps.LogicalMax);
}
else {
ULONG value;
if (HidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0, gamepad->axes[i].caps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS)
continue;
gamepad->axes[i].value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, gamepad->axes[i].caps.LogicalMax);
}
printf("% 0.02lf ", gamepad->axes[i].value);
}
printf("] ");
if (gamepad->hasDpad) {
ULONG value;
if (HidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
//UnpackDpad(value, gamepad);
double dpad = ScaleAxis(value, gamepad->dpadCaps.LogicalMin, gamepad->dpadCaps.LogicalMax);
printf("dpad[%d", value);
if (value < gamepad->dpadCaps.LogicalMin || value > gamepad->dpadCaps.LogicalMax) {
//printf(" ");
}
else {
//printf("% 0.02lf", dpad);
}
printf("]");
}
}
printf("\r");
return true;
}
bool SupportedUsage(USHORT page, USHORT usage)
{
for (unsigned i = 0; i < ARRAYSIZE(kUsagePages); i++) {
if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage)
return true;
}
return false;
}
bool
is_in(const std::vector<std::pair<int, int>>& v, const std::pair<int, int>& x)
{
for (unsigned i = 0; i < v.size(); i++) {
if (v[i].first == x.first && v[i].second == x.second) {
return true;
}
}
return false;
}
bool
GetRawGamepadInfo(HANDLE handle, RawGamepad& gamepad, FILE* out)
{
RID_DEVICE_INFO rdi;
ZeroMemory(&rdi, sizeof(RID_DEVICE_INFO));
UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO);
if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) < 0) {
return false;
}
if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) {
return false;
}
gamepad.vendorId = rdi.hid.dwVendorId;
gamepad.productId = rdi.hid.dwProductId;
wchar_t devname[256];
size = sizeof(devname);
if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, &devname, &size) < 0) {
return false;
}
OutputDebugString(devname);
// Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx
// device names containing "IG_" are XInput controllers.
if (wcsstr(devname, L"IG_"))
return false;
wchar_t name[128] = { 0 };
size = sizeof(name);
HANDLE hid_handle = CreateFile(devname, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
if (hid_handle) {
if (HidD_GetProductString(hid_handle, &name, size)) {
WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad.name, sizeof(gamepad.name), nullptr, nullptr);
}
CloseHandle(hid_handle);
}
if (!gamepad.name[0]) {
strcpy_s(gamepad.name, "Unknown Gamepad");
}
vector<uint8_t> preparsedbytes;
if (!GetPreparsedData(handle, preparsedbytes)) {
return false;
}
PHIDP_PREPARSED_DATA parsed = reinterpret_cast<PHIDP_PREPARSED_DATA>(&preparsedbytes[0]);
HIDP_CAPS caps;
if (HidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS)
return false;
USHORT count = caps.NumberInputButtonCaps;
vector<HIDP_BUTTON_CAPS> buttonCaps(count);
if (HidP_GetButtonCaps(HidP_Input, &buttonCaps[0], &count, parsed) != HIDP_STATUS_SUCCESS)
return false;
fprintf(out, "{'name': '%s', 'vendor_id': '%04x', 'product_id': '%04x',\n 'button_caps': [\n", gamepad.name, gamepad.vendorId, gamepad.productId);
for (unsigned i = 0; i < count; i++) {
gamepad.numButtons += buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1;
fprintf(out, " {'usage_page': 0x%x, 'usage_min': 0x%x, 'usage_max': 0x%x, 'report_id': %d, 'is_range': %d}\n", buttonCaps[i].UsagePage, buttonCaps[i].Range.UsageMin, buttonCaps[i].Range.UsageMax, buttonCaps[i].ReportID, buttonCaps[i].IsRange);
}
fprintf(out, " ]\n 'value_caps': [\n");
count = caps.NumberInputValueCaps;
vector<HIDP_VALUE_CAPS> valueCaps(count);
if (HidP_GetValueCaps(HidP_Input, &valueCaps[0], &count, parsed) != HIDP_STATUS_SUCCESS)
return false;
vector<std::pair<int,int>> axes;
for (unsigned i = 0; i < count; i++) {
if (valueCaps[i].UsagePage == kDesktopUsagePage && valueCaps[i].Range.UsageMin == kUsageDpad) {
gamepad.hasDpad = true;
gamepad.dpadCaps = valueCaps[i];
}
else if (!(valueCaps[i].UsagePage == kDesktopUsagePage && valueCaps[i].Range.UsageMin == 0)) {
auto a = std::make_pair(valueCaps[i].UsagePage, valueCaps[i].Range.UsageMin);
if (!is_in(axes, a)) {
axes.push_back(a);
}
}
fprintf(out, " {'usage_page': 0x%x, 'usage_min': 0x%x, 'usage_max': 0x%x, 'report_id': %d, 'report_count': %d,}\n", valueCaps[i].UsagePage, valueCaps[i].Range.UsageMin, valueCaps[i].Range.UsageMax, valueCaps[i].ReportID, valueCaps[i].ReportCount);
}
printf("\n");
std::sort(axes.begin(), axes.end());
for (unsigned i = 0; i < axes.size(); i++) {
if (i >= kMaxAxes)
break;
for (unsigned j = 0; j < count; j++) {
if (valueCaps[j].UsagePage == axes[i].first && valueCaps[j].Range.UsageMin == axes[i].second) {
gamepad.axes[i].caps = valueCaps[j];
break;
}
}
gamepad.numAxes++;
}
fprintf(out, " ]\n}\n");
gamepad.handle = handle;
return true;
}
static
LRESULT CALLBACK
WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
const unsigned int DBT_DEVICEARRIVAL = 0x8000;
const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004;
const unsigned int DBT_DEVNODES_CHANGED = 0x7;
switch (msg) {
case WM_DEVICECHANGE:
if (wParam == DBT_DEVICEARRIVAL ||
wParam == DBT_DEVICEREMOVECOMPLETE ||
wParam == DBT_DEVNODES_CHANGED) {
printf("Devices changed\n");
}
break;
case WM_INPUT:
HandleRawInput((HRAWINPUT)lParam);
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
bool RegisterRawInput(HWND hwnd, bool enable)
{
vector<RAWINPUTDEVICE> rid(ARRAYSIZE(kUsagePages));
for (unsigned i = 0; i < rid.size(); i++) {
rid[i].usUsagePage = kUsagePages[i].usagePage;
rid[i].usUsage = kUsagePages[i].usage;
rid[i].dwFlags = enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE;
rid[i].hwndTarget = hwnd;
}
if (!RegisterRawInputDevices(&rid[0], rid.size(), sizeof(RAWINPUTDEVICE))) {
printf("Error registering raw input\n");
return false;
}
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
WNDCLASSW wc;
HMODULE hSelf = GetModuleHandle(nullptr);
if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
ZeroMemory(&wc, sizeof(WNDCLASSW));
wc.hInstance = hSelf;
wc.lpfnWndProc = WindowProc;
wc.lpszClassName = L"MozillaGamepadClass";
RegisterClassW(&wc);
}
HWND hwnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
0, 0, 0, 0, 0,
nullptr, nullptr, hSelf, nullptr);
wchar_t filename[MAX_PATH];
GetModuleFileNameW(hSelf, filename, MAX_PATH);
wchar_t* slash = wcsrchr(filename, L'\\');
if (!slash) {
slash = filename;
} else {
slash++;
}
wcscpy_s(slash, MAX_PATH - (slash - filename), L"devices.out");
UINT numDevices;
GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST));
vector<RAWINPUTDEVICELIST> devices(numDevices);
if (GetRawInputDeviceList(&devices[0], &numDevices, sizeof(RAWINPUTDEVICELIST)) != -1) {
FILE* out;
_wfopen_s(&out, filename, L"wb");
if (!out)
return 1;
fprintf(out, "{'devices': [\n");
for (unsigned i = 0; i < devices.size(); i++) {
if (devices[i].dwType != RIM_TYPEHID) {
continue;
}
RawGamepad gamepad = { 0 };
if (!GetRawGamepadInfo(devices[i].hDevice, gamepad, out)) {
continue;
}
gamepads.push_back(gamepad);
printf("%s %04x-%04x: %d buttons, %d axes (dpad: %s)\n", gamepad.name, gamepad.vendorId, gamepad.productId, gamepad.numButtons, gamepad.numAxes, gamepad.hasDpad ? "yes" : "no");
}
fprintf(out, "\n]\n}\n");
fclose(out);
}
RegisterRawInput(hwnd, true);
BOOL bRet;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Return the exit code to the system.
return msg.wParam;
//return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment