// 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 #include #include #include #include #include // 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; }