strawberry-audio-player-win.../src/device/macosdevicelister.mm

893 lines
29 KiB
Plaintext
Raw Normal View History

2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
2021-03-20 21:14:47 +01:00
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
2018-02-27 18:06:05 +01:00
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
#include <libmtp.h>
2023-08-27 13:54:23 +02:00
#include <AvailabilityMacros.h>
2018-02-27 18:06:05 +01:00
#include <CoreFoundation/CFRunLoop.h>
#include <DiskArbitration/DiskArbitration.h>
#include <IOKit/kext/KextManager.h>
#include <IOKit/IOCFPlugin.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/storage/IOMedia.h>
#include <IOKit/storage/IOCDMedia.h>
2021-06-20 19:04:08 +02:00
#include <QObject>
2018-02-27 18:06:05 +01:00
#include <QMutex>
#include <QString>
#include <QStringList>
2018-07-03 19:30:54 +02:00
#include <QUrlQuery>
#include <QUrl>
#include <QScopeGuard>
2018-02-27 18:06:05 +01:00
#include "config.h"
2019-01-02 00:32:36 +01:00
#include "macosdevicelister.h"
2018-02-27 18:06:05 +01:00
#include "mtpconnection.h"
#include "core/logging.h"
#include "core/scoped_cftyperef.h"
#include "core/scoped_nsautorelease_pool.h"
#include "core/scoped_nsobject.h"
#import <AppKit/NSWorkspace.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSString.h>
#import <Foundation/NSURL.h>
#ifndef kUSBSerialNumberString
#define kUSBSerialNumberString "USB Serial Number"
#endif
#ifndef kUSBVendorString
#define kUSBVendorString "USB Vendor Name"
#endif
#ifndef kUSBProductString
#define kUSBProductString "USB Product Name"
#endif
// io_object_t, io_service_t, io_iterator_t etc. are all typedef'd to unsigned int,
// hence the lack of templating here.
class ScopedIOObject {
public:
explicit ScopedIOObject(io_object_t object = 0) : object_(object) {}
~ScopedIOObject() {
if (object_) IOObjectRelease(object_);
}
io_object_t get() const { return object_; }
private:
io_object_t object_;
Q_DISABLE_COPY(ScopedIOObject);
};
// Helpful MTP & USB links:
// Apple USB device interface guide:
2018-11-16 17:25:39 +01:00
// http://developer.apple.com/mac/library/documentation/DeviceDrivers/Conceptual/USBBook/USBDeviceInterfaces/USBDevInterfaces.html
2018-02-27 18:06:05 +01:00
// 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
2019-01-02 00:32:36 +01:00
QSet<MacOsDeviceLister::MTPDevice> MacOsDeviceLister::sMTPDeviceList;
2018-02-27 18:06:05 +01:00
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2021-06-12 20:53:23 +02:00
size_t qHash(const MacOsDeviceLister::MTPDevice &d) {
#else
2021-06-12 20:53:23 +02:00
uint qHash(const MacOsDeviceLister::MTPDevice &d) {
#endif
2018-02-27 18:06:05 +01:00
return qHash(d.vendor_id) ^ qHash(d.product_id);
}
2021-06-20 19:04:08 +02:00
MacOsDeviceLister::MacOsDeviceLister(QObject *parent) : DeviceLister(parent) {}
2018-02-27 18:06:05 +01:00
2019-01-02 00:32:36 +01:00
MacOsDeviceLister::~MacOsDeviceLister() { CFRelease(loop_session_); }
2018-02-27 18:06:05 +01:00
2019-01-02 00:32:36 +01:00
bool MacOsDeviceLister::Init() {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
ScopedNSAutoreleasePool pool;
// Populate MTP Device list.
if (sMTPDeviceList.empty()) {
2021-06-12 20:53:23 +02:00
LIBMTP_device_entry_t *devices = nullptr;
2018-02-27 18:06:05 +01:00
int num = 0;
if (LIBMTP_Get_Supported_Devices_List(&devices, &num) != 0) {
qLog(Warning) << "Failed to get MTP device list";
}
else {
for (int i = 0; i < num; ++i) {
LIBMTP_device_entry_t device = devices[i];
MTPDevice d;
2018-11-16 17:25:39 +01:00
d.vendor = QString::fromLatin1(device.vendor);
2018-02-27 18:06:05 +01:00
d.vendor_id = device.vendor_id;
2018-11-16 17:25:39 +01:00
d.product = QString::fromLatin1(device.product);
2018-02-27 18:06:05 +01:00
d.product_id = device.product_id;
d.quirks = device.device_flags;
sMTPDeviceList << d;
}
}
MTPDevice d;
d.vendor = QStringLiteral("SanDisk");
2018-02-27 18:06:05 +01:00
d.vendor_id = 0x781;
d.product = QStringLiteral("Sansa Clip+");
2018-02-27 18:06:05 +01:00
d.product_id = 0x74d0;
d.quirks = 0x2 | 0x4 | 0x40 | 0x4000;
sMTPDeviceList << d;
}
run_loop_ = CFRunLoopGetCurrent();
// Register for disk mounts/unmounts.
loop_session_ = DASessionCreate(kCFAllocatorDefault);
DARegisterDiskAppearedCallback(loop_session_, kDADiskDescriptionMatchVolumeMountable, &DiskAddedCallback, reinterpret_cast<void*>(this));
DARegisterDiskDisappearedCallback(loop_session_, nullptr, &DiskRemovedCallback, reinterpret_cast<void*>(this));
DASessionScheduleWithRunLoop(loop_session_, run_loop_, kCFRunLoopDefaultMode);
// Register for USB device connection/disconnection.
2023-08-27 13:54:23 +02:00
IONotificationPortRef notification_port = IONotificationPortCreate(
#if defined(MAC_OS_VERSION_12_0) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0)
kIOMainPortDefault
#else
kIOMasterPortDefault
#endif
);
2018-02-27 18:06:05 +01:00
CFMutableDictionaryRef matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
// IOServiceAddMatchingNotification decreases reference count.
CFRetain(matching_dict);
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);
2018-07-03 19:30:54 +02:00
}
else {
2018-02-27 18:06:05 +01:00
qLog(Warning) << "Could not add notification on USB device connection";
}
err = IOServiceAddMatchingNotification(
notification_port,
kIOTerminatedNotification,
matching_dict,
&USBDeviceRemovedCallback,
reinterpret_cast<void*>(this),
&it);
if (err == KERN_SUCCESS) {
USBDeviceRemovedCallback(this, it);
2018-07-03 19:30:54 +02:00
}
else {
2018-02-27 18:06:05 +01:00
qLog(Warning) << "Could not add notification USB device removal";
}
CFRunLoopSourceRef io_source = IONotificationPortGetRunLoopSource(notification_port);
CFRunLoopAddSource(run_loop_, io_source, kCFRunLoopDefaultMode);
CFRunLoopRun();
2019-01-02 00:32:36 +01:00
return true;
2019-09-15 01:12:05 +02:00
}
void MacOsDeviceLister::ExitAsync() {
emit ExitFinished();
2018-02-27 18:06:05 +01:00
}
2019-01-02 00:32:36 +01:00
void MacOsDeviceLister::ShutDown() { CFRunLoopStop(run_loop_); }
2018-02-27 18:06:05 +01:00
// IOKit helpers.
namespace {
// Caller is responsible for calling CFRelease().
CFTypeRef GetUSBRegistryEntry(io_object_t device, CFStringRef key) {
io_iterator_t it;
if (IORegistryEntryGetParentIterator(device, kIOServicePlane, &it) == KERN_SUCCESS) {
io_object_t next;
while ((next = IOIteratorNext(it))) {
2022-06-10 02:30:39 +02:00
CFTypeRef registry_entry = reinterpret_cast<CFStringRef>(IORegistryEntryCreateCFProperty(next, key, kCFAllocatorDefault, 0));
2018-02-27 18:06:05 +01:00
if (registry_entry) {
IOObjectRelease(next);
IOObjectRelease(it);
return registry_entry;
}
CFTypeRef ret = GetUSBRegistryEntry(next, key);
if (ret) {
IOObjectRelease(next);
IOObjectRelease(it);
return ret;
}
IOObjectRelease(next);
}
}
IOObjectRelease(it);
return nullptr;
}
QString GetUSBRegistryEntryString(io_object_t device, CFStringRef key) {
2022-06-10 02:30:39 +02:00
ScopedCFTypeRef<CFStringRef> registry_string(reinterpret_cast<CFStringRef>(GetUSBRegistryEntry(device, key)));
2018-02-27 18:06:05 +01:00
if (registry_string) {
2022-06-10 02:30:39 +02:00
return QString::fromUtf8([reinterpret_cast<NSString*>(registry_string.get()) UTF8String]);
2018-02-27 18:06:05 +01:00
}
return QString();
}
2021-06-12 20:53:23 +02:00
NSObject *GetPropertyForDevice(io_object_t device, CFStringRef key) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
CFMutableDictionaryRef properties;
kern_return_t ret = IORegistryEntryCreateCFProperties(device, &properties, kCFAllocatorDefault, 0);
if (ret != KERN_SUCCESS) {
return nil;
}
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> dict(reinterpret_cast<NSDictionary*>(properties)); // Takes ownership.
NSObject *prop = [dict objectForKey:reinterpret_cast<NSString*>(key)];
2018-02-27 18:06:05 +01:00
if (prop) {
// The dictionary goes out of scope so we should retain this object.
[prop retain];
return prop;
}
io_object_t parent;
ret = IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent);
if (ret == KERN_SUCCESS) {
return GetPropertyForDevice(parent, key);
}
return nil;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
int GetUSBDeviceClass(io_object_t device) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
ScopedCFTypeRef<CFTypeRef> interface_class(IORegistryEntrySearchCFProperty(
device,
kIOServicePlane,
CFSTR(kUSBInterfaceClass),
kCFAllocatorDefault,
kIORegistryIterateRecursively));
2022-06-10 02:30:39 +02:00
NSNumber *number = reinterpret_cast<NSNumber*>(interface_class.get());
2018-02-27 18:06:05 +01:00
if (number) {
int ret = [number unsignedShortValue];
return ret;
}
return 0;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
QString GetIconForDevice(io_object_t device) {
2019-09-15 01:12:05 +02:00
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> media_icon(reinterpret_cast<NSDictionary*>(GetPropertyForDevice(device, CFSTR("IOMediaIcon"))));
2018-02-27 18:06:05 +01:00
if (media_icon) {
2022-06-10 02:30:39 +02:00
NSString *bundle = reinterpret_cast<NSString*>([media_icon objectForKey:@"CFBundleIdentifier"]);
NSString *file = reinterpret_cast<NSString*>([media_icon objectForKey:@"IOBundleResourceFile"]);
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSURL> bundle_url(reinterpret_cast<NSURL*>(KextManagerCreateURLForBundleIdentifier(kCFAllocatorDefault, reinterpret_cast<CFStringRef>(bundle))));
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
QString path = QString::fromUtf8([[bundle_url path] UTF8String]);
path += QStringLiteral("/Contents/Resources/");
2018-02-27 18:06:05 +01:00
path += QString::fromUtf8([file UTF8String]);
return path;
}
return QString();
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
QString GetSerialForDevice(io_object_t device) {
2019-09-15 01:12:05 +02:00
const QString serial = GetUSBRegistryEntryString(device, CFSTR(kUSBSerialNumberString));
2018-02-27 18:06:05 +01:00
if (!serial.isEmpty()) {
return QStringLiteral("USB/") + serial;
2018-02-27 18:06:05 +01:00
}
2018-02-27 18:06:05 +01:00
return QString();
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
QString GetSerialForMTPDevice(io_object_t device) {
2019-09-15 01:12:05 +02:00
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSString> serial(reinterpret_cast<NSString*>(GetPropertyForDevice(device, CFSTR(kUSBSerialNumberString))));
return QString(QStringLiteral("MTP/") + QString::fromUtf8([serial UTF8String]));
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
QString FindDeviceProperty(const QString &bsd_name, CFStringRef property) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
2018-07-03 19:30:54 +02:00
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
2018-02-27 18:06:05 +01:00
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
QString ret = GetUSBRegistryEntryString(device.get(), property);
2018-02-27 18:06:05 +01:00
return ret;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 01:12:05 +02:00
} // namespace
2018-02-27 18:06:05 +01:00
2021-06-12 20:53:23 +02:00
quint64 MacOsDeviceLister::GetFreeSpace(const QUrl &url) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
QMutexLocker l(&libmtp_mutex_);
MtpConnection connection(url);
if (!connection.is_valid()) {
qLog(Warning) << "Error connecting to MTP device, couldn't get device free space";
return -1;
}
2021-06-12 20:53:23 +02:00
LIBMTP_devicestorage_t *storage = connection.device()->storage;
2018-02-27 18:06:05 +01:00
quint64 free_bytes = 0;
while (storage) {
free_bytes += storage->FreeSpaceInBytes;
storage = storage->next;
}
2018-02-27 18:06:05 +01:00
return free_bytes;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
quint64 MacOsDeviceLister::GetCapacity(const QUrl &url) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
QMutexLocker l(&libmtp_mutex_);
MtpConnection connection(url);
if (!connection.is_valid()) {
qLog(Warning) << "Error connecting to MTP device, couldn't get device capacity";
return -1;
}
2021-06-12 20:53:23 +02:00
LIBMTP_devicestorage_t *storage = connection.device()->storage;
2018-02-27 18:06:05 +01:00
quint64 capacity_bytes = 0;
while (storage) {
capacity_bytes += storage->MaxCapacity;
storage = storage->next;
}
return capacity_bytes;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
2019-09-15 01:12:05 +02:00
2021-06-12 20:53:23 +02:00
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk)));
2018-02-27 18:06:05 +01:00
#ifdef HAVE_AUDIOCD
NSString *kind = [properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionMediaKindKey)];
2018-02-27 18:06:05 +01:00
if (kind && strcmp([kind UTF8String], kIOCDMediaClass) == 0) {
// CD inserted.
2018-11-16 17:25:39 +01:00
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
2018-02-27 18:06:05 +01:00
me->cd_devices_ << bsd_name;
emit me->DeviceAdded(bsd_name);
return;
}
#endif
2022-06-10 02:30:39 +02:00
NSURL *volume_path = [[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy];
2018-02-27 18:06:05 +01:00
if (volume_path) {
ScopedIOObject device(DADiskCopyIOMedia(disk));
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(device.get()));
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));
CFMutableDictionaryRef cf_properties;
2018-07-03 19:30:54 +02:00
kern_return_t ret = IORegistryEntryCreateCFProperties(device.get(), &cf_properties, kCFAllocatorDefault, 0);
2018-02-27 18:06:05 +01:00
if (ret == KERN_SUCCESS) {
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> dict(reinterpret_cast<NSDictionary*>(cf_properties)); // Takes ownership.
if ([[dict objectForKey:@"Removable"] intValue] == 1) {
2018-02-27 18:06:05 +01:00
QString serial = GetSerialForDevice(device.get());
if (!serial.isEmpty()) {
me->current_devices_[serial] = QString::fromLatin1(DADiskGetBSDName(disk));
2018-02-27 18:06:05 +01:00
emit me->DeviceAdded(serial);
}
}
}
}
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::DiskRemovedCallback(DADiskRef disk, void *context) {
2019-09-15 01:12:05 +02:00
2021-06-12 20:53:23 +02:00
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
2018-02-27 18:06:05 +01:00
// We cannot access the USB tree when the disk is removed but we still get
// the BSD disk name.
2018-11-16 17:25:39 +01:00
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
2018-02-27 18:06:05 +01:00
if (me->cd_devices_.remove(bsd_name)) {
emit me->DeviceRemoved(bsd_name);
return;
}
2018-07-03 19:30:54 +02:00
for (QMap<QString, QString>::iterator it = me->current_devices_.begin(); it != me->current_devices_.end(); ++it) {
2018-02-27 18:06:05 +01:00
if (it.value() == bsd_name) {
emit me->DeviceRemoved(it.key());
me->current_devices_.erase(it);
break;
}
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
bool DeviceRequest(IOUSBDeviceInterface **dev,
2018-02-27 18:06:05 +01:00
quint8 direction,
quint8 type,
quint8 recipient,
quint8 request_code,
quint16 value,
quint16 index,
quint16 length,
2021-06-12 20:53:23 +02:00
QByteArray *data) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01: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);
2018-02-27 18:06:05 +01:00
return true;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
int GetBusNumber(io_object_t o) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
io_iterator_t it;
kern_return_t err = IORegistryEntryGetParentIterator(o, kIOServicePlane, &it);
if (err != KERN_SUCCESS) {
return -1;
}
while ((o = IOIteratorNext(it))) {
2021-06-12 20:53:23 +02:00
NSObject *bus = GetPropertyForDevice(o, CFSTR("USBBusNumber"));
2018-02-27 18:06:05 +01:00
if (bus) {
2022-06-10 02:30:39 +02:00
NSNumber *bus_num = reinterpret_cast<NSNumber*>(bus);
2018-02-27 18:06:05 +01:00
return [bus_num intValue];
}
}
return -1;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
2019-09-15 01:12:05 +02:00
2021-06-12 20:53:23 +02:00
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
2018-02-27 18:06:05 +01:00
io_object_t object;
while ((object = IOIteratorNext(it))) {
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
2018-02-27 18:06:05 +01:00
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
2022-06-10 02:30:39 +02:00
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
2018-02-27 18:06:05 +01:00
int interface_class = GetUSBDeviceClass(object);
qLog(Debug) << "Interface class:" << interface_class;
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];
device.quirks = 0;
device.bus = -1;
device.address = -1;
if (device.vendor_id == kAppleVendorID || // I think we can safely skip Apple products.
// Blacklist ilok2 as this probe may be breaking it.
(device.vendor_id == 0x088e && device.product_id == 0x5036) ||
// Blacklist eLicenser
(device.vendor_id == 0x0819 && device.product_id == 0x0101) ||
// Skip HID devices, printers and hubs.
interface_class == kUSBHIDInterfaceClass ||
interface_class == kUSBPrintingInterfaceClass ||
interface_class == kUSBHubClass) {
continue;
}
2022-06-10 02:30:39 +02:00
NSNumber *addr = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR("USB Address")));
2018-02-27 18:06:05 +01:00
int bus = GetBusNumber(object);
if (!addr || bus == -1) {
// Failed to get bus or address number.
continue;
}
device.bus = bus;
device.address = [addr intValue];
// First check the libmtp device list.
2020-07-13 18:11:57 +02:00
QSet<MTPDevice>::const_iterator it2 = sMTPDeviceList.find(device);
if (it2 != sMTPDeviceList.end()) {
2018-02-27 18:06:05 +01:00
// Fill in quirks flags from libmtp.
2020-07-13 18:11:57 +02:00
device.quirks = it2->quirks;
2018-02-27 18:06:05 +01:00
me->FoundMTPDevice(device, GetSerialForMTPDevice(object));
continue;
}
2021-06-12 20:53:23 +02:00
IOCFPlugInInterface **plugin_interface = nullptr;
2018-02-27 18:06:05 +01:00
SInt32 score;
kern_return_t err = IOCreatePlugInInterfaceForService(
object,
kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugin_interface,
&score);
if (err != KERN_SUCCESS) {
continue;
}
2021-06-12 20:53:23 +02:00
IOUSBDeviceInterface **dev = nullptr;
2022-06-10 02:30:39 +02:00
HRESULT result = (*plugin_interface)->QueryInterface(plugin_interface, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), reinterpret_cast<LPVOID*>(&dev));
2018-02-27 18:06:05 +01:00
(*plugin_interface)->Release(plugin_interface);
if (result || !dev) {
continue;
}
err = (*dev)->USBDeviceOpen(dev);
if (err != kIOReturnSuccess) {
continue;
}
// Automatically close & release usb device at scope exit.
const QScopeGuard dev_close_release = qScopeGuard([dev]() {
2018-02-27 18:06:05 +01:00
(*dev)->USBDeviceClose(dev);
(*dev)->Release(dev);
});
2018-02-27 18:06:05 +01:00
// Request the string descriptor at 0xee.
// This is a magic string that indicates whether this device supports MTP.
QByteArray data;
bool ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, 2, &data);
if (!ret) continue;
UInt8 string_len = data[0];
ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, string_len, &data);
if (!ret) continue;
// The device actually returned something. That's a good sign.
// Because this was designed by MS, the characters are in UTF-16 (LE?).
2020-12-28 20:33:09 +01:00
QString str = QString::fromUtf16(reinterpret_cast<char16_t*>(data.data() + 2), (data.size() / 2) - 2);
2018-02-27 18:06:05 +01:00
if (str.startsWith(QStringLiteral("MSFT100"))) {
2018-02-27 18:06:05 +01:00
// We got the OS descriptor!
char vendor_code = data[16];
ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 4, 256, &data);
if (!ret || data.at(0) != 0x28)
continue;
if (QString::fromLatin1(data.data() + 0x12, 3) != QStringLiteral("MTP")) {
2018-02-27 18:06:05 +01:00
// Not quite.
continue;
}
ret = DeviceRequest(dev, kUSBIn, kUSBVendor, kUSBDevice, vendor_code, 0, 5, 256, &data);
if (!ret || data.at(0) != 0x28) {
continue;
}
if (QString::fromLatin1(data.data() + 0x12, 3) != QStringLiteral("MTP")) {
2018-02-27 18:06:05 +01:00
// Not quite.
continue;
}
// Hurray! We made it!
me->FoundMTPDevice(device, serial);
}
}
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::USBDeviceRemovedCallback(void *refcon, io_iterator_t it) {
2019-09-15 01:12:05 +02:00
2021-06-12 20:53:23 +02:00
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
2018-02-27 18:06:05 +01:00
io_object_t object;
while ((object = IOIteratorNext(it))) {
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
2018-02-27 18:06:05 +01:00
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
2022-06-10 02:30:39 +02:00
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
2018-02-27 18:06:05 +01: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];
me->RemovedMTPDevice(serial);
}
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::RemovedMTPDevice(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
int count = mtp_devices_.remove(serial);
if (count) {
qLog(Debug) << "MTP device removed:" << serial;
emit DeviceRemoved(serial);
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::FoundMTPDevice(const MTPDevice &device, const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
qLog(Debug) << "New MTP device detected!" << device.bus << device.address;
mtp_devices_[serial] = device;
QList<QUrl> urls = MakeDeviceUrls(serial);
2021-06-12 20:53:23 +02:00
MTPDevice *d = &mtp_devices_[serial];
2018-02-27 18:06:05 +01:00
d->capacity = GetCapacity(urls[0]);
d->free_space = GetFreeSpace(urls[0]);
emit DeviceAdded(serial);
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
bool IsMTPSerial(const QString &serial) { return serial.startsWith(QStringLiteral("MTP")); }
2018-02-27 18:06:05 +01:00
2021-06-12 20:53:23 +02:00
bool MacOsDeviceLister::IsCDDevice(const QString &serial) const {
2018-02-27 18:06:05 +01:00
return cd_devices_.contains(serial);
}
2021-06-12 20:53:23 +02:00
QString MacOsDeviceLister::MakeFriendlyName(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
2021-06-12 20:53:23 +02:00
const MTPDevice &device = mtp_devices_[serial];
2018-02-27 18:06:05 +01:00
if (device.vendor.isEmpty()) {
return device.product;
2018-07-03 19:30:54 +02:00
}
else {
return device.vendor + QLatin1Char(' ') + device.product;
2018-02-27 18:06:05 +01:00
}
}
QString bsd_name = IsCDDevice(serial) ? *cd_devices_.find(serial) : current_devices_[serial];
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
if (IsCDDevice(serial)) {
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk.get())));
NSString *device_name = reinterpret_cast<NSString*>([properties.get() objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionMediaNameKey)]);
2018-02-27 18:06:05 +01:00
return QString::fromUtf8([device_name UTF8String]);
}
ScopedIOObject device(DADiskCopyIOMedia(disk));
QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
if (vendor.isEmpty()) {
return product;
}
return vendor + QLatin1Char(' ') + product;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
QList<QUrl> MacOsDeviceLister::MakeDeviceUrls(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
2021-06-12 20:53:23 +02:00
const MTPDevice &device = mtp_devices_[serial];
2019-12-21 21:56:48 +01:00
QString str = QString::asprintf("gphoto2://usb-%d-%d/", device.bus, device.address);
2018-11-16 17:25:39 +01:00
QUrlQuery url_query;
url_query.addQueryItem(QStringLiteral("vendor"), device.vendor);
url_query.addQueryItem(QStringLiteral("vendor_id"), QString::number(device.vendor_id));
url_query.addQueryItem(QStringLiteral("product"), device.product);
url_query.addQueryItem(QStringLiteral("product_id"), QString::number(device.product_id));
url_query.addQueryItem(QStringLiteral("quirks"), QString::number(device.quirks));
2018-02-27 18:06:05 +01:00
QUrl url(str);
2018-11-16 17:25:39 +01:00
url.setQuery(url_query);
2018-02-27 18:06:05 +01:00
return QList<QUrl>() << url;
}
if (IsCDDevice(serial)) {
return QList<QUrl>() << QUrl(QStringLiteral("cdda:///dev/r") + serial);
2018-02-27 18:06:05 +01:00
}
QString bsd_name = current_devices_[serial];
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
2018-07-03 19:30:54 +02:00
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk.get())));
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
2018-02-27 18:06:05 +01:00
QUrl ret = MakeUrlFromLocalPath(path);
return QList<QUrl>() << ret;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2019-01-02 00:32:36 +01:00
QStringList MacOsDeviceLister::DeviceUniqueIDs() {
2018-02-27 18:06:05 +01:00
return current_devices_.keys() + mtp_devices_.keys();
}
2021-06-12 20:53:23 +02:00
QVariantList MacOsDeviceLister::DeviceIcons(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
return QVariantList();
}
if (IsCDDevice(serial)) {
return QVariantList() << QStringLiteral("media-optical");
2018-02-27 18:06:05 +01:00
}
QString bsd_name = current_devices_[serial];
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
2018-07-03 19:30:54 +02:00
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
2018-02-27 18:06:05 +01:00
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
QString icon = GetIconForDevice(device.get());
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk)));
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
2018-02-27 18:06:05 +01:00
2022-06-10 02:30:39 +02:00
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
2018-02-27 18:06:05 +01:00
QVariantList ret;
ret << GuessIconForPath(path);
ret << GuessIconForModel(DeviceManufacturer(serial), DeviceModel(serial));
if (!icon.isEmpty()) {
ret << icon;
}
return ret;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
QString MacOsDeviceLister::DeviceManufacturer(const QString &serial) {
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
return mtp_devices_[serial].vendor;
}
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBVendorString));
}
2021-06-12 20:53:23 +02:00
QString MacOsDeviceLister::DeviceModel(const QString &serial) {
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
return mtp_devices_[serial].product;
}
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBProductString));
}
2021-06-12 20:53:23 +02:00
quint64 MacOsDeviceLister::DeviceCapacity(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
QList<QUrl> urls = MakeDeviceUrls(serial);
return mtp_devices_[serial].capacity;
}
QString bsd_name = current_devices_[serial];
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
io_object_t device = DADiskCopyIOMedia(disk);
2022-06-10 02:30:39 +02:00
NSNumber *capacity = reinterpret_cast<NSNumber*>(GetPropertyForDevice(device, CFSTR("Size")));
2018-02-27 18:06:05 +01:00
quint64 ret = [capacity unsignedLongLongValue];
IOObjectRelease(device);
return ret;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
quint64 MacOsDeviceLister::DeviceFreeSpace(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
QList<QUrl> urls = MakeDeviceUrls(serial);
return mtp_devices_[serial].free_space;
}
QString bsd_name = current_devices_[serial];
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
2022-06-10 02:30:39 +02:00
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk)));
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
2018-02-27 18:06:05 +01:00
2021-06-12 20:53:23 +02:00
NSNumber *value = nil;
NSError *error = nil;
2018-02-27 18:06:05 +01:00
if ([volume_path getResourceValue:&value forKey: NSURLVolumeAvailableCapacityKey error: &error] && value) {
return [value unsignedLongLongValue];
}
return 0;
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
QVariantMap MacOsDeviceLister::DeviceHardwareInfo(const QString &serial){
2020-07-17 16:36:24 +02:00
Q_UNUSED(serial);
return QVariantMap();
}
2018-02-27 18:06:05 +01:00
2021-06-12 20:53:23 +02:00
bool MacOsDeviceLister::AskForScan(const QString &serial) const {
2018-02-27 18:06:05 +01:00
return !IsCDDevice(serial);
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::UnmountDevice(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) return;
QString bsd_name = current_devices_[serial];
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, loop_session_, bsd_name.toLatin1().constData()));
DADiskUnmount(disk, kDADiskUnmountOptionDefault, &DiskUnmountCallback, this);
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void *context) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (dissenter) {
qLog(Warning) << "Another app blocked the unmount";
}
else {
DiskRemovedCallback(disk, context);
}
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}
2021-06-12 20:53:23 +02:00
void MacOsDeviceLister::UpdateDeviceFreeSpace(const QString &serial) {
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
if (IsMTPSerial(serial)) {
if (mtp_devices_.contains(serial)) {
QList<QUrl> urls = MakeDeviceUrls(serial);
2021-06-12 20:53:23 +02:00
MTPDevice *d = &mtp_devices_[serial];
2018-02-27 18:06:05 +01:00
d->free_space = GetFreeSpace(urls[0]);
}
}
emit DeviceChanged(serial);
2019-09-15 01:12:05 +02:00
2018-02-27 18:06:05 +01:00
}