You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
11 KiB
288 lines
11 KiB
|
|
|
|
|
|
#include "qextserialenumerator.h" |
|
#include <QDebug> |
|
#include <QMetaType> |
|
|
|
#include <IOKit/serial/IOSerialKeys.h> |
|
#include <CoreFoundation/CFNumber.h> |
|
#include <sys/param.h> |
|
|
|
QextSerialEnumerator::QextSerialEnumerator( ) |
|
{ |
|
if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) |
|
qRegisterMetaType<QextPortInfo>("QextPortInfo"); |
|
} |
|
|
|
QextSerialEnumerator::~QextSerialEnumerator( ) |
|
{ |
|
IONotificationPortDestroy( notificationPortRef ); |
|
} |
|
|
|
// static |
|
QList<QextPortInfo> QextSerialEnumerator::getPorts() |
|
{ |
|
QList<QextPortInfo> infoList; |
|
io_iterator_t serialPortIterator = 0; |
|
kern_return_t kernResult = KERN_FAILURE; |
|
CFMutableDictionaryRef matchingDictionary; |
|
|
|
// first try to get any serialbsd devices, then try any USBCDC devices |
|
if( !(matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue) ) ) { |
|
qWarning("IOServiceMatching returned a NULL dictionary."); |
|
return infoList; |
|
} |
|
CFDictionaryAddValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); |
|
|
|
// then create the iterator with all the matching devices |
|
if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { |
|
qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; |
|
return infoList; |
|
} |
|
iterateServicesOSX(serialPortIterator, infoList); |
|
IOObjectRelease(serialPortIterator); |
|
serialPortIterator = 0; |
|
|
|
if( !(matchingDictionary = IOServiceNameMatching("AppleUSBCDC")) ) { |
|
qWarning("IOServiceNameMatching returned a NULL dictionary."); |
|
return infoList; |
|
} |
|
|
|
if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { |
|
qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; |
|
return infoList; |
|
} |
|
iterateServicesOSX(serialPortIterator, infoList); |
|
IOObjectRelease(serialPortIterator); |
|
|
|
return infoList; |
|
} |
|
|
|
void QextSerialEnumerator::iterateServicesOSX(io_object_t service, QList<QextPortInfo> & infoList) |
|
{ |
|
// Iterate through all modems found. |
|
io_object_t usbService; |
|
while( ( usbService = IOIteratorNext(service) ) ) |
|
{ |
|
QextPortInfo info; |
|
info.vendorID = 0; |
|
info.productID = 0; |
|
getServiceDetailsOSX( usbService, &info ); |
|
infoList.append(info); |
|
} |
|
} |
|
|
|
bool QextSerialEnumerator::getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ) |
|
{ |
|
bool retval = true; |
|
CFTypeRef bsdPathAsCFString = NULL; |
|
CFTypeRef productNameAsCFString = NULL; |
|
CFTypeRef vendorIdAsCFNumber = NULL; |
|
CFTypeRef productIdAsCFNumber = NULL; |
|
// check the name of the modem's callout device |
|
bsdPathAsCFString = IORegistryEntryCreateCFProperty(service, CFSTR(kIOCalloutDeviceKey), |
|
kCFAllocatorDefault, 0); |
|
|
|
// wander up the hierarchy until we find the level that can give us the |
|
// vendor/product IDs and the product name, if available |
|
io_registry_entry_t parent; |
|
kern_return_t kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); |
|
while( kernResult == KERN_SUCCESS && !vendorIdAsCFNumber && !productIdAsCFNumber ) |
|
{ |
|
if(!productNameAsCFString) |
|
productNameAsCFString = IORegistryEntrySearchCFProperty(parent, |
|
kIOServicePlane, |
|
CFSTR("Product Name"), |
|
kCFAllocatorDefault, 0); |
|
vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, |
|
kIOServicePlane, |
|
CFSTR(kUSBVendorID), |
|
kCFAllocatorDefault, 0); |
|
productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, |
|
kIOServicePlane, |
|
CFSTR(kUSBProductID), |
|
kCFAllocatorDefault, 0); |
|
io_registry_entry_t oldparent = parent; |
|
kernResult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent); |
|
IOObjectRelease(oldparent); |
|
} |
|
|
|
io_string_t ioPathName; |
|
IORegistryEntryGetPath( service, kIOServicePlane, ioPathName ); |
|
portInfo->physName = ioPathName; |
|
|
|
if( bsdPathAsCFString ) |
|
{ |
|
char path[MAXPATHLEN]; |
|
if( CFStringGetCString((CFStringRef)bsdPathAsCFString, path, |
|
PATH_MAX, kCFStringEncodingUTF8) ) |
|
portInfo->portName = path; |
|
CFRelease(bsdPathAsCFString); |
|
} |
|
|
|
if(productNameAsCFString) |
|
{ |
|
char productName[MAXPATHLEN]; |
|
if( CFStringGetCString((CFStringRef)productNameAsCFString, productName, |
|
PATH_MAX, kCFStringEncodingUTF8) ) |
|
portInfo->friendName = productName; |
|
CFRelease(productNameAsCFString); |
|
} |
|
|
|
if(vendorIdAsCFNumber) |
|
{ |
|
SInt32 vID; |
|
if(CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberSInt32Type, &vID)) |
|
portInfo->vendorID = vID; |
|
CFRelease(vendorIdAsCFNumber); |
|
} |
|
|
|
if(productIdAsCFNumber) |
|
{ |
|
SInt32 pID; |
|
if(CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberSInt32Type, &pID)) |
|
portInfo->productID = pID; |
|
CFRelease(productIdAsCFNumber); |
|
} |
|
IOObjectRelease(service); |
|
return retval; |
|
} |
|
|
|
// IOKit callbacks registered via setupNotifications() |
|
void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); |
|
void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); |
|
|
|
void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) |
|
{ |
|
QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; |
|
io_object_t serialService; |
|
while ((serialService = IOIteratorNext(serialPortIterator))) |
|
qese->onDeviceDiscoveredOSX(serialService); |
|
} |
|
|
|
void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) |
|
{ |
|
QextSerialEnumerator* qese = (QextSerialEnumerator*)ctxt; |
|
io_object_t serialService; |
|
while ((serialService = IOIteratorNext(serialPortIterator))) |
|
qese->onDeviceTerminatedOSX(serialService); |
|
} |
|
|
|
/* |
|
A device has been discovered via IOKit. |
|
Create a QextPortInfo if possible, and emit the signal indicating that we've found it. |
|
*/ |
|
void QextSerialEnumerator::onDeviceDiscoveredOSX( io_object_t service ) |
|
{ |
|
QextPortInfo info; |
|
info.vendorID = 0; |
|
info.productID = 0; |
|
if( getServiceDetailsOSX( service, &info ) ) |
|
emit deviceDiscovered( info ); |
|
} |
|
|
|
/* |
|
Notification via IOKit that a device has been removed. |
|
Create a QextPortInfo if possible, and emit the signal indicating that it's gone. |
|
*/ |
|
void QextSerialEnumerator::onDeviceTerminatedOSX( io_object_t service ) |
|
{ |
|
QextPortInfo info; |
|
info.vendorID = 0; |
|
info.productID = 0; |
|
if( getServiceDetailsOSX( service, &info ) ) |
|
emit deviceRemoved( info ); |
|
} |
|
|
|
/* |
|
Create matching dictionaries for the devices we want to get notifications for, |
|
and add them to the current run loop. Invoke the callbacks that will be responding |
|
to these notifications once to arm them, and discover any devices that |
|
are currently connected at the time notifications are setup. |
|
*/ |
|
void QextSerialEnumerator::setUpNotifications( ) |
|
{ |
|
kern_return_t kernResult; |
|
mach_port_t masterPort; |
|
CFRunLoopSourceRef notificationRunLoopSource; |
|
CFMutableDictionaryRef classesToMatch; |
|
CFMutableDictionaryRef cdcClassesToMatch; |
|
io_iterator_t portIterator; |
|
|
|
kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); |
|
if (KERN_SUCCESS != kernResult) { |
|
qDebug() << "IOMasterPort returned:" << kernResult; |
|
return; |
|
} |
|
|
|
classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); |
|
if (classesToMatch == NULL) |
|
qDebug("IOServiceMatching returned a NULL dictionary."); |
|
else |
|
CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); |
|
|
|
if( !(cdcClassesToMatch = IOServiceNameMatching("AppleUSBCDC") ) ) { |
|
qWarning("couldn't create cdc matching dict"); |
|
return; |
|
} |
|
|
|
// Retain an additional reference since each call to IOServiceAddMatchingNotification consumes one. |
|
classesToMatch = (CFMutableDictionaryRef) CFRetain(classesToMatch); |
|
cdcClassesToMatch = (CFMutableDictionaryRef) CFRetain(cdcClassesToMatch); |
|
|
|
notificationPortRef = IONotificationPortCreate(masterPort); |
|
if(notificationPortRef == NULL) { |
|
qDebug("IONotificationPortCreate return a NULL IONotificationPortRef."); |
|
return; |
|
} |
|
|
|
notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationPortRef); |
|
if (notificationRunLoopSource == NULL) { |
|
qDebug("IONotificationPortGetRunLoopSource returned NULL CFRunLoopSourceRef."); |
|
return; |
|
} |
|
|
|
CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); |
|
|
|
kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, classesToMatch, |
|
deviceDiscoveredCallbackOSX, this, &portIterator); |
|
if (kernResult != KERN_SUCCESS) { |
|
qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; |
|
return; |
|
} |
|
|
|
// arm the callback, and grab any devices that are already connected |
|
deviceDiscoveredCallbackOSX( this, portIterator ); |
|
|
|
kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, cdcClassesToMatch, |
|
deviceDiscoveredCallbackOSX, this, &portIterator); |
|
if (kernResult != KERN_SUCCESS) { |
|
qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; |
|
return; |
|
} |
|
|
|
// arm the callback, and grab any devices that are already connected |
|
deviceDiscoveredCallbackOSX( this, portIterator ); |
|
|
|
kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, classesToMatch, |
|
deviceTerminatedCallbackOSX, this, &portIterator); |
|
if (kernResult != KERN_SUCCESS) { |
|
qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; |
|
return; |
|
} |
|
|
|
// arm the callback, and clear any devices that are terminated |
|
deviceTerminatedCallbackOSX( this, portIterator ); |
|
|
|
kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, cdcClassesToMatch, |
|
deviceTerminatedCallbackOSX, this, &portIterator); |
|
if (kernResult != KERN_SUCCESS) { |
|
qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; |
|
return; |
|
} |
|
|
|
// arm the callback, and clear any devices that are terminated |
|
deviceTerminatedCallbackOSX( this, portIterator ); |
|
} |
|
|
|
|