celtera / libremidi

A modern C++ MIDI 1 / MIDI 2 real-time & file I/O library. Supports Windows, macOS, Linux and WebMIDI.
Other
456 stars 51 forks source link

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

Open JoergAtGithub opened 7 months ago

JoergAtGithub commented 7 months ago

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:

jcelerier commented 7 months ago

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

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

JoergAtGithub commented 7 months ago

For macOS you need to get the USBLocationID property from MIDIDeviceRef, similar to the code example here: https://stackoverflow.com/questions/43071011/is-there-a-way-to-tell-if-a-midi-device-is-connected-via-usb-on-ios/50637087#50637087

For Windows you need to get the DEVPKEY_Device_ContainerId property returned by CM_Get_DevNode_PropertyW: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/devpkey-device-containerid#:~:text=The%20DEVPKEY_Device_ContainerId%20device%20property%20is%20used%20by%20the,that%20represents%20an%20instance%20of%20a%20physical%20device

jcelerier commented 7 months ago

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 commented 2 months ago

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. :/