/*============================================================================= CAMIDIEndpoints.cpp $Log: CAMIDIEndpoints.cpp,v $ Revision 1.3 2003/12/04 19:56:14 dwyatt fixes: - in EndpointInfo copy constructor, null out mDisplayName - don't prepend the device name to the entity name if the entity name already starts with the device name (Edirol UM-4) Revision 1.2 2003/10/04 00:17:04 dwyatt minor tweak to endpoint naming Revision 1.1 2003/09/24 21:31:11 dwyatt initial checkin created Thu Sep 18 2003, Doug Wyatt Copyright (c) 2003 Apple Computer, Inc. All Rights Reserved $NoKeywords: $ =============================================================================*/ #include "CAMIDIEndpoints.h" #include // ____________________________________________________________________________ // Obtain the name of an endpoint without regard for whether it has connections. // The result should be released by the caller. static CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal) { CFMutableStringRef result = CFStringCreateMutable(NULL, 0); CFStringRef str; // begin with the endpoint's name str = NULL; MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str); if (str != NULL) { CFStringAppend(result, str); CFRelease(str); } MIDIEntityRef entity = NULL; MIDIEndpointGetEntity(endpoint, &entity); if (entity == NULL) // probably virtual return result; if (CFStringGetLength(result) == 0) { // endpoint name has zero length -- try the entity str = NULL; MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str); if (str != NULL) { CFStringAppend(result, str); CFRelease(str); } } // now consider the device's name MIDIDeviceRef device = NULL; MIDIEntityGetDevice(entity, &device); if (device == NULL) return result; str = NULL; MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str); if (str != NULL) { // if an external device has only one entity, throw away the endpoint name and just use the device name if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) { CFRelease(result); return str; } else { // does the entity name already start with the device name? (some drivers do this though they shouldn't) // if so, do not prepend if (CFStringCompareWithOptions(str /* device name */, result /* endpoint name */, CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) { // prepend the device name to the entity name if (CFStringGetLength(result) > 0) CFStringInsert(result, 0, CFSTR(" ")); CFStringInsert(result, 0, str); } CFRelease(str); } } return result; } #if 0 // Obtain the name of an endpoint, following connections. // The result should be released by the caller. static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint) { CFMutableStringRef result = CFStringCreateMutable(NULL, 0); CFStringRef str; OSStatus err; // Does the endpoint have connections? CFDataRef connections = NULL; int nConnected = 0; bool anyStrings = false; err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); if (connections != NULL) { // It has connections, follow them // Concatenate the names of all connected devices nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); if (nConnected) { const SInt32 *pid = reinterpret_cast(CFDataGetBytePtr(connections)); for (int i = 0; i < nConnected; ++i, ++pid) { MIDIUniqueID id = EndianS32_BtoN(*pid); MIDIObjectRef connObject; MIDIObjectType connObjectType; err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); if (err == noErr) { if (connObjectType == kMIDIObjectType_ExternalSource || connObjectType == kMIDIObjectType_ExternalDestination) { // Connected to an external device's endpoint (10.3 and later). str = EndpointName(static_cast(connObject), true); } else { // Connected to an external device (10.2) (or something else, catch-all) str = NULL; MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); } if (str != NULL) { if (anyStrings) CFStringAppend(result, CFSTR(", ")); else anyStrings = true; CFStringAppend(result, str); CFRelease(str); } } } } CFRelease(connections); } if (anyStrings) return result; // Here, either the endpoint had no connections, or we failed to obtain names for any of them. return EndpointName(endpoint, false); } #endif // ____________________________________________________________________________ CAMIDIEndpoints::Endpoint::Endpoint(MIDIEndpointRef endpoint, CFStringRef name, MIDIObjectRef connectedObj) : mUniqueID(kMIDIInvalidUniqueID), mIOEndpoint(endpoint), mName(name), mEntity(NULL), mEmbeddedOrVirtual(false), mConnectedObj(connectedObj), mNext(NULL), mPairMate(NULL) { MIDIObjectGetIntegerProperty(connectedObj ? connectedObj : endpoint, kMIDIPropertyUniqueID, &mUniqueID); // Is the endpoint that of an embedded entity? or virtual? MIDIEndpointGetEntity(endpoint, &mEntity); if (mEntity == NULL) { mEmbeddedOrVirtual = true; // presumably virtual } else { SInt32 embedded = 0; MIDIObjectGetIntegerProperty(mEntity, kMIDIPropertyIsEmbeddedEntity, &embedded); if (embedded) { mEmbeddedOrVirtual = true; } } } CAMIDIEndpoints::Endpoint::~Endpoint() { if (mName) CFRelease(mName); } bool CAMIDIEndpoints::Endpoint::GetEndpointInfo(EMode mode, EndpointInfo &info) { Endpoint *ept = this, *ept2; if (mode == kPairs) { if ((ept2 = ept->PairMate()) == NULL) return false; info.mSourceEndpoint = ept2->IOEndpoint(); info.mDestinationEndpoint = ept->IOEndpoint(); } else if (mode == kSources) { info.mSourceEndpoint = ept->IOEndpoint(); info.mDestinationEndpoint = NULL; } else { info.mSourceEndpoint = NULL; info.mDestinationEndpoint = ept->IOEndpoint(); } info.mUniqueID = ept->UniqueID(); if (ept->DriverOwned() && ept->Next() != NULL) { // add one item for all connected items CFMutableStringRef names = CFStringCreateMutable(NULL, 0); bool first = true; while (true) { ept = ept->Next(); if (ept == NULL) break; if (!first) { CFStringAppend(names, CFSTR(", ")); } else first = false; CFStringAppend(names, ept->Name()); } info.mDisplayName = names; } else { // a driver-owned endpoint with nothing connected externally, // or an external endpoint CFRetain(info.mDisplayName = ept->Name()); } return true; } // ____________________________________________________________________________ CAMIDIEndpoints::CAMIDIEndpoints() { UpdateFromCurrentState(); } CAMIDIEndpoints::~CAMIDIEndpoints() { Clear(); } void CAMIDIEndpoints::Clear() { EndpointList::iterator epit; for (epit = mSources.begin(); epit != mSources.end(); ++epit) { delete *epit; } mSources.clear(); for (epit = mDestinations.begin(); epit != mDestinations.end(); ++epit) { delete *epit; } mDestinations.clear(); } void CAMIDIEndpoints::UpdateFromCurrentState() { Clear(); UInt32 i, n; MIDIEndpointRef epRef; n = MIDIGetNumberOfSources(); mSources.reserve(n); for (i = 0; i < n; ++i) { epRef = MIDIGetSource(i); if (epRef) AddEndpoints(epRef, mSources); } n = MIDIGetNumberOfDestinations(); mDestinations.reserve(n); for (i = 0; i < n; ++i) { epRef = MIDIGetDestination(i); if (epRef) AddEndpoints(epRef, mDestinations); } // pairing for (EndpointList::iterator dit = mDestinations.begin(); dit != mDestinations.end(); ++dit) { Endpoint *ep = *dit; MIDIEntityRef destEntity = ep->Entity(); if (destEntity != NULL) { for (EndpointList::iterator eit = mSources.begin(); eit != mSources.end(); ++eit) { Endpoint *ep2 = *eit; MIDIEntityRef srcEntity = ep2->Entity(); if (srcEntity == destEntity && ep2->DriverOwned() == ep->DriverOwned()) { ep2->SetPairMate(ep); ep->SetPairMate(ep2); } } } } } void CAMIDIEndpoints::AddEndpoints(MIDIEndpointRef endpoint, EndpointList &eplist) { Endpoint *ep, *prev; OSStatus err; CFStringRef str; // Add the driver-owned endpoint ep = new Endpoint(endpoint, EndpointName(endpoint, false), NULL); eplist.push_back(ep); prev = ep; // Does the endpoint have connections? CFDataRef connections = NULL; int nConnected = 0; MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections); if (connections != NULL) { // It has connections, follow them nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID); if (nConnected) { const SInt32 *pid = reinterpret_cast(CFDataGetBytePtr(connections)); for (int i = 0; i < nConnected; ++i, ++pid) { MIDIUniqueID id = EndianS32_BtoN(*pid); MIDIObjectRef connObject; MIDIObjectType connObjectType; err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType); if (err == noErr) { if (connObjectType == kMIDIObjectType_ExternalSource || connObjectType == kMIDIObjectType_ExternalDestination) { // Connected to an external device's endpoint (10.3 and later). str = EndpointName(static_cast(connObject), true); } else { // Connected to an external device (10.2) (or something else, catch-all) str = NULL; MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str); } if (str != NULL) { ep = new Endpoint(endpoint, str, connObject); eplist.push_back(ep); prev->SetNext(ep); prev = ep; } } } } } } class CompareEndpointsByName { public: bool operator () (CAMIDIEndpoints::EndpointInfo *a, CAMIDIEndpoints::EndpointInfo *b) { CFStringRef namea = a->mDisplayName; return CFStringCompareWithOptions(namea, b->mDisplayName, CFRangeMake(0, CFStringGetLength(namea)), 0) < 0; } }; CAMIDIEndpoints::EndpointInfoList * CAMIDIEndpoints::GetEndpoints(EMode mode, UInt32 opts) { EndpointList &srcList = (mode == kSources) ? mSources : mDestinations; EndpointInfoList *list = new EndpointInfoList; EndpointInfo info; for (EndpointList::iterator it = srcList.begin(); it != srcList.end(); ++it) { Endpoint *ept = *it; if (ept->DriverOwned()) { // driver-owned endpoint if (ept->Next() == NULL) { // nothing connected externally if ((opts & kOptIncludeUnconnectedExternalPorts) || ept->EmbeddedOrVirtual()) { if (ept->GetEndpointInfo(mode, info)) list->push_back(new EndpointInfo(info)); } } else if (opts & kOptCombineByPort) { // add one item for all connected items if (ept->GetEndpointInfo(mode, info)) list->push_back(new EndpointInfo(info)); } // else it has external connections, which we'll pick up separately } else { // external endpoint if (!(opts & kOptCombineByPort)) { if (ept->GetEndpointInfo(mode, info)) list->push_back(new EndpointInfo(info)); } } } if (opts & kOptSortByName) { std::sort(list->begin(), list->end(), CompareEndpointsByName() ); } return list; } bool CAMIDIEndpoints::FindEndpoint(EMode mode, EndpointInfo &info, UInt32 opts) { EndpointList &srcList = (mode == kSources) ? mSources : mDestinations; for (EndpointList::iterator it = srcList.begin(); it != srcList.end(); ++it) { Endpoint *ept = *it; if (ept->UniqueID() == info.mUniqueID) { if (ept->GetEndpointInfo(mode, info)) return true; break; } } info.mSourceEndpoint = NULL; info.mDestinationEndpoint = NULL; return false; } /* Resolving of persistent endpoint references Cases to handle: - Was referring to an external endpoint - missing - Was referring to a driver endpoint - missing - now has external endpoint(s) connected */