Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API function to get macOS LocationID and Windows ContainerID of an USB device #107

Open
JoergAtGithub opened this issue Apr 4, 2024 · 8 comments

Comments

@JoergAtGithub
Copy link

For compound USB devices, that have not only a MIDI interface, but interfaces used with other USB device classes (e.g. a display) as well, it is needed to group them. The operating systems macOS and Windows provide identifiers for this purpose:

  • macOS: LocationID
  • Windows: ContainerID
    It would be nice, if the libremidi API would provide getters, to get these IDs for an USB device.
@jcelerier
Copy link
Member

The operating systems macOS and Windows provide identifiers for this purpose:

Do you have some sample code of how to do this ?

@JoergAtGithub
Copy link
Author

JoergAtGithub commented Apr 5, 2024

@jcelerier
Copy link
Member

Thanks. Now I have to find how I get these device nodes in MS Windows, as right now the code does not use these APIs AFAIK.
For macOS it looks pretty doable.

@jcelerier
Copy link
Member

jcelerier commented Aug 25, 2024

I'm revisiting this and am wondering: port_information already stores the MIDIObjectRef's UUID so you can get it back in the following way (typing this without access to macOS so likely there are some mistakes but you get the gist):

int32_t usb_id_from_port(const libremidi::port_information& info) {
  // Get the MIDI object from the uid
  auto uid = std::bit_cast<std::int32_t>((uint32_t)info.port);
  MIDIObjectRef object{};
  MIDIObjectType type{};
  auto ret = MIDIObjectFindByUniqueID(uid, &object, &type);
  assert(type == kMIDIObjectType_Source || type == kMIDIObjectType_Destination);

  // Get the MIDI entity from the object
  MIDIEntityRef entity{};
  MIDIEndpointGetEntity(object, &entity); 
  if(!entity) 
    throw; // It means it's not a hardware port

  // Get the MIDI device from the entity
  MIDIDeviceRef device{};
  MIDIEntityGetDevice(entity, &device);
  if(!device)
    throw;

  SInt32 res{};
  MIDIObjectGetIntegerProperty(device, CFSTR("USBDeviceID"), &res);
  return res;
}

For Windows I investigated but I see absolutely no way to get the device ID from the WinMM API. :/

@JoergAtGithub
Copy link
Author

For Windows I investigated but I see absolutely no way to get the device ID from the WinMM API. :/

What about this approach:

#include <windows.h>
#include <setupapi.h>
#include <devpkey.h>
#include <iostream>
#include <string>
#include <initguid.h>

#pragma comment(lib, "setupapi.lib")

void CheckMidiDevices() {
    // Get the device information set for all MIDI devices
    HDEVINFO deviceInfoSet = SetupDiGetClassDevs(NULL, "SWD\\MMDEVAPI\\{0.0.1.00000000}.{00000000-0000-0000-0000-000000000000}", NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES);
    if (deviceInfoSet == INVALID_HANDLE_VALUE) {
        std::cerr << "Failed to get device information set." << std::endl;
        return;
    }

    SP_DEVINFO_DATA deviceInfoData;
    deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);

    // Enumerate through all devices in the set
    for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); ++i) {
        // Get the device instance ID
        char deviceInstanceId[MAX_DEVICE_ID_LEN];
        if (CM_Get_Device_ID(deviceInfoData.DevInst, deviceInstanceId, MAX_DEVICE_ID_LEN, 0) != CR_SUCCESS) {
            continue;
        }

        // Get the device description
        char deviceDescription[256];
        if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_DEVICEDESC, NULL, (PBYTE)deviceDescription, sizeof(deviceDescription), NULL)) {
            std::cout << "Device: " << deviceDescription << std::endl;
        }

        // Get the ContainerID
        DEVPROPTYPE propType;
        GUID containerId;
        if (SetupDiGetDeviceProperty(deviceInfoSet, &deviceInfoData, &DEVPKEY_Device_ContainerId, &propType, (PBYTE)&containerId, sizeof(containerId), NULL, 0)) {
            LPOLESTR guidString;
            StringFromCLSID(containerId, &guidString);
            std::wcout << L"ContainerID: " << guidString << std::endl;
            CoTaskMemFree(guidString);
        }

        // Get the device's parent to determine the connection type
        DEVINST parentDevInst;
        if (CM_Get_Parent(&parentDevInst, deviceInfoData.DevInst, 0) == CR_SUCCESS) {
            char parentDeviceInstanceId[MAX_DEVICE_ID_LEN];
            if (CM_Get_Device_ID(parentDevInst, parentDeviceInstanceId, MAX_DEVICE_ID_LEN, 0) == CR_SUCCESS) {
                // Check if the parent device is a USB or Bluetooth device
                if (strstr(parentDeviceInstanceId, "USB") != NULL) {
                    std::cout << "Connection Type: USB" << std::endl;
                } else if (strstr(parentDeviceInstanceId, "BTH") != NULL) {
                    std::cout << "Connection Type: Bluetooth" << std::endl;
                } else {
                    std::cout << "Connection Type: Other" << std::endl;
                }
            }
        }
    }

    // Clean up
    SetupDiDestroyDeviceInfoList(deviceInfoSet);
}

int main() {
    CheckMidiDevices();
    return 0;
}

@jcelerier
Copy link
Member

hm but do we know for sure that the index of

for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); ++i) 

is the same index than the one used by WinMM to enumerate MIDI devices ?

@jcelerier
Copy link
Member

especially in "borderline" cases where we plug multiple identical MIDI devices which have historically been iffy in winmm

@JoergAtGithub
Copy link
Author

I don't know unfortunately.
But in general the purpose of the ContainerId is to handle these "iffy" devices, multi functional(e.g. MIDI and screens) but without proper serial number or with multiple physical interfaces.
The most common use case for the ContainerId is a multifunctional printer/scanner connected via USB and WLAN at the same time. The ContainerId groups everything and ensures, that it appears as one device.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants