How to differentiate between devices in the windows HID API?

504 views Asked by At

I need to detect when a gamepad is plugged in for my game (I'm not using a higher-level input API for reasons), how can I do this? I've already enumerated all HID devices and opened files on them (except keyboard, mouse ofc) and can get all the info on the device, but what info do I want? What value(s) will tell me right away that this is an xbox controller, for example, and where are these values?

My code for enumerating over the devices (very messy atm :p):

    DWORD required_size = 0, determined_size;
    SP_DEVICE_INTERFACE_DATA device_interface_data;
    device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    PSP_DEVICE_INTERFACE_DETAIL_DATA device_interface_detail_data;
    HANDLE current_device;
    PHIDP_PREPARSED_DATA preparsed_data;
    WCHAR product_string[128];

    for (int i = 0; ; ++i) /* Enumerate HID devices */
    {
        rv = SetupDiEnumDeviceInterfaces(device_enumeration, NULL, &interface_guid, i, &device_interface_data); /* Does the actual enumeration,
                                                                                                                each time we increase index i
                                                                                                                to get the next device */      
        error = GetLastError();
        if (error == ERROR_NO_MORE_ITEMS) /* If there are no more devices, break */
            break;
        else if (!rv && error != ERROR_NO_MORE_ITEMS) /* Otherwise, we have a legit error */
        {
            cr_print_error(GetLastError());
            return EXIT_FAILURE;
        }

        SetupDiGetDeviceInterfaceDetail(device_enumeration, &device_interface_data, NULL, 0, &required_size, NULL); /* Probing call only to get buffer size,
                                                                                                                    so error code (122) ignored */
        /* Allocate new device detail struct using buffer size we obtained */
        determined_size = required_size;
        device_interface_detail_data = cr_safe_malloc(required_size);
        device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

        /* Get detailed info about device */
        rv = SetupDiGetDeviceInterfaceDetail(device_enumeration, &device_interface_data, device_interface_detail_data, determined_size, &required_size, NULL);

        if (!rv)
        {
            cr_print_error(GetLastError());
            return EXIT_FAILURE;
        }

        cr_printf("FOUNDD DEVICE: %s\n\n", device_interface_detail_data->DevicePath);
        
        current_device = cr_open_device(device_interface_detail_data->DevicePath);

        if (!current_device && GetLastError() == ERROR_ACCESS_DENIED)
            goto Done;

        cr_printf("OPENED DEVICE: %s\n\n", device_interface_detail_data->DevicePath);

        preparsed_data = HidD_GetPreparsedData(current_device, &preparsed_data);

        if (!preparsed_data)
        {
            cr_print_error(GetLastError());
            return EXIT_FAILURE;
        }

        HidD_GetProductString(current_device, product_string, sizeof(product_string));

        cr_printf("PRODUCT STRING: %S\n\n", product_string);

        HidD_FreePreparsedData(&preparsed_data);
        DeleteFile(device_interface_detail_data->DevicePath);
    Done:
        cr_safe_free(&device_interface_detail_data);
    }

    SetupDiDestroyDeviceInfoList(device_enumeration);

As you can see I've found the product string, which tells me what the device actually is, but maybe comparing product strings is not the best way to do PnP?

1

There are 1 answers

0
DJm00n On

Check if the device ID contains "IG_". If it does, then it's an XInput device. More info on that.

Alternatively you can check for XUSB device interface:

bool RawInputDeviceHid::QueryXInputDeviceInterface()
{
    DCHECK(IsValidHandle(m_InterfaceHandle.get()));

    // https://docs.microsoft.com/windows/win32/xinput/xinput-and-directinput
    stringutils::ci_string tmp(m_InterfacePath.c_str(), m_InterfacePath.size());
    if (tmp.find("IG_") == stringutils::ci_string::npos)
        return false;

    // Xbox 360 XUSB Interface
    // {EC87F1E3-C13B-4100-B5F7-8B84D54260CB}
    static constexpr GUID XUSB_INTERFACE_CLASS_GUID = { 0xEC87F1E3, 0xC13B, 0x4100, { 0xB5, 0xF7, 0x8B, 0x84, 0xD5, 0x42, 0x60, 0xCB } };

    m_XInputInterfacePath = SearchParentDeviceInterface(m_DeviceInstanceId, &XUSB_INTERFACE_CLASS_GUID);

    return !m_XInputInterfacePath.empty();
}

See https://github.com/DJm00n/RawInputDemo/blob/master/RawInputLib/CfgMgr32Wrapper.h for a CfgMgr32 wrapper code .