Advertisement

How did MS enumerate the product name for DeviceManager's container?

Started by November 04, 2020 09:23 PM
7 comments, last by frob 4 years ago

TLDR: How and where did Microsoft get the information for device containers circled in green?

I've been struggling for the past week trying to figure out how to enumerate HID devices for raw input. I was greeted with multiple mouse and keyboard devices despite I only have one of each; plus a drawing monitor. I spent the first few days sifting through Microsoft's documentation and the last couple prodding and poking around using the API. I can't seem to get the real manufacturer or product name other than the device provided “HID-Compliant" Mouse or Keyboard. I found the GUID for my keyboard's container which contains all the devices that raw input picked up. I searched up and down the Window Registry using GUIDs and VID and can't find any hint of keyboard product information. How and where did Microsoft get the information for device containers circled in green?

in good ol' Win DDK documentation, here somewhere:

https://docs.microsoft.com/en-us/windows-hardware/drivers/

All the best ?

Advertisement

I suppose I can continue the epic odyssey of swimming through MS DDK, but I was hoping someone would already have the insight of where to get a device's friendly collection name.

Scattered throughout the documentation you would find statements like;
“As a general rule, user-mode applications can use the HidD_Xxx and HidP_Xxx routines”
I'm already been using these types functions, along with many others found in the include files in my project:

#include <initguid.h>
#include <Usbiodef.h>
#include <devguid.h>

// #include <Devpropdef.h> 
// NOTE: included in Devpkey.h
#include <Devpropdef.h>
#include <Devpkey.h>

// To get CM_Get_Device_Interface_PropertyW function
// Also link Cfgmgr32.lib
#include <cfgmgr32.h>
#include <hidusage.h>
#include <hidpi.h>

// Hid.lib
#include <hidsdi.h>

// SetupDiDestroyDeviceInfoList
// Setupapi.lib
#include <setupapi.h>

There got to be a way to get the kind of user friendly string like I circled in green in the OP; without having to download an entire DDK from MS.

The magic of HID is simple once you understood it.

UINT expectedNumberOfHIDs = 0;
if(0 != GetRawInputDeviceList(nullptr, &expectedNumberOfHIDs, sizeof(RAWINPUTDEVICELIST))) 
{
    return; //no devices found
}
std::vector<RAWINPUTDEVICELIST> hidDescriptors(expectedNumberOfHIDs);
if(expectedNumberOfHIDs != GetRawInputDeviceList(&*hidDescriptors.begin(), &expectedNumberOfHIDs, sizeof(RAWINPUTDEVICELIST))) 
{
    return; // Error
}

Devices are enumerated from the OS Driver Level into a struct which gives little to no information at all, except which kind of input type and data pages it has. This is because HID e.g. the USB (Universal Serial Bus) standard is made for any kind of device, so you have first to list the generic instances you are interested in and second “cast” them into the objects that “inherit” those generic properties.

for(auto const & hidDescriptor : hidDescriptors)
{
    if(RIM_TYPEHID == hidDescriptor.dwType) 
        theHIDs.push_back(hidDescriptor.hDevice);
}

Like in Unix (which Windows took a deep look at even if they don't say it) every device can be accessed like a file and this is where we get the properties from. So you have to first get the “file path” from the Driver API to finally open a handle and then you are able to obtain all the info you want from device name up to vendor IDs.

wchar_t pathBuffer[260 + 4];
auto pathsLength = UINT(sizeof pathBuffer / sizeof pathBuffer[0]);
pathsLength = GetRawInputDeviceInfoW(itsRawInputHandle, RIDI_DEVICENAME, pathBuffer, &pathsLength);
if(sizeof pathBuffer / sizeof pathBuffer[0] < pathsLength)
{
    //invalid HID: could not retrieve its path
    return;
}
pathBuffer[1] = L'\\';
ntHandle = CreateFileW(pathBuffer, 0u, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0u, nullptr);
if(INVALID_HANDLE_VALUE == ntHandle) 
{
    //invalid HID: could not open its handle
    return;
}

wchar_t nameBuffer[255];
if(TRUE == HidD_GetManufacturerString(ntHandle, nameBuffer, 127)) 
{
    auto manufacturerLength = wcslen(nameBuffer);
    nameBuffer[manufacturerLength++] = ' ';
    HidD_GetProductString(ntHandle, nameBuffer + manufacturerLength, ULONG(255 - manufacturerLength));
}

//don't forget to close the handle if you're done
CloseHandle(ntHandle);

You should now be able to see something like

G502 LIGHTSPEED WIRELESS GAMING MOUSE

I found a neat and well explained tutorial some time ago but unfortunately it is in German. However, it explains in very detail how you “could” handle those things without any pain. As I don't have my source on hand at the moment, I took the source provided here

Ah, so it looks like I already on the right track but wasn't going far enough.
I have used GetRawInputDeviceInfo to get the device path, but I never actually opened the device with CreateFile.

Therefor, I was using CM_Get_Device_Interface_Property to get the device's instance and was extracting the info using CM_Get_DevNode_Property which gives me those generic names.

I've been overlooking those HidD_Getxxxx functions because I assumed it will give me the same info I was getting with CM_Get_DevNode_Property. I'll give HidD_GetProductString a try today and report back how it went.

Sorry it took so long to respond back with my results; my framework needed a massive refactoring.

So yes, opening the device and using the CM_xxx functions did in fact help get better device names and descriptions.
The most useful property was:

DEVPKEY_Device_ContainerId

because it allowed me to logically group all those composite interfaces to one device.

I'm satisfied with what I have at the moment; however, I'm still confused since MS device manager gets a different description for some devices. (highlighted in yellow):

Advertisement

I forgot:
Thank you for your wisdom ?

There are quite a few strings available. Perhaps you want HidD_GetPhysicalDescriptor()?

This topic is closed to new replies.

Advertisement