Files
2026-06-14 19:18:06 +02:00

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