7 changed files with 792 additions and 6 deletions
@ -0,0 +1,207 @@ |
|||||||
|
/*!
|
||||||
|
* \file qextserialenumerator.h |
||||||
|
* \author Michal Policht |
||||||
|
* \see QextSerialEnumerator |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef _QEXTSERIALENUMERATOR_H_ |
||||||
|
#define _QEXTSERIALENUMERATOR_H_ |
||||||
|
|
||||||
|
|
||||||
|
#include <QtCore/qglobal.h> |
||||||
|
|
||||||
|
#ifdef QEXTSERIALPORT_LIB |
||||||
|
# define QEXTSERIALPORT_EXPORT Q_DECL_EXPORT |
||||||
|
#else |
||||||
|
# define QEXTSERIALPORT_EXPORT Q_DECL_IMPORT |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <QString> |
||||||
|
#include <QList> |
||||||
|
#include <QObject> |
||||||
|
//#include "qextserialport_global.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN |
||||||
|
#include <windows.h> |
||||||
|
#include <setupapi.h> |
||||||
|
#include <dbt.h> |
||||||
|
#endif /*Q_OS_WIN*/ |
||||||
|
|
||||||
|
#ifdef Q_OS_MAC |
||||||
|
#include <IOKit/usb/IOUSBLib.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
/*!
|
||||||
|
* Structure containing port information. |
||||||
|
*/ |
||||||
|
struct QextPortInfo { |
||||||
|
QString portName; ///< Port name.
|
||||||
|
QString physName; ///< Physical name.
|
||||||
|
QString friendName; ///< Friendly name.
|
||||||
|
QString enumName; ///< Enumerator name.
|
||||||
|
int vendorID; ///< Vendor ID.
|
||||||
|
int productID; ///< Product ID
|
||||||
|
}; |
||||||
|
|
||||||
|
#ifdef Q_OS_WIN |
||||||
|
#ifdef QT_GUI_LIB |
||||||
|
#include <QWidget> |
||||||
|
class QextSerialEnumerator; |
||||||
|
|
||||||
|
class QextSerialRegistrationWidget : public QWidget |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
QextSerialRegistrationWidget( QextSerialEnumerator* qese ) { |
||||||
|
this->qese = qese; |
||||||
|
} |
||||||
|
~QextSerialRegistrationWidget( ) { } |
||||||
|
|
||||||
|
protected: |
||||||
|
QextSerialEnumerator* qese; |
||||||
|
bool winEvent( MSG* message, long* result ); |
||||||
|
}; |
||||||
|
#endif // QT_GUI_LIB
|
||||||
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides list of ports available in the system. |
||||||
|
|
||||||
|
\section Usage |
||||||
|
To poll the system for a list of connected devices, simply use getPorts(). Each |
||||||
|
QextPortInfo structure will populated with information about the corresponding device. |
||||||
|
|
||||||
|
\b Example |
||||||
|
\code |
||||||
|
QList<QextPortInfo> ports = QextSerialEnumerator::getPorts(); |
||||||
|
foreach( QextPortInfo port, ports ) { |
||||||
|
// inspect port...
|
||||||
|
} |
||||||
|
\endcode |
||||||
|
|
||||||
|
To enable event-driven notification of device connection events, first call |
||||||
|
setUpNotifications() and then connect to the deviceDiscovered() and deviceRemoved() |
||||||
|
signals. Event-driven behavior is currently available only on Windows and OS X. |
||||||
|
|
||||||
|
\b Example |
||||||
|
\code |
||||||
|
QextSerialEnumerator* enumerator = new QextSerialEnumerator(); |
||||||
|
connect(enumerator, SIGNAL(deviceDiscovered(const QextPortInfo &)), |
||||||
|
myClass, SLOT(onDeviceDiscovered(const QextPortInfo &))); |
||||||
|
connect(enumerator, SIGNAL(deviceRemoved(const QextPortInfo &)), |
||||||
|
myClass, SLOT(onDeviceRemoved(const QextPortInfo &))); |
||||||
|
\endcode |
||||||
|
|
||||||
|
\section Credits |
||||||
|
Windows implementation is based on Zach Gorman's work from |
||||||
|
<a href="http://www.codeproject.com">The Code Project</a> (http://www.codeproject.com/system/setupdi.asp).
|
||||||
|
|
||||||
|
OS X implementation, see |
||||||
|
http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_Finding_Devices/chapter_4_section_2.html
|
||||||
|
|
||||||
|
\author Michal Policht, Liam Staskawicz |
||||||
|
*/ |
||||||
|
class QEXTSERIALPORT_EXPORT QextSerialEnumerator : public QObject |
||||||
|
{ |
||||||
|
Q_OBJECT |
||||||
|
public: |
||||||
|
QextSerialEnumerator( ); |
||||||
|
~QextSerialEnumerator( ); |
||||||
|
|
||||||
|
#ifdef Q_OS_WIN |
||||||
|
LRESULT onDeviceChangeWin( WPARAM wParam, LPARAM lParam ); |
||||||
|
private: |
||||||
|
/*!
|
||||||
|
* Get value of specified property from the registry. |
||||||
|
* \param key handle to an open key. |
||||||
|
* \param property property name. |
||||||
|
* \return property value. |
||||||
|
*/ |
||||||
|
static QString getRegKeyValue(HKEY key, LPCTSTR property); |
||||||
|
|
||||||
|
/*!
|
||||||
|
* Get specific property from registry. |
||||||
|
* \param devInfo pointer to the device information set that contains the interface |
||||||
|
* and its underlying device. Returned by SetupDiGetClassDevs() function. |
||||||
|
* \param devData pointer to an SP_DEVINFO_DATA structure that defines the device instance. |
||||||
|
* this is returned by SetupDiGetDeviceInterfaceDetail() function. |
||||||
|
* \param property registry property. One of defined SPDRP_* constants. |
||||||
|
* \return property string. |
||||||
|
*/ |
||||||
|
static QString getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property); |
||||||
|
|
||||||
|
/*!
|
||||||
|
* Search for serial ports using setupapi. |
||||||
|
* \param infoList list with result. |
||||||
|
*/ |
||||||
|
static void setupAPIScan(QList<QextPortInfo> & infoList); |
||||||
|
void setUpNotificationWin( ); |
||||||
|
static bool getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, |
||||||
|
PSP_DEVINFO_DATA devData, WPARAM wParam = DBT_DEVICEARRIVAL ); |
||||||
|
static void enumerateDevicesWin( const GUID & guidDev, QList<QextPortInfo>* infoList ); |
||||||
|
bool matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam); |
||||||
|
#ifdef QT_GUI_LIB |
||||||
|
QextSerialRegistrationWidget* notificationWidget; |
||||||
|
#endif |
||||||
|
#endif /*Q_OS_WIN*/ |
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX |
||||||
|
#ifdef Q_OS_MAC |
||||||
|
private: |
||||||
|
/*!
|
||||||
|
* Search for serial ports using IOKit. |
||||||
|
* \param infoList list with result. |
||||||
|
*/ |
||||||
|
static void scanPortsOSX(QList<QextPortInfo> & infoList); |
||||||
|
static void iterateServicesOSX(io_object_t service, QList<QextPortInfo> & infoList); |
||||||
|
static bool getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ); |
||||||
|
|
||||||
|
void setUpNotificationOSX( ); |
||||||
|
void onDeviceDiscoveredOSX( io_object_t service ); |
||||||
|
void onDeviceTerminatedOSX( io_object_t service ); |
||||||
|
friend void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); |
||||||
|
friend void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); |
||||||
|
|
||||||
|
IONotificationPortRef notificationPortRef; |
||||||
|
|
||||||
|
#else // Q_OS_MAC
|
||||||
|
private: |
||||||
|
/*!
|
||||||
|
* Search for serial ports on unix. |
||||||
|
* \param infoList list with result. |
||||||
|
*/ |
||||||
|
static void scanPortsNix(QList<QextPortInfo> & infoList); |
||||||
|
#endif // Q_OS_MAC
|
||||||
|
#endif /* Q_OS_UNIX */ |
||||||
|
|
||||||
|
public: |
||||||
|
/*!
|
||||||
|
Get list of ports. |
||||||
|
\return list of ports currently available in the system. |
||||||
|
*/ |
||||||
|
static QList<QextPortInfo> getPorts(); |
||||||
|
/*!
|
||||||
|
Enable event-driven notifications of board discovery/removal. |
||||||
|
*/ |
||||||
|
void setUpNotifications( ); |
||||||
|
|
||||||
|
signals: |
||||||
|
/*!
|
||||||
|
A new device has been connected to the system. |
||||||
|
|
||||||
|
setUpNotifications() must be called first to enable event-driven device notifications. |
||||||
|
Currently only implemented on Windows and OS X. |
||||||
|
\param info The device that has been discovered. |
||||||
|
*/ |
||||||
|
void deviceDiscovered( const QextPortInfo & info ); |
||||||
|
/*!
|
||||||
|
A device has been disconnected from the system. |
||||||
|
|
||||||
|
setUpNotifications() must be called first to enable event-driven device notifications. |
||||||
|
Currently only implemented on Windows and OS X. |
||||||
|
\param info The device that was disconnected. |
||||||
|
*/ |
||||||
|
void deviceRemoved( const QextPortInfo & info ); |
||||||
|
}; |
||||||
|
|
||||||
|
#endif /*_QEXTSERIALENUMERATOR_H_*/ |
@ -0,0 +1,288 @@ |
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#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 ); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,75 @@ |
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "qextserialenumerator.h" |
||||||
|
#include <QDebug> |
||||||
|
#include <QMetaType> |
||||||
|
#include <QStringList> |
||||||
|
#include <QDir> |
||||||
|
|
||||||
|
QextSerialEnumerator::QextSerialEnumerator( ) |
||||||
|
{ |
||||||
|
if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) |
||||||
|
qRegisterMetaType<QextPortInfo>("QextPortInfo"); |
||||||
|
} |
||||||
|
|
||||||
|
QextSerialEnumerator::~QextSerialEnumerator( ) |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
QList<QextPortInfo> QextSerialEnumerator::getPorts() |
||||||
|
{ |
||||||
|
QList<QextPortInfo> infoList; |
||||||
|
#ifdef Q_OS_LINUX |
||||||
|
QStringList portNamePrefixes, portNameList; |
||||||
|
portNamePrefixes << "ttyS*"; // list normal serial ports first
|
||||||
|
|
||||||
|
QDir dir("/dev"); |
||||||
|
portNameList = dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name); |
||||||
|
|
||||||
|
// remove the values which are not serial ports for e.g. /dev/ttysa
|
||||||
|
for (int i = 0; i < portNameList.size(); i++) { |
||||||
|
bool ok; |
||||||
|
QString current = portNameList.at(i); |
||||||
|
// remove the ttyS part, and check, if the other part is a number
|
||||||
|
current.remove(0,4).toInt(&ok, 10); |
||||||
|
if (!ok) { |
||||||
|
portNameList.removeAt(i); |
||||||
|
i--; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// get the non standard serial ports names
|
||||||
|
// (USB-serial, bluetooth-serial, 18F PICs, and so on)
|
||||||
|
// if you know an other name prefix for serial ports please let us know
|
||||||
|
portNamePrefixes.clear(); |
||||||
|
portNamePrefixes << "ttyACM*" << "ttyUSB*" << "rfcomm*"; |
||||||
|
portNameList.append(dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name)); |
||||||
|
|
||||||
|
foreach (QString str , portNameList) { |
||||||
|
QextPortInfo inf; |
||||||
|
inf.physName = "/dev/"+str; |
||||||
|
inf.portName = str; |
||||||
|
|
||||||
|
if (str.contains("ttyS")) { |
||||||
|
inf.friendName = "Serial port "+str.remove(0, 4); |
||||||
|
} |
||||||
|
else if (str.contains("ttyUSB")) { |
||||||
|
inf.friendName = "USB-serial adapter "+str.remove(0, 6); |
||||||
|
} |
||||||
|
else if (str.contains("rfcomm")) { |
||||||
|
inf.friendName = "Bluetooth-serial adapter "+str.remove(0, 6); |
||||||
|
} |
||||||
|
inf.enumName = "/dev"; // is there a more helpful name for this?
|
||||||
|
infoList.append(inf); |
||||||
|
} |
||||||
|
#else |
||||||
|
qCritical("Enumeration for POSIX systems (except Linux) is not implemented yet."); |
||||||
|
#endif |
||||||
|
return infoList; |
||||||
|
} |
||||||
|
|
||||||
|
void QextSerialEnumerator::setUpNotifications( ) |
||||||
|
{ |
||||||
|
qCritical("Notifications for *Nix/FreeBSD are not implemented yet"); |
||||||
|
} |
@ -0,0 +1,206 @@ |
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include "qextserialenumerator.h" |
||||||
|
#include <QDebug> |
||||||
|
#include <QMetaType> |
||||||
|
|
||||||
|
#include <objbase.h> |
||||||
|
#include <initguid.h> |
||||||
|
#include "qextserialport.h" |
||||||
|
#include <QRegExp> |
||||||
|
|
||||||
|
QextSerialEnumerator::QextSerialEnumerator( ) |
||||||
|
{ |
||||||
|
if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) |
||||||
|
qRegisterMetaType<QextPortInfo>("QextPortInfo"); |
||||||
|
#if (defined QT_GUI_LIB) |
||||||
|
notificationWidget = 0; |
||||||
|
#endif // Q_OS_WIN
|
||||||
|
} |
||||||
|
|
||||||
|
QextSerialEnumerator::~QextSerialEnumerator( ) |
||||||
|
{ |
||||||
|
#if (defined QT_GUI_LIB) |
||||||
|
if( notificationWidget ) |
||||||
|
delete notificationWidget; |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// see http://msdn.microsoft.com/en-us/library/ms791134.aspx for list of GUID classes
|
||||||
|
#ifndef GUID_DEVCLASS_PORTS |
||||||
|
DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); |
||||||
|
#endif |
||||||
|
|
||||||
|
/* Gordon Schumacher's macros for TCHAR -> QString conversions and vice versa */ |
||||||
|
#ifdef UNICODE |
||||||
|
#define QStringToTCHAR(x) (wchar_t*) x.utf16() |
||||||
|
#define PQStringToTCHAR(x) (wchar_t*) x->utf16() |
||||||
|
#define TCHARToQString(x) QString::fromUtf16((ushort*)(x)) |
||||||
|
#define TCHARToQStringN(x,y) QString::fromUtf16((ushort*)(x),(y)) |
||||||
|
#else |
||||||
|
#define QStringToTCHAR(x) x.local8Bit().constData() |
||||||
|
#define PQStringToTCHAR(x) x->local8Bit().constData() |
||||||
|
#define TCHARToQString(x) QString::fromLocal8Bit((x)) |
||||||
|
#define TCHARToQStringN(x,y) QString::fromLocal8Bit((x),(y)) |
||||||
|
#endif /*UNICODE*/ |
||||||
|
|
||||||
|
|
||||||
|
//static
|
||||||
|
QString QextSerialEnumerator::getRegKeyValue(HKEY key, LPCTSTR property) |
||||||
|
{ |
||||||
|
DWORD size = 0; |
||||||
|
DWORD type; |
||||||
|
RegQueryValueEx(key, property, NULL, NULL, NULL, & size); |
||||||
|
BYTE* buff = new BYTE[size]; |
||||||
|
QString result; |
||||||
|
if( RegQueryValueEx(key, property, NULL, &type, buff, & size) == ERROR_SUCCESS ) |
||||||
|
result = TCHARToQString(buff); |
||||||
|
RegCloseKey(key); |
||||||
|
delete [] buff; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
//static
|
||||||
|
QString QextSerialEnumerator::getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property) |
||||||
|
{ |
||||||
|
DWORD buffSize = 0; |
||||||
|
SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, NULL, 0, & buffSize); |
||||||
|
BYTE* buff = new BYTE[buffSize]; |
||||||
|
SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, buff, buffSize, NULL); |
||||||
|
QString result = TCHARToQString(buff); |
||||||
|
delete [] buff; |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
QList<QextPortInfo> QextSerialEnumerator::getPorts() |
||||||
|
{ |
||||||
|
QList<QextPortInfo> ports; |
||||||
|
enumerateDevicesWin(GUID_DEVCLASS_PORTS, &ports); |
||||||
|
return ports; |
||||||
|
} |
||||||
|
|
||||||
|
void QextSerialEnumerator::enumerateDevicesWin( const GUID & guid, QList<QextPortInfo>* infoList ) |
||||||
|
{ |
||||||
|
HDEVINFO devInfo; |
||||||
|
if( (devInfo = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT)) != INVALID_HANDLE_VALUE) |
||||||
|
{ |
||||||
|
SP_DEVINFO_DATA devInfoData; |
||||||
|
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); |
||||||
|
for(int i = 0; SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++) |
||||||
|
{ |
||||||
|
QextPortInfo info; |
||||||
|
info.productID = info.vendorID = 0; |
||||||
|
getDeviceDetailsWin( &info, devInfo, &devInfoData ); |
||||||
|
infoList->append(info); |
||||||
|
} |
||||||
|
SetupDiDestroyDeviceInfoList(devInfo); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef QT_GUI_LIB |
||||||
|
bool QextSerialRegistrationWidget::winEvent( MSG* message, long* result ) |
||||||
|
{ |
||||||
|
if ( message->message == WM_DEVICECHANGE ) { |
||||||
|
qese->onDeviceChangeWin( message->wParam, message->lParam ); |
||||||
|
*result = 1; |
||||||
|
return true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
void QextSerialEnumerator::setUpNotifications( ) |
||||||
|
{ |
||||||
|
#ifdef QT_GUI_LIB |
||||||
|
if(notificationWidget) |
||||||
|
return; |
||||||
|
notificationWidget = new QextSerialRegistrationWidget(this); |
||||||
|
|
||||||
|
DEV_BROADCAST_DEVICEINTERFACE dbh; |
||||||
|
ZeroMemory(&dbh, sizeof(dbh)); |
||||||
|
dbh.dbcc_size = sizeof(dbh); |
||||||
|
dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; |
||||||
|
CopyMemory(&dbh.dbcc_classguid, &GUID_DEVCLASS_PORTS, sizeof(GUID)); |
||||||
|
if( RegisterDeviceNotification( notificationWidget->winId( ), &dbh, DEVICE_NOTIFY_WINDOW_HANDLE ) == NULL) |
||||||
|
qWarning() << "RegisterDeviceNotification failed:" << GetLastError(); |
||||||
|
// setting up notifications doesn't tell us about devices already connected
|
||||||
|
// so get those manually
|
||||||
|
foreach( QextPortInfo port, getPorts() ) |
||||||
|
emit deviceDiscovered( port ); |
||||||
|
#else |
||||||
|
qWarning("QextSerialEnumerator: GUI not enabled - can't register for device notifications."); |
||||||
|
#endif // QT_GUI_LIB
|
||||||
|
} |
||||||
|
|
||||||
|
LRESULT QextSerialEnumerator::onDeviceChangeWin( WPARAM wParam, LPARAM lParam ) |
||||||
|
{ |
||||||
|
if ( DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) |
||||||
|
{ |
||||||
|
PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; |
||||||
|
if( pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE ) |
||||||
|
{ |
||||||
|
PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; |
||||||
|
// delimiters are different across APIs...change to backslash. ugh.
|
||||||
|
QString deviceID = TCHARToQString(pDevInf->dbcc_name).toUpper().replace("#", "\\"); |
||||||
|
|
||||||
|
matchAndDispatchChangedDevice(deviceID, GUID_DEVCLASS_PORTS, wParam); |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
bool QextSerialEnumerator::matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam) |
||||||
|
{ |
||||||
|
bool rv = false; |
||||||
|
DWORD dwFlag = (DBT_DEVICEARRIVAL == wParam) ? DIGCF_PRESENT : DIGCF_ALLCLASSES; |
||||||
|
HDEVINFO devInfo; |
||||||
|
if( (devInfo = SetupDiGetClassDevs(&guid,NULL,NULL,dwFlag)) != INVALID_HANDLE_VALUE ) |
||||||
|
{ |
||||||
|
SP_DEVINFO_DATA spDevInfoData; |
||||||
|
spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); |
||||||
|
for(int i=0; SetupDiEnumDeviceInfo(devInfo, i, &spDevInfoData); i++) |
||||||
|
{ |
||||||
|
DWORD nSize=0 ; |
||||||
|
TCHAR buf[MAX_PATH]; |
||||||
|
if ( SetupDiGetDeviceInstanceId(devInfo, &spDevInfoData, buf, MAX_PATH, &nSize) && |
||||||
|
deviceID.contains(TCHARToQString(buf))) // we found a match
|
||||||
|
{ |
||||||
|
rv = true; |
||||||
|
QextPortInfo info; |
||||||
|
info.productID = info.vendorID = 0; |
||||||
|
getDeviceDetailsWin( &info, devInfo, &spDevInfoData, wParam ); |
||||||
|
if( wParam == DBT_DEVICEARRIVAL ) |
||||||
|
emit deviceDiscovered(info); |
||||||
|
else if( wParam == DBT_DEVICEREMOVECOMPLETE ) |
||||||
|
emit deviceRemoved(info); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
SetupDiDestroyDeviceInfoList(devInfo); |
||||||
|
} |
||||||
|
return rv; |
||||||
|
} |
||||||
|
|
||||||
|
bool QextSerialEnumerator::getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, PSP_DEVINFO_DATA devData, WPARAM wParam ) |
||||||
|
{ |
||||||
|
portInfo->friendName = getDeviceProperty(devInfo, devData, SPDRP_FRIENDLYNAME); |
||||||
|
if( wParam == DBT_DEVICEARRIVAL) |
||||||
|
portInfo->physName = getDeviceProperty(devInfo, devData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME); |
||||||
|
portInfo->enumName = getDeviceProperty(devInfo, devData, SPDRP_ENUMERATOR_NAME); |
||||||
|
QString hardwareIDs = getDeviceProperty(devInfo, devData, SPDRP_HARDWAREID); |
||||||
|
HKEY devKey = SetupDiOpenDevRegKey(devInfo, devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ); |
||||||
|
portInfo->portName = QextSerialPort::fullPortNameWin( getRegKeyValue(devKey, TEXT("PortName")) ); |
||||||
|
QRegExp idRx("VID_(\\w+)&PID_(\\w+)"); |
||||||
|
if( hardwareIDs.toUpper().contains(idRx) ) |
||||||
|
{ |
||||||
|
bool dummy; |
||||||
|
portInfo->vendorID = idRx.cap(1).toInt(&dummy, 16); |
||||||
|
portInfo->productID = idRx.cap(2).toInt(&dummy, 16); |
||||||
|
//qDebug() << "got vid:" << vid << "pid:" << pid;
|
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
Loading…
Reference in new issue