Dodgy code for detecting MTP devices on Mac without crashing (I'm looking at you libmtp).

This commit is contained in:
John Maguire 2010-09-01 20:31:10 +00:00
parent 3858c30614
commit cce7fadc89
2 changed files with 233 additions and 4 deletions

View File

@ -9,6 +9,8 @@
#include <DiskArbitration/DADissenter.h> #include <DiskArbitration/DADissenter.h>
#include <IOKit/IOKitLib.h> #include <IOKit/IOKitLib.h>
struct libusb_context;
class MacDeviceLister : public DeviceLister { class MacDeviceLister : public DeviceLister {
Q_OBJECT Q_OBJECT
public: public:
@ -28,6 +30,13 @@ class MacDeviceLister : public DeviceLister {
virtual void UnmountDevice(const QString &id); virtual void UnmountDevice(const QString &id);
virtual void UpdateDeviceFreeSpace(const QString& id); virtual void UpdateDeviceFreeSpace(const QString& id);
struct MTPDevice {
QString vendor;
quint16 vendor_id;
QString product;
quint16 product_id;
};
public slots: public slots:
virtual void ShutDown(); virtual void ShutDown();
@ -36,14 +45,26 @@ class MacDeviceLister : public DeviceLister {
static void DiskAddedCallback(DADiskRef disk, void* context); static void DiskAddedCallback(DADiskRef disk, void* context);
static void DiskRemovedCallback(DADiskRef disk, void* context); static void DiskRemovedCallback(DADiskRef disk, void* context);
static void USBDeviceAddedCallback(void* refcon, io_iterator_t it);
static void DiskUnmountCallback( static void DiskUnmountCallback(
DADiskRef disk, DADissenterRef dissenter, void* context); DADiskRef disk, DADissenterRef dissenter, void* context);
DASessionRef loop_session_; DASessionRef loop_session_;
CFRunLoopRef run_loop_; CFRunLoopRef run_loop_;
libusb_context* usb_context_;
QMap<QString, QString> current_devices_; QMap<QString, QString> current_devices_;
static QSet<MTPDevice> sMTPDeviceList;
}; };
uint qHash(const MacDeviceLister::MTPDevice& device);
inline bool operator==(const MacDeviceLister::MTPDevice& a, const MacDeviceLister::MTPDevice& b) {
return (a.vendor == b.vendor) &&
(a.vendor_id == b.vendor_id) &&
(a.product == b.product) &&
(a.product_id == b.product_id);
}
#endif #endif

View File

@ -3,6 +3,7 @@
#include <CoreFoundation/CFRunLoop.h> #include <CoreFoundation/CFRunLoop.h>
#include <DiskArbitration/DiskArbitration.h> #include <DiskArbitration/DiskArbitration.h>
#include <IOKit/kext/KextManager.h> #include <IOKit/kext/KextManager.h>
#include <IOKit/IOCFPlugin.h>
#include <IOKit/usb/IOUSBLib.h> #include <IOKit/usb/IOUSBLib.h>
#import <AppKit/NSWorkspace.h> #import <AppKit/NSWorkspace.h>
@ -13,6 +14,8 @@
#import <Foundation/NSString.h> #import <Foundation/NSString.h>
#import <Foundation/NSURL.h> #import <Foundation/NSURL.h>
#include <libmtp.h>
#include <QtDebug> #include <QtDebug>
#include <QString> #include <QString>
@ -30,6 +33,12 @@
#define kUSBProductString "USB Product Name" #define kUSBProductString "USB Product Name"
#endif #endif
QSet<MacDeviceLister::MTPDevice> MacDeviceLister::sMTPDeviceList;
uint qHash(const MacDeviceLister::MTPDevice& d) {
return qHash(d.vendor) ^ qHash(d.vendor_id) ^ qHash(d.product) ^ qHash(d.product_id);
}
MacDeviceLister::MacDeviceLister() { MacDeviceLister::MacDeviceLister() {
} }
@ -40,8 +49,27 @@ MacDeviceLister::~MacDeviceLister() {
void MacDeviceLister::Init() { void MacDeviceLister::Init() {
[[NSAutoreleasePool alloc] init]; [[NSAutoreleasePool alloc] init];
// Populate MTP Device list.
if (sMTPDeviceList.empty()) {
LIBMTP_device_entry_t* devices = NULL;
int num = 0;
if (LIBMTP_Get_Supported_Devices_List(&devices, &num) != 0) {
qWarning() << "Failed to get MTP device list";
} else {
for (int i = 0; i < num; ++i) {
LIBMTP_device_entry_t device = devices[i];
MTPDevice d;
d.vendor = QString::fromAscii(device.vendor);
d.vendor_id = device.vendor_id;
d.product = QString::fromAscii(device.product);
d.product_id = device.product_id;
}
}
}
run_loop_ = CFRunLoopGetCurrent(); run_loop_ = CFRunLoopGetCurrent();
// Register for disk mounts/unmounts.
loop_session_ = DASessionCreate(kCFAllocatorDefault); loop_session_ = DASessionCreate(kCFAllocatorDefault);
DARegisterDiskAppearedCallback( DARegisterDiskAppearedCallback(
loop_session_, kDADiskDescriptionMatchVolumeMountable, &DiskAddedCallback, reinterpret_cast<void*>(this)); loop_session_, kDADiskDescriptionMatchVolumeMountable, &DiskAddedCallback, reinterpret_cast<void*>(this));
@ -49,6 +77,26 @@ void MacDeviceLister::Init() {
loop_session_, NULL, &DiskRemovedCallback, reinterpret_cast<void*>(this)); loop_session_, NULL, &DiskRemovedCallback, reinterpret_cast<void*>(this));
DASessionScheduleWithRunLoop(loop_session_, run_loop_, kCFRunLoopDefaultMode); DASessionScheduleWithRunLoop(loop_session_, run_loop_, kCFRunLoopDefaultMode);
// Register for USB device connection/disconnection.
IONotificationPortRef notification_port = IONotificationPortCreate(kIOMasterPortDefault);
CFMutableDictionaryRef matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
io_iterator_t it;
kern_return_t err = IOServiceAddMatchingNotification(
notification_port,
kIOFirstMatchNotification,
matching_dict,
&USBDeviceAddedCallback,
reinterpret_cast<void*>(this),
&it);
if (err == KERN_SUCCESS) {
USBDeviceAddedCallback(this, it);
} else {
qWarning() << "Could not add notification on USB device connection";
}
CFRunLoopSourceRef io_source = IONotificationPortGetRunLoopSource(notification_port);
CFRunLoopAddSource(run_loop_, io_source, kCFRunLoopDefaultMode);
CFRunLoopRun(); CFRunLoopRun();
} }
@ -114,7 +162,7 @@ quint64 GetUSBRegistryEntryInt64(io_object_t device, CFStringRef key) {
return 0; return 0;
} }
NSObject* GetPropertyForDevice(io_object_t device, NSString* key) { NSObject* GetPropertyForDevice(io_object_t device, CFStringRef key) {
CFMutableDictionaryRef properties; CFMutableDictionaryRef properties;
kern_return_t ret = IORegistryEntryCreateCFProperties( kern_return_t ret = IORegistryEntryCreateCFProperties(
device, &properties, kCFAllocatorDefault, 0); device, &properties, kCFAllocatorDefault, 0);
@ -124,7 +172,7 @@ NSObject* GetPropertyForDevice(io_object_t device, NSString* key) {
} }
NSDictionary* dict = (NSDictionary*)properties; NSDictionary* dict = (NSDictionary*)properties;
NSObject* prop = [dict objectForKey:key]; NSObject* prop = [dict objectForKey:(NSString*)key];
if (prop) { if (prop) {
return prop; return prop;
} }
@ -139,7 +187,7 @@ NSObject* GetPropertyForDevice(io_object_t device, NSString* key) {
} }
QString GetIconForDevice(io_object_t device) { QString GetIconForDevice(io_object_t device) {
NSDictionary* media_icon = (NSDictionary*)GetPropertyForDevice(device, @"IOMediaIcon"); NSDictionary* media_icon = (NSDictionary*)GetPropertyForDevice(device, CFSTR("IOMediaIcon"));
if (media_icon) { if (media_icon) {
NSString* bundle = (NSString*)[media_icon objectForKey:@"CFBundleIdentifier"]; NSString* bundle = (NSString*)[media_icon objectForKey:@"CFBundleIdentifier"];
NSString* file = (NSString*)[media_icon objectForKey:@"IOBundleResourceFile"]; NSString* file = (NSString*)[media_icon objectForKey:@"IOBundleResourceFile"];
@ -221,6 +269,166 @@ void MacDeviceLister::DiskRemovedCallback(DADiskRef disk, void* context) {
} }
} }
void MacDeviceLister::USBDeviceAddedCallback(void* refcon, io_iterator_t it) {
MacDeviceLister* me = reinterpret_cast<MacDeviceLister*>(refcon);
io_object_t object;
while ((object = IOIteratorNext(it))) {
CFStringRef class_name = IOObjectCopyClass(object);
if (CFStringCompare(class_name, CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
NSString* vendor = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBVendorString));
NSString* product = (NSString*)GetPropertyForDevice(object, CFSTR(kUSBProductString));
NSNumber* vendor_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBVendorID));
NSNumber* product_id = (NSNumber*)GetPropertyForDevice(object, CFSTR(kUSBProductID));
MTPDevice device;
device.vendor = QString::fromUtf8([vendor UTF8String]);
device.product = QString::fromUtf8([product UTF8String]);
device.vendor_id = [vendor_id unsignedShortValue];
device.product_id = [product_id unsignedShortValue];
qDebug() << device.vendor
<< device.vendor_id
<< device.product
<< device.product_id;
// First check the libmtp device list.
if (sMTPDeviceList.contains(device)) {
qDebug() << "Matched device to libmtp list!";
// emit.
return;
}
IOCFPlugInInterface** plugin_interface = NULL;
SInt32 score;
kern_return_t err = IOCreatePlugInInterfaceForService(
object,
kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugin_interface,
&score);
if (err != KERN_SUCCESS) {
continue;
}
IOUSBDeviceInterface** dev = NULL;
HRESULT result = (*plugin_interface)->QueryInterface(
plugin_interface,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
(LPVOID*)&dev);
(*plugin_interface)->Release(plugin_interface);
if (result || !dev) {
continue;
}
err = (*dev)->USBDeviceOpen(dev);
if (err != kIOReturnSuccess) {
continue;
}
// Request the string descriptor at 0xee.
// This is a magic string that indicates whether this device supports MTP.
// Fetch string descriptor length.
UInt8 desc[256]; // Max descriptor length.
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
req.bRequest = kUSBRqGetDescriptor;
req.wValue = (kUSBStringDesc << 8) | 0xee; // 0xee is the MTP descriptor.
req.wIndex = 0x0409; // English
req.wLength = 2;
req.pData = &desc;
err = (*dev)->DeviceRequest(dev, &req);
if (err != kIOReturnSuccess) {
continue;
}
UInt8 string_len = desc[0];
if (string_len == 0) {
continue;
}
// Fetch actual string descriptor.
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
req.bRequest = kUSBRqGetDescriptor;
req.wValue = (kUSBStringDesc << 8) | 0xee;
req.wIndex = 0x0409;
req.wLength = string_len;
req.pData = &desc;
err = (*dev)->DeviceRequest(dev, &req);
if (err != kIOReturnSuccess) {
continue;
}
// The device actually returned something. That's a good sign.
// Because this was designed by MS, the characters are in UTF-16 (LE?).
CFStringRef str = CFStringCreateWithCharacters(NULL, (const UniChar*)(desc + 2), (req.wLenDone-2) / 2);
char buf[256];
CFStringGetCString(str, buf, 256, kCFStringEncodingNonLossyASCII);
CFRelease(str);
if (QString(buf).startsWith("MSFT100")) {
// We got the OS descriptor!
char vendor_code = desc[16];
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBVendor, kUSBDevice);
req.bRequest = vendor_code;
req.wValue = 0;
req.wIndex = 4;
req.wLength = 256; // Magic number!
req.pData = &desc;
err = (*dev)->DeviceRequest(dev, &req);
if (err != kIOReturnSuccess) {
qDebug() << "Getting vendor OS descriptor failed";
continue;
}
// Moar magic!
if (desc[0] != 0x28) {
continue;
}
if (desc[0x12] != 'M' || desc[0x13] != 'T' || desc[0x14] != 'P') {
// Not quite.
continue;
}
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBVendor, kUSBDevice);
req.bRequest = vendor_code;
req.wValue = 0;
req.wIndex = 5;
req.wLength = 256;
req.pData = &desc;
err = (*dev)->DeviceRequest(dev, &req);
if (err != kIOReturnSuccess || desc[0] != 0x28) {
continue;
}
if (desc[0x12] != 'M' || desc[0x13] != 'T' || desc[0x14] != 'P') {
// :-(
continue;
}
// Hurray! We made it!
qDebug() << "New MTP device detected!";
}
(*dev)->USBDeviceClose(dev);
(*dev)->Release(dev);
}
CFRelease(class_name);
IOObjectRelease(object);
}
}
QString MacDeviceLister::MakeFriendlyName(const QString& serial) { QString MacDeviceLister::MakeFriendlyName(const QString& serial) {
QString bsd_name = current_devices_[serial]; QString bsd_name = current_devices_[serial];
DASessionRef session = DASessionCreate(kCFAllocatorDefault); DASessionRef session = DASessionCreate(kCFAllocatorDefault);
@ -308,7 +516,7 @@ quint64 MacDeviceLister::DeviceCapacity(const QString& serial){
io_object_t device = DADiskCopyIOMedia(disk); io_object_t device = DADiskCopyIOMedia(disk);
NSNumber* capacity = (NSNumber*)GetPropertyForDevice(device, @"Size"); NSNumber* capacity = (NSNumber*)GetPropertyForDevice(device, CFSTR("Size"));
quint64 ret = [capacity unsignedLongLongValue]; quint64 ret = [capacity unsignedLongLongValue];