Dodgy code for detecting MTP devices on Mac without crashing (I'm looking at you libmtp).
This commit is contained in:
parent
3858c30614
commit
cce7fadc89
|
@ -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
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue