2010-11-23 12:42:19 +01:00
|
|
|
/* This file is part of Clementine.
|
|
|
|
Copyright 2010, David Sansome <me@davidsansome.com>
|
|
|
|
|
|
|
|
Clementine is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
Clementine is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2010-07-23 15:46:30 +02:00
|
|
|
#include "macdevicelister.h"
|
2020-09-18 16:15:19 +02:00
|
|
|
#include "config.h"
|
2011-04-22 18:50:29 +02:00
|
|
|
#include "core/logging.h"
|
2012-01-21 01:06:56 +01:00
|
|
|
#include "core/scoped_cftyperef.h"
|
2012-01-31 13:54:03 +01:00
|
|
|
#include "core/scoped_nsautorelease_pool.h"
|
2012-01-21 01:06:56 +01:00
|
|
|
#include "core/scoped_nsobject.h"
|
2020-09-18 16:15:19 +02:00
|
|
|
#include "mtpconnection.h"
|
2010-09-04 21:32:36 +02:00
|
|
|
|
2010-07-23 15:46:30 +02:00
|
|
|
#include <CoreFoundation/CFRunLoop.h>
|
|
|
|
#include <DiskArbitration/DiskArbitration.h>
|
2010-09-01 22:31:10 +02:00
|
|
|
#include <IOKit/IOCFPlugin.h>
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <IOKit/kext/KextManager.h>
|
2011-08-11 21:37:09 +02:00
|
|
|
#include <IOKit/storage/IOCDMedia.h>
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <IOKit/storage/IOMedia.h>
|
|
|
|
#include <IOKit/usb/IOUSBLib.h>
|
2010-07-23 15:46:30 +02:00
|
|
|
|
|
|
|
#import <AppKit/NSWorkspace.h>
|
2010-07-24 20:34:22 +02:00
|
|
|
#import <Foundation/NSDictionary.h>
|
2010-07-23 15:46:30 +02:00
|
|
|
#import <Foundation/NSNotification.h>
|
2010-07-24 20:34:22 +02:00
|
|
|
#import <Foundation/NSPathUtilities.h>
|
2010-07-23 15:46:30 +02:00
|
|
|
#import <Foundation/NSString.h>
|
2010-07-24 20:34:22 +02:00
|
|
|
#import <Foundation/NSURL.h>
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-09-01 23:43:23 +02:00
|
|
|
#include <boost/scope_exit.hpp>
|
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
#include <libmtp.h>
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
#include <QMutex>
|
2010-07-23 15:46:30 +02:00
|
|
|
#include <QString>
|
|
|
|
#include <QStringList>
|
2018-11-17 15:08:37 +01:00
|
|
|
#include <QUrl>
|
|
|
|
#include <QUrlQuery>
|
|
|
|
#include <QtDebug>
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-26 13:01:36 +02:00
|
|
|
#ifndef kUSBSerialNumberString
|
|
|
|
#define kUSBSerialNumberString "USB Serial Number"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef kUSBVendorString
|
|
|
|
#define kUSBVendorString "USB Vendor Name"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef kUSBProductString
|
|
|
|
#define kUSBProductString "USB Product Name"
|
|
|
|
#endif
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
// io_object_t, io_service_t, io_iterator_t etc. are all typedef'd to unsigned
|
|
|
|
// int,
|
2012-01-24 03:12:51 +01:00
|
|
|
// hence the lack of templating here.
|
|
|
|
class ScopedIOObject {
|
|
|
|
public:
|
2014-01-30 14:48:49 +01:00
|
|
|
explicit ScopedIOObject(io_object_t object = 0) : object_(object) {}
|
2012-01-24 03:12:51 +01:00
|
|
|
|
|
|
|
~ScopedIOObject() {
|
2014-01-30 14:48:49 +01:00
|
|
|
if (object_) IOObjectRelease(object_);
|
2012-01-24 03:12:51 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
io_object_t get() const { return object_; }
|
2012-01-24 03:12:51 +01:00
|
|
|
|
|
|
|
private:
|
|
|
|
io_object_t object_;
|
|
|
|
|
|
|
|
Q_DISABLE_COPY(ScopedIOObject);
|
|
|
|
};
|
|
|
|
|
2010-09-01 23:50:05 +02:00
|
|
|
// Helpful MTP & USB links:
|
|
|
|
// Apple USB device interface guide:
|
|
|
|
// http://developer.apple.com/mac/library/documentation/DeviceDrivers/Conceptual/USBBook/USBDeviceInterfaces/USBDevInterfaces.html
|
|
|
|
// Example Apple code for requesting a USB device descriptor:
|
|
|
|
// http://www.opensource.apple.com/source/IOUSBFamily/IOUSBFamily-208.4.5/USBProber/BusProbeClass.m
|
|
|
|
// Libmtp's detection code:
|
|
|
|
// http://libmtp.cvs.sourceforge.net/viewvc/libmtp/libmtp/src/libusb-glue.c?view=markup
|
|
|
|
// Libusb's Mac code:
|
|
|
|
// http://www.libusb.org/browser/libusb/libusb/os/darwin_usb.c
|
|
|
|
// Microsoft OS Descriptors:
|
|
|
|
// http://www.microsoft.com/whdc/connect/usb/os_desc.mspx
|
|
|
|
// Symbian docs for implementing the device side:
|
|
|
|
// http://developer.symbian.org/main/documentation/reference/s3/pdk/GUID-3FF0F248-EDF0-5348-BC43-869CE1B5B415.html
|
|
|
|
// Libgphoto2 MTP detection code:
|
|
|
|
// http://www.sfr-fresh.com/unix/privat/libgphoto2-2.4.10.1.tar.gz:a/libgphoto2-2.4.10.1/libgphoto2_port/usb/check-mtp-device.c
|
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
QSet<MacDeviceLister::MTPDevice> MacDeviceLister::sMTPDeviceList;
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
uint qHash(const MacDeviceLister::MTPDevice& d) { return qHash(d.vendor_id) ^ qHash(d.product_id); }
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
MacDeviceLister::MacDeviceLister() {}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
MacDeviceLister::~MacDeviceLister() { CFRelease(loop_session_); }
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
void MacDeviceLister::Init() {
|
2012-01-31 13:54:03 +01:00
|
|
|
ScopedNSAutoreleasePool pool;
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
// Populate MTP Device list.
|
|
|
|
if (sMTPDeviceList.empty()) {
|
2014-02-21 17:24:49 +01:00
|
|
|
LIBMTP_device_entry_t* devices = nullptr;
|
2010-09-01 22:31:10 +02:00
|
|
|
int num = 0;
|
|
|
|
if (LIBMTP_Get_Supported_Devices_List(&devices, &num) != 0) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Failed to get MTP device list";
|
2010-09-01 22:31:10 +02:00
|
|
|
} else {
|
|
|
|
for (int i = 0; i < num; ++i) {
|
|
|
|
LIBMTP_device_entry_t device = devices[i];
|
|
|
|
MTPDevice d;
|
2018-11-17 15:08:37 +01:00
|
|
|
d.vendor = QString::fromLatin1(device.vendor);
|
2010-09-01 22:31:10 +02:00
|
|
|
d.vendor_id = device.vendor_id;
|
2018-11-17 15:08:37 +01:00
|
|
|
d.product = QString::fromLatin1(device.product);
|
2010-09-01 22:31:10 +02:00
|
|
|
d.product_id = device.product_id;
|
2010-09-02 23:20:27 +02:00
|
|
|
d.quirks = device.device_flags;
|
2010-09-04 22:11:14 +02:00
|
|
|
sMTPDeviceList << d;
|
2010-09-01 22:31:10 +02:00
|
|
|
}
|
|
|
|
}
|
2010-09-04 22:11:14 +02:00
|
|
|
|
|
|
|
MTPDevice d;
|
|
|
|
d.vendor = "SanDisk";
|
|
|
|
d.vendor_id = 0x781;
|
|
|
|
d.product = "Sansa Clip+";
|
|
|
|
d.product_id = 0x74d0;
|
|
|
|
|
|
|
|
d.quirks = 0x2 | 0x4 | 0x40 | 0x4000;
|
|
|
|
sMTPDeviceList << d;
|
2010-09-01 22:31:10 +02:00
|
|
|
}
|
|
|
|
|
2010-08-23 12:26:00 +02:00
|
|
|
run_loop_ = CFRunLoopGetCurrent();
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
// Register for disk mounts/unmounts.
|
2010-07-25 03:29:22 +02:00
|
|
|
loop_session_ = DASessionCreate(kCFAllocatorDefault);
|
2020-09-18 16:15:19 +02:00
|
|
|
DARegisterDiskAppearedCallback(loop_session_, kDADiskDescriptionMatchVolumeMountable,
|
|
|
|
&DiskAddedCallback, reinterpret_cast<void*>(this));
|
2014-02-21 17:24:49 +01:00
|
|
|
DARegisterDiskDisappearedCallback(loop_session_, nullptr, &DiskRemovedCallback,
|
2014-01-30 14:48:49 +01:00
|
|
|
reinterpret_cast<void*>(this));
|
2010-08-23 12:26:00 +02:00
|
|
|
DASessionScheduleWithRunLoop(loop_session_, run_loop_, kCFRunLoopDefaultMode);
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
// Register for USB device connection/disconnection.
|
2020-09-18 16:15:19 +02:00
|
|
|
IONotificationPortRef notification_port = IONotificationPortCreate(kIOMasterPortDefault);
|
|
|
|
CFMutableDictionaryRef matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
|
2010-09-04 21:32:36 +02:00
|
|
|
// IOServiceAddMatchingNotification decreases reference count.
|
|
|
|
CFRetain(matching_dict);
|
2010-09-01 22:31:10 +02:00
|
|
|
io_iterator_t it;
|
2020-09-18 16:15:19 +02:00
|
|
|
kern_return_t err =
|
|
|
|
IOServiceAddMatchingNotification(notification_port, kIOFirstMatchNotification, matching_dict,
|
|
|
|
&USBDeviceAddedCallback, reinterpret_cast<void*>(this), &it);
|
2010-09-01 22:31:10 +02:00
|
|
|
if (err == KERN_SUCCESS) {
|
|
|
|
USBDeviceAddedCallback(this, it);
|
|
|
|
} else {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Could not add notification on USB device connection";
|
2010-09-01 22:31:10 +02:00
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
err = IOServiceAddMatchingNotification(notification_port, kIOTerminatedNotification,
|
|
|
|
matching_dict, &USBDeviceRemovedCallback,
|
|
|
|
reinterpret_cast<void*>(this), &it);
|
2010-09-04 21:32:36 +02:00
|
|
|
if (err == KERN_SUCCESS) {
|
|
|
|
USBDeviceRemovedCallback(this, it);
|
|
|
|
} else {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Could not add notification USB device removal";
|
2010-09-04 21:32:36 +02:00
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
CFRunLoopSourceRef io_source = IONotificationPortGetRunLoopSource(notification_port);
|
2010-09-01 22:31:10 +02:00
|
|
|
CFRunLoopAddSource(run_loop_, io_source, kCFRunLoopDefaultMode);
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
CFRunLoopRun();
|
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
void MacDeviceLister::ShutDown() { CFRunLoopStop(run_loop_); }
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
// IOKit helpers.
|
|
|
|
namespace {
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2012-01-21 01:25:05 +01:00
|
|
|
// Caller is responsible for calling CFRelease().
|
2010-07-24 20:34:22 +02:00
|
|
|
CFTypeRef GetUSBRegistryEntry(io_object_t device, CFStringRef key) {
|
|
|
|
io_iterator_t it;
|
2020-09-18 16:15:19 +02:00
|
|
|
if (IORegistryEntryGetParentIterator(device, kIOServicePlane, &it) == KERN_SUCCESS) {
|
2010-07-24 20:34:22 +02:00
|
|
|
io_object_t next;
|
|
|
|
while ((next = IOIteratorNext(it))) {
|
2020-09-18 16:15:19 +02:00
|
|
|
CFTypeRef registry_entry =
|
|
|
|
(CFStringRef)IORegistryEntryCreateCFProperty(next, key, kCFAllocatorDefault, 0);
|
2010-07-24 20:34:22 +02:00
|
|
|
if (registry_entry) {
|
|
|
|
IOObjectRelease(next);
|
|
|
|
IOObjectRelease(it);
|
|
|
|
return registry_entry;
|
|
|
|
}
|
2010-07-24 21:02:49 +02:00
|
|
|
|
|
|
|
CFTypeRef ret = GetUSBRegistryEntry(next, key);
|
|
|
|
if (ret) {
|
|
|
|
IOObjectRelease(next);
|
|
|
|
IOObjectRelease(it);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
IOObjectRelease(next);
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
IOObjectRelease(it);
|
2014-02-21 17:24:49 +01:00
|
|
|
return nullptr;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString GetUSBRegistryEntryString(io_object_t device, CFStringRef key) {
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<CFStringRef> registry_string((CFStringRef)GetUSBRegistryEntry(device, key));
|
2010-07-24 20:34:22 +02:00
|
|
|
if (registry_string) {
|
2012-02-21 13:10:25 +01:00
|
|
|
return QString::fromUtf8([(NSString*)registry_string.get() UTF8String]);
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
2012-02-21 13:10:25 +01:00
|
|
|
return QString();
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
NSObject* GetPropertyForDevice(io_object_t device, CFStringRef key) {
|
2010-07-24 20:34:22 +02:00
|
|
|
CFMutableDictionaryRef properties;
|
2020-09-18 16:15:19 +02:00
|
|
|
kern_return_t ret =
|
|
|
|
IORegistryEntryCreateCFProperties(device, &properties, kCFAllocatorDefault, 0);
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
if (ret != KERN_SUCCESS) {
|
|
|
|
return nil;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> dict((NSDictionary*)properties); // Takes ownership.
|
2010-09-01 22:31:10 +02:00
|
|
|
NSObject* prop = [dict objectForKey:(NSString*)key];
|
2010-07-24 20:34:22 +02:00
|
|
|
if (prop) {
|
2012-01-21 01:25:05 +01:00
|
|
|
// The dictionary goes out of scope so we should retain this object.
|
|
|
|
[prop retain];
|
2010-07-24 20:34:22 +02:00
|
|
|
return prop;
|
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
io_object_t parent;
|
|
|
|
ret = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent);
|
|
|
|
if (ret == KERN_SUCCESS) {
|
|
|
|
return GetPropertyForDevice(parent, key);
|
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
return nil;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2011-11-21 15:16:24 +01:00
|
|
|
int GetUSBDeviceClass(io_object_t device) {
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<CFTypeRef> interface_class(
|
|
|
|
IORegistryEntrySearchCFProperty(device, kIOServicePlane, CFSTR(kUSBInterfaceClass),
|
|
|
|
kCFAllocatorDefault, kIORegistryIterateRecursively));
|
2012-01-21 01:06:56 +01:00
|
|
|
NSNumber* number = (NSNumber*)interface_class.get();
|
2011-11-21 15:16:24 +01:00
|
|
|
if (number) {
|
2011-11-21 16:33:01 +01:00
|
|
|
int ret = [number unsignedShortValue];
|
|
|
|
return ret;
|
2011-11-21 15:16:24 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString GetIconForDevice(io_object_t device) {
|
2014-01-30 14:48:49 +01:00
|
|
|
scoped_nsobject<NSDictionary> media_icon(
|
|
|
|
(NSDictionary*)GetPropertyForDevice(device, CFSTR("IOMediaIcon")));
|
2010-07-24 20:34:22 +02:00
|
|
|
if (media_icon) {
|
2020-09-18 16:15:19 +02:00
|
|
|
NSString* bundle = (NSString*)[media_icon objectForKey:@"CFBundleIdentifier"];
|
|
|
|
NSString* file = (NSString*)[media_icon objectForKey:@"IOBundleResourceFile"];
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
scoped_nsobject<NSURL> bundle_url(
|
2020-09-18 16:15:19 +02:00
|
|
|
(NSURL*)KextManagerCreateURLForBundleIdentifier(kCFAllocatorDefault, (CFStringRef)bundle));
|
2010-07-24 20:34:22 +02:00
|
|
|
|
|
|
|
QString path = QString::fromUtf8([[bundle_url path] UTF8String]);
|
|
|
|
path += "/Contents/Resources/";
|
2012-02-21 13:10:25 +01:00
|
|
|
path += QString::fromUtf8([file UTF8String]);
|
2010-07-24 20:34:22 +02:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString GetSerialForDevice(io_object_t device) {
|
2020-09-18 16:15:19 +02:00
|
|
|
QString serial = GetUSBRegistryEntryString(device, CFSTR(kUSBSerialNumberString));
|
2010-09-27 19:27:08 +02:00
|
|
|
if (!serial.isEmpty()) {
|
|
|
|
return "USB/" + serial;
|
|
|
|
}
|
|
|
|
return QString();
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-09-02 23:20:27 +02:00
|
|
|
QString GetSerialForMTPDevice(io_object_t device) {
|
2014-01-30 14:48:49 +01:00
|
|
|
scoped_nsobject<NSString> serial(
|
|
|
|
(NSString*)GetPropertyForDevice(device, CFSTR(kUSBSerialNumberString)));
|
2012-02-21 13:10:25 +01:00
|
|
|
return QString(QString("MTP/") + QString::fromUtf8([serial UTF8String]));
|
2010-09-02 23:20:27 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 21:02:49 +02:00
|
|
|
QString FindDeviceProperty(const QString& bsd_name, CFStringRef property) {
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-24 21:02:49 +02:00
|
|
|
|
2012-01-24 03:12:51 +01:00
|
|
|
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
|
|
|
|
QString ret = GetUSBRegistryEntryString(device.get(), property);
|
2010-07-24 21:02:49 +02:00
|
|
|
return ret;
|
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2011-04-04 15:15:56 +02:00
|
|
|
quint64 MacDeviceLister::GetFreeSpace(const QUrl& url) {
|
2010-09-04 21:32:36 +02:00
|
|
|
QMutexLocker l(&libmtp_mutex_);
|
|
|
|
MtpConnection connection(url);
|
|
|
|
if (!connection.is_valid()) {
|
2020-09-18 16:15:19 +02:00
|
|
|
qLog(Warning) << "Error connecting to MTP device, couldn't get device free space";
|
2010-09-04 21:32:36 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2011-04-04 15:15:56 +02:00
|
|
|
LIBMTP_devicestorage_t* storage = connection.device()->storage;
|
|
|
|
quint64 free_bytes = 0;
|
|
|
|
while (storage) {
|
|
|
|
free_bytes += storage->FreeSpaceInBytes;
|
|
|
|
storage = storage->next;
|
|
|
|
}
|
|
|
|
return free_bytes;
|
2010-09-04 21:32:36 +02:00
|
|
|
}
|
|
|
|
|
2011-04-04 15:15:56 +02:00
|
|
|
quint64 MacDeviceLister::GetCapacity(const QUrl& url) {
|
2010-09-04 21:32:36 +02:00
|
|
|
QMutexLocker l(&libmtp_mutex_);
|
|
|
|
MtpConnection connection(url);
|
|
|
|
if (!connection.is_valid()) {
|
2020-09-18 16:15:19 +02:00
|
|
|
qLog(Warning) << "Error connecting to MTP device, couldn't get device capacity";
|
2010-09-04 21:32:36 +02:00
|
|
|
return -1;
|
|
|
|
}
|
2011-04-04 15:15:56 +02:00
|
|
|
LIBMTP_devicestorage_t* storage = connection.device()->storage;
|
|
|
|
quint64 capacity_bytes = 0;
|
|
|
|
while (storage) {
|
|
|
|
capacity_bytes += storage->MaxCapacity;
|
|
|
|
storage = storage->next;
|
|
|
|
}
|
|
|
|
return capacity_bytes;
|
2010-09-04 21:32:36 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
void MacDeviceLister::DiskAddedCallback(DADiskRef disk, void* context) {
|
|
|
|
MacDeviceLister* me = reinterpret_cast<MacDeviceLister*>(context);
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> properties((NSDictionary*)DADiskCopyDescription(disk));
|
2011-08-11 21:37:09 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
NSString* kind = [properties objectForKey:(NSString*)kDADiskDescriptionMediaKindKey];
|
2011-08-15 18:13:05 +02:00
|
|
|
#ifdef HAVE_AUDIOCD
|
2011-08-11 21:37:09 +02:00
|
|
|
if (kind && strcmp([kind UTF8String], kIOCDMediaClass) == 0) {
|
|
|
|
// CD inserted.
|
2018-11-17 15:08:37 +01:00
|
|
|
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
2011-08-11 21:37:09 +02:00
|
|
|
me->cd_devices_ << bsd_name;
|
|
|
|
emit me->DeviceAdded(bsd_name);
|
|
|
|
return;
|
|
|
|
}
|
2011-08-15 18:13:05 +02:00
|
|
|
#endif
|
2011-08-11 21:37:09 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
NSURL* volume_path = [[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy];
|
2010-07-24 20:34:22 +02:00
|
|
|
|
|
|
|
if (volume_path) {
|
2012-01-24 03:12:51 +01:00
|
|
|
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
|
|
|
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(device.get()));
|
2020-09-18 16:15:19 +02:00
|
|
|
if (class_name &&
|
|
|
|
CFStringCompare(class_name.get(), CFSTR(kIOMediaClass), 0) == kCFCompareEqualTo) {
|
|
|
|
QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
|
|
|
QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
2010-09-02 20:24:39 +02:00
|
|
|
|
2012-01-21 01:25:05 +01:00
|
|
|
CFMutableDictionaryRef cf_properties;
|
2020-09-18 16:15:19 +02:00
|
|
|
kern_return_t ret =
|
|
|
|
IORegistryEntryCreateCFProperties(device.get(), &cf_properties, kCFAllocatorDefault, 0);
|
2010-09-02 20:24:39 +02:00
|
|
|
|
|
|
|
if (ret == KERN_SUCCESS) {
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> dict((NSDictionary*)cf_properties); // Takes ownership.
|
2010-09-02 20:24:39 +02:00
|
|
|
if ([[dict objectForKey:@"Removable"] intValue] == 1) {
|
2012-01-24 03:12:51 +01:00
|
|
|
QString serial = GetSerialForDevice(device.get());
|
2010-09-27 19:27:08 +02:00
|
|
|
if (!serial.isEmpty()) {
|
|
|
|
me->current_devices_[serial] = QString(DADiskGetBSDName(disk));
|
|
|
|
emit me->DeviceAdded(serial);
|
|
|
|
}
|
2010-09-02 20:24:39 +02:00
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
void MacDeviceLister::DiskRemovedCallback(DADiskRef disk, void* context) {
|
|
|
|
MacDeviceLister* me = reinterpret_cast<MacDeviceLister*>(context);
|
|
|
|
// We cannot access the USB tree when the disk is removed but we still get
|
|
|
|
// the BSD disk name.
|
2011-08-11 21:43:00 +02:00
|
|
|
|
2018-11-17 15:08:37 +01:00
|
|
|
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
2011-08-11 21:43:00 +02:00
|
|
|
if (me->cd_devices_.remove(bsd_name)) {
|
|
|
|
emit me->DeviceRemoved(bsd_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
for (QMap<QString, QString>::iterator it = me->current_devices_.begin();
|
|
|
|
it != me->current_devices_.end(); ++it) {
|
2011-08-11 21:43:00 +02:00
|
|
|
if (it.value() == bsd_name) {
|
2010-07-24 20:34:22 +02:00
|
|
|
emit me->DeviceRemoved(it.key());
|
|
|
|
me->current_devices_.erase(it);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
bool DeviceRequest(IOUSBDeviceInterface** dev, quint8 direction, quint8 type, quint8 recipient,
|
|
|
|
quint8 request_code, quint16 value, quint16 index, quint16 length,
|
|
|
|
QByteArray* data) {
|
2010-09-01 23:43:23 +02:00
|
|
|
IOUSBDevRequest req;
|
|
|
|
req.bmRequestType = USBmakebmRequestType(direction, type, recipient);
|
|
|
|
req.bRequest = request_code;
|
|
|
|
req.wValue = value;
|
|
|
|
req.wIndex = index;
|
|
|
|
req.wLength = length;
|
|
|
|
data->resize(256);
|
|
|
|
req.pData = data->data();
|
|
|
|
kern_return_t err = (*dev)->DeviceRequest(dev, &req);
|
|
|
|
if (err != kIOReturnSuccess) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
data->resize(req.wLenDone);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2010-09-02 20:10:40 +02:00
|
|
|
int GetBusNumber(io_object_t o) {
|
|
|
|
io_iterator_t it;
|
|
|
|
kern_return_t err = IORegistryEntryGetParentIterator(o, kIOServicePlane, &it);
|
|
|
|
if (err != KERN_SUCCESS) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
while ((o = IOIteratorNext(it))) {
|
|
|
|
NSObject* bus = GetPropertyForDevice(o, CFSTR("USBBusNumber"));
|
|
|
|
if (bus) {
|
|
|
|
NSNumber* bus_num = (NSNumber*)bus;
|
|
|
|
return [bus_num intValue];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
void MacDeviceLister::USBDeviceAddedCallback(void* refcon, io_iterator_t it) {
|
|
|
|
MacDeviceLister* me = reinterpret_cast<MacDeviceLister*>(refcon);
|
|
|
|
|
|
|
|
io_object_t object;
|
|
|
|
while ((object = IOIteratorNext(it))) {
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
|
2014-01-30 14:48:49 +01:00
|
|
|
BOOST_SCOPE_EXIT((object)) { IOObjectRelease(object); }
|
|
|
|
BOOST_SCOPE_EXIT_END
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
if (CFStringCompare(class_name.get(), 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));
|
2011-11-21 15:16:24 +01:00
|
|
|
int interface_class = GetUSBDeviceClass(object);
|
|
|
|
qLog(Debug) << "Interface class:" << interface_class;
|
|
|
|
|
2010-09-03 00:35:00 +02:00
|
|
|
QString serial = GetSerialForMTPDevice(object);
|
2010-09-01 22:31:10 +02:00
|
|
|
|
|
|
|
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];
|
2010-09-02 23:20:27 +02:00
|
|
|
device.quirks = 0;
|
|
|
|
|
|
|
|
device.bus = -1;
|
|
|
|
device.address = -1;
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
if (device.vendor_id == kAppleVendorID || // I think we can safely skip Apple products.
|
2011-09-13 15:57:13 +02:00
|
|
|
// Blacklist ilok2 as this probe may be breaking it.
|
2011-09-15 17:42:03 +02:00
|
|
|
(device.vendor_id == 0x088e && device.product_id == 0x5036) ||
|
|
|
|
// Blacklist eLicenser
|
2011-11-21 15:16:24 +01:00
|
|
|
(device.vendor_id == 0x0819 && device.product_id == 0x0101) ||
|
2013-07-09 12:03:01 +02:00
|
|
|
// Skip HID devices, printers and hubs.
|
2011-11-21 15:16:24 +01:00
|
|
|
interface_class == kUSBHIDInterfaceClass ||
|
2020-09-18 16:15:19 +02:00
|
|
|
interface_class == kUSBPrintingInterfaceClass || interface_class == kUSBHubClass) {
|
2010-09-01 23:43:23 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
NSNumber* addr = (NSNumber*)GetPropertyForDevice(object, CFSTR("USB Address"));
|
2010-09-02 20:10:40 +02:00
|
|
|
int bus = GetBusNumber(object);
|
|
|
|
if (!addr || bus == -1) {
|
|
|
|
// Failed to get bus or address number.
|
|
|
|
continue;
|
|
|
|
}
|
2010-09-02 23:20:27 +02:00
|
|
|
device.bus = bus;
|
|
|
|
device.address = [addr intValue];
|
2010-09-02 20:10:40 +02:00
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
// First check the libmtp device list.
|
2010-09-02 23:20:27 +02:00
|
|
|
QSet<MTPDevice>::const_iterator it = sMTPDeviceList.find(device);
|
|
|
|
if (it != sMTPDeviceList.end()) {
|
|
|
|
// Fill in quirks flags from libmtp.
|
|
|
|
device.quirks = it->quirks;
|
|
|
|
me->FoundMTPDevice(device, GetSerialForMTPDevice(object));
|
|
|
|
continue;
|
2010-09-01 22:31:10 +02:00
|
|
|
}
|
|
|
|
|
2014-02-21 17:24:49 +01:00
|
|
|
IOCFPlugInInterface** plugin_interface = nullptr;
|
2010-09-01 22:31:10 +02:00
|
|
|
SInt32 score;
|
|
|
|
kern_return_t err = IOCreatePlugInInterfaceForService(
|
2020-09-18 16:15:19 +02:00
|
|
|
object, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin_interface, &score);
|
2010-09-01 22:31:10 +02:00
|
|
|
if (err != KERN_SUCCESS) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2014-02-21 17:24:49 +01:00
|
|
|
IOUSBDeviceInterface** dev = nullptr;
|
2020-09-18 16:15:19 +02:00
|
|
|
HRESULT result =
|
|
|
|
(*plugin_interface)
|
|
|
|
->QueryInterface(plugin_interface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
|
|
|
|
(LPVOID*)&dev);
|
2010-09-01 22:31:10 +02:00
|
|
|
|
|
|
|
(*plugin_interface)->Release(plugin_interface);
|
|
|
|
|
|
|
|
if (result || !dev) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = (*dev)->USBDeviceOpen(dev);
|
|
|
|
if (err != kIOReturnSuccess) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2010-09-01 23:43:23 +02:00
|
|
|
// Automatically close & release usb device at scope exit.
|
|
|
|
BOOST_SCOPE_EXIT((dev)) {
|
|
|
|
(*dev)->USBDeviceClose(dev);
|
|
|
|
(*dev)->Release(dev);
|
2014-01-30 14:48:49 +01:00
|
|
|
}
|
|
|
|
BOOST_SCOPE_EXIT_END
|
2010-09-01 22:31:10 +02:00
|
|
|
|
|
|
|
// Request the string descriptor at 0xee.
|
|
|
|
// This is a magic string that indicates whether this device supports MTP.
|
2010-09-01 23:43:23 +02:00
|
|
|
QByteArray data;
|
2020-09-18 16:15:19 +02:00
|
|
|
bool ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor,
|
2014-01-30 14:48:49 +01:00
|
|
|
(kUSBStringDesc << 8) | 0xee, 0x0409, 2, &data);
|
|
|
|
if (!ret) continue;
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2010-09-01 23:43:23 +02:00
|
|
|
UInt8 string_len = data[0];
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor,
|
|
|
|
(kUSBStringDesc << 8) | 0xee, 0x0409, string_len, &data);
|
2014-01-30 14:48:49 +01:00
|
|
|
if (!ret) continue;
|
2010-09-01 22:31:10 +02:00
|
|
|
|
|
|
|
// The device actually returned something. That's a good sign.
|
|
|
|
// Because this was designed by MS, the characters are in UTF-16 (LE?).
|
2020-09-18 16:15:19 +02:00
|
|
|
QString str =
|
|
|
|
QString::fromUtf16(reinterpret_cast<ushort*>(data.data() + 2), (data.size() / 2) - 2);
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2010-09-01 23:43:23 +02:00
|
|
|
if (str.startsWith("MSFT100")) {
|
2010-09-01 22:31:10 +02:00
|
|
|
// We got the OS descriptor!
|
2010-09-01 23:43:23 +02:00
|
|
|
char vendor_code = data[16];
|
2020-09-18 16:15:19 +02:00
|
|
|
ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 4, 256, &data);
|
2014-01-30 14:48:49 +01:00
|
|
|
if (!ret || data.at(0) != 0x28) continue;
|
2010-09-01 22:31:10 +02:00
|
|
|
|
2018-11-17 15:08:37 +01:00
|
|
|
if (QString::fromLatin1(data.data() + 0x12, 3) != "MTP") {
|
2010-09-01 22:31:10 +02:00
|
|
|
// Not quite.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 5, 256, &data);
|
2010-09-01 23:43:23 +02:00
|
|
|
if (!ret || data.at(0) != 0x28) {
|
2010-09-01 22:31:10 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-11-17 15:08:37 +01:00
|
|
|
if (QString::fromLatin1(data.data() + 0x12, 3) != "MTP") {
|
2010-09-01 23:43:23 +02:00
|
|
|
// Not quite.
|
2010-09-01 22:31:10 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Hurray! We made it!
|
2010-09-03 00:35:00 +02:00
|
|
|
me->FoundMTPDevice(device, serial);
|
2010-09-01 22:31:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-04 21:32:36 +02:00
|
|
|
void MacDeviceLister::USBDeviceRemovedCallback(void* refcon, io_iterator_t it) {
|
|
|
|
MacDeviceLister* me = reinterpret_cast<MacDeviceLister*>(refcon);
|
|
|
|
io_object_t object;
|
|
|
|
while ((object = IOIteratorNext(it))) {
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
|
2014-01-30 14:48:49 +01:00
|
|
|
BOOST_SCOPE_EXIT((object)) { IOObjectRelease(object); }
|
|
|
|
BOOST_SCOPE_EXIT_END
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
if (CFStringCompare(class_name.get(), 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));
|
2010-09-04 21:32:36 +02:00
|
|
|
QString serial = GetSerialForMTPDevice(object);
|
|
|
|
|
|
|
|
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];
|
2012-01-21 00:22:14 +01:00
|
|
|
|
2010-09-04 21:32:36 +02:00
|
|
|
me->RemovedMTPDevice(serial);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MacDeviceLister::RemovedMTPDevice(const QString& serial) {
|
|
|
|
int count = mtp_devices_.remove(serial);
|
|
|
|
if (count) {
|
2012-01-21 00:22:14 +01:00
|
|
|
qLog(Debug) << "MTP device removed:" << serial;
|
2010-09-04 21:32:36 +02:00
|
|
|
emit DeviceRemoved(serial);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
void MacDeviceLister::FoundMTPDevice(const MTPDevice& device, const QString& serial) {
|
2012-01-21 00:22:14 +01:00
|
|
|
qLog(Debug) << "New MTP device detected!" << device.bus << device.address;
|
2010-09-02 23:20:27 +02:00
|
|
|
mtp_devices_[serial] = device;
|
2010-09-04 21:32:36 +02:00
|
|
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
|
|
|
MTPDevice* d = &mtp_devices_[serial];
|
|
|
|
d->capacity = GetCapacity(urls[0]);
|
|
|
|
d->free_space = GetFreeSpace(urls[0]);
|
2010-09-02 23:20:27 +02:00
|
|
|
emit DeviceAdded(serial);
|
|
|
|
}
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
bool IsMTPSerial(const QString& serial) { return serial.startsWith("MTP"); }
|
2010-09-02 23:20:27 +02:00
|
|
|
|
2011-08-11 21:37:09 +02:00
|
|
|
bool MacDeviceLister::IsCDDevice(const QString& serial) const {
|
|
|
|
return cd_devices_.contains(serial);
|
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString MacDeviceLister::MakeFriendlyName(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
const MTPDevice& device = mtp_devices_[serial];
|
|
|
|
if (device.vendor.isEmpty()) {
|
|
|
|
return device.product;
|
|
|
|
} else {
|
|
|
|
return device.vendor + " " + device.product;
|
|
|
|
}
|
|
|
|
}
|
2011-08-11 21:37:09 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
QString bsd_name = IsCDDevice(serial) ? *cd_devices_.find(serial) : current_devices_[serial];
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2011-08-11 21:37:09 +02:00
|
|
|
if (IsCDDevice(serial)) {
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> properties((NSDictionary*)DADiskCopyDescription(disk.get()));
|
|
|
|
NSString* device_name =
|
|
|
|
(NSString*)[properties.get() objectForKey:(NSString*)kDADiskDescriptionMediaNameKey];
|
2011-08-11 21:37:09 +02:00
|
|
|
|
|
|
|
return QString::fromUtf8([device_name UTF8String]);
|
|
|
|
}
|
|
|
|
|
2012-01-24 03:12:51 +01:00
|
|
|
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
2011-08-15 18:13:05 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
|
|
|
QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 21:02:49 +02:00
|
|
|
if (vendor.isEmpty()) {
|
|
|
|
return product;
|
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
return vendor + " " + product;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-08-01 19:38:58 +02:00
|
|
|
QList<QUrl> MacDeviceLister::MakeDeviceUrls(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
const MTPDevice& device = mtp_devices_[serial];
|
|
|
|
QString str;
|
2020-01-05 00:12:32 +01:00
|
|
|
str = QString::asprintf("gphoto2://usb-%d-%d/", device.bus, device.address);
|
2018-11-17 15:08:37 +01:00
|
|
|
QUrlQuery url_query;
|
|
|
|
url_query.addQueryItem("vendor", device.vendor);
|
|
|
|
url_query.addQueryItem("vendor_id", QString::number(device.vendor_id));
|
|
|
|
url_query.addQueryItem("product", device.product);
|
|
|
|
url_query.addQueryItem("product_id", QString::number(device.product_id));
|
|
|
|
url_query.addQueryItem("quirks", QString::number(device.quirks));
|
2010-09-02 23:20:27 +02:00
|
|
|
QUrl url(str);
|
2018-11-17 15:08:37 +01:00
|
|
|
url.setQuery(url_query);
|
2010-09-02 23:20:27 +02:00
|
|
|
return QList<QUrl>() << url;
|
|
|
|
}
|
|
|
|
|
2011-08-11 21:37:09 +02:00
|
|
|
if (IsCDDevice(serial)) {
|
2012-09-26 18:12:14 +02:00
|
|
|
return QList<QUrl>() << QUrl(QString("cdda:///dev/r" + serial));
|
2011-08-11 21:37:09 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString bsd_name = current_devices_[serial];
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> properties((NSDictionary*)DADiskCopyDescription(disk.get()));
|
|
|
|
scoped_nsobject<NSURL> volume_path(
|
|
|
|
[[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]);
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2012-02-21 13:10:25 +01:00
|
|
|
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
2010-07-24 21:02:49 +02:00
|
|
|
QUrl ret = MakeUrlFromLocalPath(path);
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-08-01 13:55:01 +02:00
|
|
|
return QList<QUrl>() << ret;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QStringList MacDeviceLister::DeviceUniqueIDs() {
|
2010-09-02 23:20:27 +02:00
|
|
|
return current_devices_.keys() + mtp_devices_.keys();
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-08-16 01:26:04 +02:00
|
|
|
QVariantList MacDeviceLister::DeviceIcons(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
return QVariantList();
|
|
|
|
}
|
2011-08-11 21:43:00 +02:00
|
|
|
|
|
|
|
if (IsCDDevice(serial)) {
|
|
|
|
return QVariantList() << "media-optical";
|
|
|
|
}
|
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
QString bsd_name = current_devices_[serial];
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2012-01-24 03:12:51 +01:00
|
|
|
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
|
|
|
|
QString icon = GetIconForDevice(device.get());
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> properties((NSDictionary*)DADiskCopyDescription(disk));
|
|
|
|
scoped_nsobject<NSURL> volume_path(
|
|
|
|
[[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]);
|
2010-07-26 13:01:36 +02:00
|
|
|
|
|
|
|
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
|
|
|
|
2010-08-16 01:26:04 +02:00
|
|
|
QVariantList ret;
|
2010-07-26 13:01:36 +02:00
|
|
|
ret << GuessIconForPath(path);
|
|
|
|
ret << GuessIconForModel(DeviceManufacturer(serial), DeviceModel(serial));
|
2010-07-24 20:34:22 +02:00
|
|
|
if (!icon.isEmpty()) {
|
|
|
|
ret << icon;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
return ret;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
QString MacDeviceLister::DeviceManufacturer(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
return mtp_devices_[serial].vendor;
|
|
|
|
}
|
2010-07-24 21:02:49 +02:00
|
|
|
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBVendorString));
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
QString MacDeviceLister::DeviceModel(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
return mtp_devices_[serial].product;
|
|
|
|
}
|
2010-07-24 21:02:49 +02:00
|
|
|
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBProductString));
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
quint64 MacDeviceLister::DeviceCapacity(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
2010-09-04 21:32:36 +02:00
|
|
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
|
|
|
return mtp_devices_[serial].capacity;
|
2010-09-02 23:20:27 +02:00
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
QString bsd_name = current_devices_[serial];
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
io_object_t device = DADiskCopyIOMedia(disk);
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-09-01 22:31:10 +02:00
|
|
|
NSNumber* capacity = (NSNumber*)GetPropertyForDevice(device, CFSTR("Size"));
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
quint64 ret = [capacity unsignedLongLongValue];
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
IOObjectRelease(device);
|
2010-07-23 15:46:30 +02:00
|
|
|
|
2010-07-24 20:34:22 +02:00
|
|
|
return ret;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 14:48:49 +01:00
|
|
|
quint64 MacDeviceLister::DeviceFreeSpace(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
2010-09-04 21:32:36 +02:00
|
|
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
|
|
|
return mtp_devices_[serial].free_space;
|
2010-09-02 23:20:27 +02:00
|
|
|
}
|
2010-07-24 20:34:22 +02:00
|
|
|
QString bsd_name = current_devices_[serial];
|
2012-01-21 01:06:56 +01:00
|
|
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
scoped_nsobject<NSDictionary> properties((NSDictionary*)DADiskCopyDescription(disk));
|
|
|
|
scoped_nsobject<NSURL> volume_path(
|
|
|
|
[[properties objectForKey:(NSString*)kDADiskDescriptionVolumePathKey] copy]);
|
2010-07-24 20:34:22 +02:00
|
|
|
|
2012-09-13 16:24:45 +02:00
|
|
|
NSNumber* value = nil;
|
|
|
|
NSError* error = nil;
|
2020-09-18 16:15:19 +02:00
|
|
|
if ([volume_path getResourceValue:&value forKey:NSURLVolumeAvailableCapacityKey error:&error] &&
|
2014-01-30 14:48:49 +01:00
|
|
|
value) {
|
2012-09-13 16:24:45 +02:00
|
|
|
return [value unsignedLongLongValue];
|
2010-07-24 20:34:22 +02:00
|
|
|
}
|
|
|
|
return 0;
|
2010-07-23 15:46:30 +02:00
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
QVariantMap MacDeviceLister::DeviceHardwareInfo(const QString& serial) { return QVariantMap(); }
|
2010-07-25 03:07:51 +02:00
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
bool MacDeviceLister::AskForScan(const QString& serial) const { return !IsCDDevice(serial); }
|
2011-08-11 22:10:14 +02:00
|
|
|
|
2010-07-25 03:29:22 +02:00
|
|
|
void MacDeviceLister::UnmountDevice(const QString& serial) {
|
2010-09-02 23:20:27 +02:00
|
|
|
if (IsMTPSerial(serial)) return;
|
|
|
|
|
2010-07-25 03:29:22 +02:00
|
|
|
QString bsd_name = current_devices_[serial];
|
2020-09-18 16:15:19 +02:00
|
|
|
ScopedCFTypeRef<DADiskRef> disk(
|
|
|
|
DADiskCreateFromBSDName(kCFAllocatorDefault, loop_session_, bsd_name.toLatin1().constData()));
|
2010-07-25 03:29:22 +02:00
|
|
|
|
|
|
|
DADiskUnmount(disk, kDADiskUnmountOptionDefault, &DiskUnmountCallback, this);
|
|
|
|
}
|
|
|
|
|
2020-09-18 16:15:19 +02:00
|
|
|
void MacDeviceLister::DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void* context) {
|
2010-07-25 03:29:22 +02:00
|
|
|
if (dissenter) {
|
2011-04-22 18:50:29 +02:00
|
|
|
qLog(Warning) << "Another app blocked the unmount";
|
2010-07-25 03:29:22 +02:00
|
|
|
} else {
|
|
|
|
DiskRemovedCallback(disk, context);
|
|
|
|
}
|
2010-07-25 03:07:51 +02:00
|
|
|
}
|
2010-08-14 20:37:16 +02:00
|
|
|
|
|
|
|
void MacDeviceLister::UpdateDeviceFreeSpace(const QString& serial) {
|
2010-09-04 21:32:36 +02:00
|
|
|
if (IsMTPSerial(serial)) {
|
|
|
|
if (mtp_devices_.contains(serial)) {
|
|
|
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
|
|
|
MTPDevice* d = &mtp_devices_[serial];
|
|
|
|
d->free_space = GetFreeSpace(urls[0]);
|
|
|
|
}
|
|
|
|
}
|
2010-08-14 20:37:16 +02:00
|
|
|
emit DeviceChanged(serial);
|
|
|
|
}
|