Created
July 3, 2025 19:41
-
-
Save luser/f170804c8c2e2c9debea940e23edda6d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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