235 lines
8.6 KiB
C
235 lines
8.6 KiB
C
// hid_discover.c — enumerate HID devices, find EdgeTx radio, dump caps
|
|
//
|
|
// Build via CMake (target: hid_discover)
|
|
//
|
|
// Usage:
|
|
// hid_discover.exe (lists all HID devices)
|
|
// hid_discover.exe 1209 4F54 (find EdgeTx radio, dump input caps)
|
|
|
|
#include <windows.h>
|
|
#include <setupapi.h>
|
|
#include <hidsdi.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
// ENUM_GUID from devguid.h — used to enumerate HID class devices
|
|
static const GUID GUID_DEVINTERFACE_HID = {
|
|
0x4d1e55b2, 0xf16f, 0x11cf,
|
|
{ 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }
|
|
};
|
|
#define ENUM_GUID GUID_DEVINTERFACE_HID
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Capability listing (works even when the raw report descriptor IOCTL fails)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static const char* usage_page_name(USAGE page) {
|
|
switch (page) {
|
|
case 0x01: return "Generic Desktop";
|
|
case 0x02: return "Simulation Controls";
|
|
case 0x09: return "Button";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
static void print_value_caps(PHIDP_PREPARSED_DATA preparsed,
|
|
const HIDP_CAPS* caps) {
|
|
USHORT count = caps->NumberInputValueCaps;
|
|
if (count == 0)
|
|
return;
|
|
|
|
PHIDP_VALUE_CAPS vcaps =
|
|
HeapAlloc(GetProcessHeap(), 0, count * sizeof(HIDP_VALUE_CAPS));
|
|
if (!vcaps)
|
|
return;
|
|
|
|
if (HidP_GetValueCaps(HidP_Input, vcaps, &count, preparsed) == HIDP_STATUS_SUCCESS) {
|
|
printf("\nInput value caps (%u):\n", count);
|
|
for (USHORT i = 0; i < count; i++) {
|
|
const HIDP_VALUE_CAPS* v = &vcaps[i];
|
|
if (v->IsRange) {
|
|
printf(" [%u] UsagePage=0x%02X (%s) Usage=0x%02X..0x%02X "
|
|
"ReportID=%u BitSize=%u ReportCount=%u "
|
|
"LogicalMin=%ld LogicalMax=%ld\n",
|
|
i, v->UsagePage, usage_page_name(v->UsagePage),
|
|
v->Range.UsageMin, v->Range.UsageMax,
|
|
v->ReportID, v->BitSize, v->ReportCount,
|
|
(long)v->LogicalMin, (long)v->LogicalMax);
|
|
} else {
|
|
printf(" [%u] UsagePage=0x%02X (%s) Usage=0x%02X "
|
|
"ReportID=%u BitSize=%u ReportCount=%u "
|
|
"LogicalMin=%ld LogicalMax=%ld\n",
|
|
i, v->UsagePage, usage_page_name(v->UsagePage),
|
|
v->NotRange.Usage,
|
|
v->ReportID, v->BitSize, v->ReportCount,
|
|
(long)v->LogicalMin, (long)v->LogicalMax);
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(stderr, "HidP_GetValueCaps failed\n");
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, vcaps);
|
|
}
|
|
|
|
static void print_button_caps(PHIDP_PREPARSED_DATA preparsed,
|
|
const HIDP_CAPS* caps) {
|
|
USHORT count = caps->NumberInputButtonCaps;
|
|
if (count == 0)
|
|
return;
|
|
|
|
PHIDP_BUTTON_CAPS bcaps =
|
|
HeapAlloc(GetProcessHeap(), 0, count * sizeof(HIDP_BUTTON_CAPS));
|
|
if (!bcaps)
|
|
return;
|
|
|
|
if (HidP_GetButtonCaps(HidP_Input, bcaps, &count, preparsed) == HIDP_STATUS_SUCCESS) {
|
|
printf("\nInput button caps (%u):\n", count);
|
|
for (USHORT i = 0; i < count; i++) {
|
|
const HIDP_BUTTON_CAPS* b = &bcaps[i];
|
|
if (b->IsRange) {
|
|
printf(" [%u] UsagePage=0x%02X (%s) Usage=0x%02X..0x%02X "
|
|
"ReportID=%u\n",
|
|
i, b->UsagePage, usage_page_name(b->UsagePage),
|
|
b->Range.UsageMin, b->Range.UsageMax, b->ReportID);
|
|
} else {
|
|
printf(" [%u] UsagePage=0x%02X (%s) Usage=0x%02X ReportID=%u\n",
|
|
i, b->UsagePage, usage_page_name(b->UsagePage),
|
|
b->NotRange.Usage, b->ReportID);
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(stderr, "HidP_GetButtonCaps failed\n");
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, bcaps);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Device probe
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int device_num = 0;
|
|
|
|
static int probe_device(HANDLE handle, const wchar_t* path,
|
|
uint16_t filter_vid, uint16_t filter_pid) {
|
|
HIDD_ATTRIBUTES attrs = { .Size = sizeof(attrs) };
|
|
if (!HidD_GetAttributes(handle, &attrs))
|
|
return 0;
|
|
|
|
if (filter_vid && filter_pid) {
|
|
if (attrs.VendorID != filter_vid || attrs.ProductID != filter_pid) {
|
|
fprintf(stderr, "Skipping VID:PID 0x%04X:0x%04X\n", attrs.VendorID, attrs.ProductID);
|
|
return 0; // not our target
|
|
}
|
|
}
|
|
|
|
// Get HID caps via preparsed data
|
|
HIDP_CAPS caps = { 0 };
|
|
PHIDP_PREPARSED_DATA preparsed = NULL;
|
|
BOOL has_caps = FALSE;
|
|
if (HidD_GetPreparsedData(handle, &preparsed)) {
|
|
if (HidP_GetCaps(preparsed, &caps) == HIDP_STATUS_SUCCESS)
|
|
has_caps = TRUE;
|
|
}
|
|
|
|
device_num++;
|
|
printf("\n=== Device %d ===\n", device_num);
|
|
printf("Path: %ls\n", path);
|
|
printf("VID:PID: 0x%04X:0x%04X Version: 0x%04X\n",
|
|
attrs.VendorID, attrs.ProductID, attrs.VersionNumber);
|
|
|
|
if (has_caps) {
|
|
printf("Input report length: %u\n", caps.InputReportByteLength);
|
|
printf("Output report length: %u\n", caps.OutputReportByteLength);
|
|
printf("Feature report length: %u\n", caps.FeatureReportByteLength);
|
|
printf("Input buttons: %u\n", caps.NumberInputButtonCaps);
|
|
printf("Input values: %u\n", caps.NumberInputValueCaps);
|
|
|
|
print_value_caps(preparsed, &caps);
|
|
print_button_caps(preparsed, &caps);
|
|
}
|
|
|
|
if (preparsed)
|
|
HidD_FreePreparsedData(preparsed);
|
|
|
|
return 1;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Enumeration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static int enumerate_hid_devices(uint16_t filter_vid, uint16_t filter_pid) {
|
|
HDEVINFO info = SetupDiGetClassDevsW(&ENUM_GUID, NULL, NULL,
|
|
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
if (info == INVALID_HANDLE_VALUE) {
|
|
fprintf(stderr, "SetupDiGetClassDevs failed: %lu\n", GetLastError());
|
|
return -1;
|
|
}
|
|
|
|
int found = 0;
|
|
for (DWORD i = 0; ; i++) {
|
|
SP_DEVICE_INTERFACE_DATA iface = { .cbSize = sizeof(iface) };
|
|
BOOL ok = SetupDiEnumDeviceInterfaces(info, NULL, &ENUM_GUID, i, &iface);
|
|
if (!ok) {
|
|
fprintf(stderr, "Enumerated %lu interfaces (last error: %lu)\n",
|
|
(unsigned long)i, GetLastError());
|
|
break;
|
|
}
|
|
|
|
DWORD buflen = 0;
|
|
SetupDiGetDeviceInterfaceDetailW(info, &iface, NULL, 0, &buflen, NULL);
|
|
if (buflen == 0) continue;
|
|
|
|
PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail =
|
|
(PSP_DEVICE_INTERFACE_DETAIL_DATA_W)HeapAlloc(GetProcessHeap(), 0, buflen);
|
|
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
|
|
|
if (!SetupDiGetDeviceInterfaceDetailW(info, &iface, detail, buflen,
|
|
&buflen, NULL)) {
|
|
HeapFree(GetProcessHeap(), 0, detail);
|
|
continue;
|
|
}
|
|
|
|
HANDLE handle = CreateFileW(detail->DevicePath,
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, 0,
|
|
NULL);
|
|
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
if (probe_device(handle, detail->DevicePath, filter_vid, filter_pid))
|
|
found++;
|
|
CloseHandle(handle);
|
|
if (filter_vid && filter_pid && found > 0) break; // found our target
|
|
} else {
|
|
if (filter_vid && filter_pid)
|
|
fprintf(stderr, "CreateFile failed for %ls: %lu\n",
|
|
detail->DevicePath, GetLastError());
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, detail);
|
|
}
|
|
|
|
SetupDiDestroyDeviceInfoList(info);
|
|
return found;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Main
|
|
// ---------------------------------------------------------------------------
|
|
|
|
int main(int argc, char* argv[]) {
|
|
uint16_t vid = 0, pid = 0;
|
|
if (argc >= 3) {
|
|
vid = (uint16_t)strtoul(argv[1], NULL, 16);
|
|
pid = (uint16_t)strtoul(argv[2], NULL, 16);
|
|
printf("Looking for VID:PID 0x%04X:0x%04X\n", vid, pid);
|
|
} else {
|
|
printf("Listing all HID devices (pass VID PID to filter)\n");
|
|
}
|
|
return enumerate_hid_devices(vid, pid) == 0 ? 1 : 0;
|
|
}
|