Files
qt-creator/src/plugins/ios/iosdevice.cpp
hjk f187ddd590 iOS: Put common IosDevice constructor code into a function
It is not even clear that the remaining difference are needed.

Change-Id: Ic4418143242793d82a1f4c4c368af69a2e537078
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2019-08-19 13:51:56 +00:00

540 lines
20 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "iosdevice.h"
#include "iossimulator.h"
#include "iosconstants.h"
#include "iosconfigurations.h"
#include "iostoolhandler.h"
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/kitinformation.h>
#include <coreplugin/helpmanager.h>
#include <utils/portlist.h>
#include <QCoreApplication>
#include <QVariant>
#include <QVariantMap>
#include <QMessageBox>
#ifdef Q_OS_MAC
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
#include <exception>
using namespace ProjectExplorer;
namespace {
Q_LOGGING_CATEGORY(detectLog, "qtc.ios.deviceDetect", QtWarningMsg)
}
#ifdef Q_OS_MAC
static QString CFStringRef2QString(CFStringRef s)
{
unsigned char buf[250];
CFIndex len = CFStringGetLength(s);
CFIndex usedBufLen;
CFIndex converted = CFStringGetBytes(s, CFRangeMake(0,len), kCFStringEncodingUTF8,
'?', false, &buf[0], sizeof(buf), &usedBufLen);
if (converted == len)
return QString::fromUtf8(reinterpret_cast<char *>(&buf[0]), usedBufLen);
size_t bufSize = sizeof(buf)
+ CFStringGetMaximumSizeForEncoding(len - converted, kCFStringEncodingUTF8);
unsigned char *bigBuf = new unsigned char[bufSize];
memcpy(bigBuf, buf, usedBufLen);
CFIndex newUseBufLen;
CFStringGetBytes(s, CFRangeMake(converted,len), kCFStringEncodingUTF8,
'?', false, &bigBuf[usedBufLen], bufSize, &newUseBufLen);
QString res = QString::fromUtf8(reinterpret_cast<char *>(bigBuf), usedBufLen + newUseBufLen);
delete[] bigBuf;
return res;
}
#endif
namespace Ios {
namespace Internal {
IosDevice::IosDevice(CtorHelper)
: m_lastPort(Constants::IOS_DEVICE_PORT_START)
{
setType(Constants::IOS_DEVICE_TYPE);
setDefaultDisplayName(IosDevice::name());
setDisplayType(QCoreApplication::translate("Ios::Internal::IosDevice", "iOS"));
setMachineType(IDevice::Hardware);
setOsType(Utils::OsTypeMac);
setDeviceState(DeviceDisconnected);
}
IosDevice::IosDevice()
: IosDevice(CtorHelper{})
{
setupId(IDevice::AutoDetected, Constants::IOS_DEVICE_ID);
Utils::PortList ports;
ports.addRange(Utils::Port(Constants::IOS_DEVICE_PORT_START),
Utils::Port(Constants::IOS_DEVICE_PORT_END));
setFreePorts(ports);
}
IosDevice::IosDevice(const QString &uid)
: IosDevice(CtorHelper{})
{
setupId(IDevice::AutoDetected, Core::Id(Constants::IOS_DEVICE_ID).withSuffix(uid));
}
IDevice::DeviceInfo IosDevice::deviceInformation() const
{
IDevice::DeviceInfo res;
for (auto i = m_extraInfo.cbegin(), end = m_extraInfo.cend(); i != end; ++i) {
IosDeviceManager::TranslationMap tMap = IosDeviceManager::translationMap();
if (tMap.contains(i.key()))
res.append(DeviceInfoItem(tMap.value(i.key()), tMap.value(i.value(), i.value())));
}
return res;
}
IDeviceWidget *IosDevice::createWidget()
{
return nullptr;
}
DeviceProcessSignalOperation::Ptr IosDevice::signalOperation() const
{
return DeviceProcessSignalOperation::Ptr();
}
void IosDevice::fromMap(const QVariantMap &map)
{
IDevice::fromMap(map);
m_extraInfo.clear();
const QVariantMap vMap = map.value(QLatin1String(Constants::EXTRA_INFO_KEY)).toMap();
for (auto i = vMap.cbegin(), end = vMap.cend(); i != end; ++i)
m_extraInfo.insert(i.key(), i.value().toString());
}
QVariantMap IosDevice::toMap() const
{
QVariantMap res = IDevice::toMap();
QVariantMap vMap;
for (auto i = m_extraInfo.cbegin(), end = m_extraInfo.cend(); i != end; ++i)
vMap.insert(i.key(), i.value());
res.insert(QLatin1String(Constants::EXTRA_INFO_KEY), vMap);
return res;
}
QString IosDevice::uniqueDeviceID() const
{
return id().suffixAfter(Core::Id(Constants::IOS_DEVICE_ID));
}
QString IosDevice::name()
{
return QCoreApplication::translate("Ios::Internal::IosDevice", "iOS Device");
}
QString IosDevice::osVersion() const
{
return m_extraInfo.value(QLatin1String("osVersion"));
}
Utils::Port IosDevice::nextPort() const
{
// use qrand instead?
if (++m_lastPort >= Constants::IOS_DEVICE_PORT_END)
m_lastPort = Constants::IOS_DEVICE_PORT_START;
return Utils::Port(m_lastPort);
}
bool IosDevice::canAutoDetectPorts() const
{
return true;
}
// IosDeviceManager
IosDeviceManager::TranslationMap IosDeviceManager::translationMap()
{
static TranslationMap *translationMap = nullptr;
if (translationMap)
return *translationMap;
TranslationMap &tMap = *new TranslationMap;
tMap[QLatin1String("deviceName")] = tr("Device name");
//: Whether the device is in developer mode.
tMap[QLatin1String("developerStatus")] = tr("Developer status");
tMap[QLatin1String("deviceConnected")] = tr("Connected");
tMap[QLatin1String("YES")] = tr("yes");
tMap[QLatin1String("NO")] = tr("no");
tMap[QLatin1String("YES")] = tr("yes");
tMap[QLatin1String("*unknown*")] = tr("unknown");
tMap[QLatin1String("osVersion")] = tr("OS version");
translationMap = &tMap;
return tMap;
}
void IosDeviceManager::deviceConnected(const QString &uid, const QString &name)
{
DeviceManager *devManager = DeviceManager::instance();
Core::Id baseDevId(Constants::IOS_DEVICE_ID);
Core::Id devType(Constants::IOS_DEVICE_TYPE);
Core::Id devId = baseDevId.withSuffix(uid);
IDevice::ConstPtr dev = devManager->find(devId);
if (dev.isNull()) {
auto newDev = new IosDevice(uid);
if (!name.isNull())
newDev->setDisplayName(name);
qCDebug(detectLog) << "adding ios device " << uid;
devManager->addDevice(IDevice::ConstPtr(newDev));
} else if (dev->deviceState() != IDevice::DeviceConnected &&
dev->deviceState() != IDevice::DeviceReadyToUse) {
qCDebug(detectLog) << "updating ios device " << uid;
if (dev->type() == devType) // FIXME: Should that be a QTC_ASSERT?
devManager->addDevice(dev->clone());
else
devManager->addDevice(IDevice::ConstPtr(new IosDevice(uid)));
}
updateInfo(uid);
}
void IosDeviceManager::deviceDisconnected(const QString &uid)
{
qCDebug(detectLog) << "detected disconnection of ios device " << uid;
DeviceManager *devManager = DeviceManager::instance();
Core::Id baseDevId(Constants::IOS_DEVICE_ID);
Core::Id devType(Constants::IOS_DEVICE_TYPE);
Core::Id devId = baseDevId.withSuffix(uid);
IDevice::ConstPtr dev = devManager->find(devId);
if (dev.isNull() || dev->type() != devType) {
qCWarning(detectLog) << "ignoring disconnection of ios device " << uid; // should neve happen
} else {
auto iosDev = static_cast<const IosDevice *>(dev.data());
if (iosDev->m_extraInfo.isEmpty()
|| iosDev->m_extraInfo.value(QLatin1String("deviceName")) == QLatin1String("*unknown*")) {
devManager->removeDevice(iosDev->id());
} else if (iosDev->deviceState() != IDevice::DeviceDisconnected) {
qCDebug(detectLog) << "disconnecting device " << iosDev->uniqueDeviceID();
devManager->setDeviceState(iosDev->id(), IDevice::DeviceDisconnected);
}
}
}
void IosDeviceManager::updateInfo(const QString &devId)
{
IosToolHandler *requester = new IosToolHandler(IosDeviceType(IosDeviceType::IosDevice), this);
connect(requester, &IosToolHandler::deviceInfo,
this, &IosDeviceManager::deviceInfo, Qt::QueuedConnection);
connect(requester, &IosToolHandler::finished,
this, &IosDeviceManager::infoGathererFinished);
requester->requestDeviceInfo(devId);
}
void IosDeviceManager::deviceInfo(IosToolHandler *, const QString &uid,
const Ios::IosToolHandler::Dict &info)
{
DeviceManager *devManager = DeviceManager::instance();
Core::Id baseDevId(Constants::IOS_DEVICE_ID);
Core::Id devType(Constants::IOS_DEVICE_TYPE);
Core::Id devId = baseDevId.withSuffix(uid);
IDevice::ConstPtr dev = devManager->find(devId);
bool skipUpdate = false;
IosDevice *newDev = nullptr;
if (!dev.isNull() && dev->type() == devType) {
auto iosDev = static_cast<const IosDevice *>(dev.data());
if (iosDev->m_extraInfo == info) {
skipUpdate = true;
newDev = const_cast<IosDevice *>(iosDev);
} else {
newDev = new IosDevice();
newDev->fromMap(iosDev->toMap());
}
} else {
newDev = new IosDevice(uid);
}
if (!skipUpdate) {
QString devNameKey = QLatin1String("deviceName");
if (info.contains(devNameKey))
newDev->setDisplayName(info.value(devNameKey));
newDev->m_extraInfo = info;
qCDebug(detectLog) << "updated info of ios device " << uid;
dev = IDevice::ConstPtr(newDev);
devManager->addDevice(dev);
}
QLatin1String devStatusKey = QLatin1String("developerStatus");
if (info.contains(devStatusKey)) {
QString devStatus = info.value(devStatusKey);
if (devStatus == QLatin1String("Development")) {
devManager->setDeviceState(newDev->id(), IDevice::DeviceReadyToUse);
m_userModeDeviceIds.removeOne(uid);
} else {
devManager->setDeviceState(newDev->id(), IDevice::DeviceConnected);
bool shouldIgnore = newDev->m_ignoreDevice;
newDev->m_ignoreDevice = true;
if (devStatus == QLatin1String("*off*")) {
if (!shouldIgnore && !IosConfigurations::ignoreAllDevices()) {
QMessageBox mBox;
mBox.setText(tr("An iOS device in user mode has been detected."));
mBox.setInformativeText(tr("Do you want to see how to set it up for development?"));
mBox.setStandardButtons(QMessageBox::NoAll | QMessageBox::No | QMessageBox::Yes);
mBox.setDefaultButton(QMessageBox::Yes);
int ret = mBox.exec();
switch (ret) {
case QMessageBox::Yes:
Core::HelpManager::showHelpUrl(
QLatin1String("qthelp://org.qt-project.qtcreator/doc/creator-developing-ios.html"));
break;
case QMessageBox::No:
break;
case QMessageBox::NoAll:
IosConfigurations::setIgnoreAllDevices(true);
break;
default:
break;
}
}
}
if (!m_userModeDeviceIds.contains(uid))
m_userModeDeviceIds.append(uid);
m_userModeDevicesTimer.start();
}
}
}
void IosDeviceManager::infoGathererFinished(IosToolHandler *gatherer)
{
gatherer->deleteLater();
}
#ifdef Q_OS_MAC
namespace {
io_iterator_t gAddedIter;
io_iterator_t gRemovedIter;
extern "C" {
void deviceConnectedCallback(void *refCon, io_iterator_t iterator)
{
try {
kern_return_t kr;
io_service_t usbDevice;
(void) refCon;
while ((usbDevice = IOIteratorNext(iterator))) {
io_name_t deviceName;
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
QString name;
if (KERN_SUCCESS == kr)
name = QString::fromLocal8Bit(deviceName);
qCDebug(detectLog) << "ios device " << name << " in deviceAddedCallback";
CFStringRef cfUid = static_cast<CFStringRef>(IORegistryEntryCreateCFProperty(
usbDevice,
CFSTR(kUSBSerialNumberString),
kCFAllocatorDefault, 0));
QString uid = CFStringRef2QString(cfUid);
CFRelease(cfUid);
IosDeviceManager::instance()->deviceConnected(uid, name);
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
catch (const std::exception &e) {
qCWarning(detectLog) << "Exception " << e.what() << " in iosdevice.cpp deviceConnectedCallback";
}
catch (...) {
qCWarning(detectLog) << "Exception in iosdevice.cpp deviceConnectedCallback";
throw;
}
}
void deviceDisconnectedCallback(void *refCon, io_iterator_t iterator)
{
try {
kern_return_t kr;
io_service_t usbDevice;
(void) refCon;
while ((usbDevice = IOIteratorNext(iterator))) {
io_name_t deviceName;
// Get the USB device's name.
kr = IORegistryEntryGetName(usbDevice, deviceName);
if (KERN_SUCCESS != kr)
deviceName[0] = '\0';
qCDebug(detectLog) << "ios device " << deviceName << " in deviceDisconnectedCallback";
{
CFStringRef cfUid = static_cast<CFStringRef>(IORegistryEntryCreateCFProperty(
usbDevice,
CFSTR(kUSBSerialNumberString),
kCFAllocatorDefault, 0));
QString uid = CFStringRef2QString(cfUid);
CFRelease(cfUid);
IosDeviceManager::instance()->deviceDisconnected(uid);
}
// Done with this USB device; release the reference added by IOIteratorNext
kr = IOObjectRelease(usbDevice);
}
}
catch (const std::exception &e) {
qCWarning(detectLog) << "Exception " << e.what() << " in iosdevice.cpp deviceDisconnectedCallback";
}
catch (...) {
qCWarning(detectLog) << "Exception in iosdevice.cpp deviceDisconnectedCallback";
throw;
}
}
} // extern C
} // anonymous namespace
#endif
void IosDeviceManager::monitorAvailableDevices()
{
#ifdef Q_OS_MAC
CFMutableDictionaryRef matchingDictionary =
IOServiceMatching("IOUSBDevice" );
{
UInt32 vendorId = 0x05ac;
CFNumberRef cfVendorValue = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type,
&vendorId );
CFDictionaryAddValue( matchingDictionary, CFSTR( kUSBVendorID ), cfVendorValue);
CFRelease( cfVendorValue );
UInt32 productId = 0x1280;
CFNumberRef cfProductIdValue = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type,
&productId );
CFDictionaryAddValue( matchingDictionary, CFSTR( kUSBProductID ), cfProductIdValue);
CFRelease( cfProductIdValue );
UInt32 productIdMask = 0xFFC0;
CFNumberRef cfProductIdMaskValue = CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type,
&productIdMask );
CFDictionaryAddValue( matchingDictionary, CFSTR( kUSBProductIDMask ), cfProductIdMaskValue);
CFRelease( cfProductIdMaskValue );
}
IONotificationPortRef notificationPort = IONotificationPortCreate(kIOMasterPortDefault);
CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationPort);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
// IOServiceAddMatchingNotification releases this, so we retain for the next call
CFRetain(matchingDictionary);
// Now set up a notification to be called when a device is first matched by I/O Kit.
kern_return_t kr;
kr = IOServiceAddMatchingNotification(notificationPort,
kIOMatchedNotification,
matchingDictionary,
deviceConnectedCallback,
NULL,
&gAddedIter);
kr = IOServiceAddMatchingNotification(notificationPort,
kIOTerminatedNotification,
matchingDictionary,
deviceDisconnectedCallback,
NULL,
&gRemovedIter);
// Iterate once to get already-present devices and arm the notification
deviceConnectedCallback(NULL, gAddedIter);
deviceDisconnectedCallback(NULL, gRemovedIter);
#endif
}
IosDeviceManager::IosDeviceManager(QObject *parent) :
QObject(parent)
{
m_userModeDevicesTimer.setSingleShot(true);
m_userModeDevicesTimer.setInterval(8000);
connect(&m_userModeDevicesTimer, &QTimer::timeout,
this, &IosDeviceManager::updateUserModeDevices);
}
void IosDeviceManager::updateUserModeDevices()
{
foreach (const QString &uid, m_userModeDeviceIds)
updateInfo(uid);
}
IosDeviceManager *IosDeviceManager::instance()
{
static IosDeviceManager obj;
return &obj;
}
void IosDeviceManager::updateAvailableDevices(const QStringList &devices)
{
foreach (const QString &uid, devices)
deviceConnected(uid);
DeviceManager *devManager = DeviceManager::instance();
for (int iDevice = 0; iDevice < devManager->deviceCount(); ++iDevice) {
IDevice::ConstPtr dev = devManager->deviceAt(iDevice);
Core::Id devType(Constants::IOS_DEVICE_TYPE);
if (dev.isNull() || dev->type() != devType)
continue;
auto iosDev = static_cast<const IosDevice *>(dev.data());
if (devices.contains(iosDev->uniqueDeviceID()))
continue;
if (iosDev->deviceState() != IDevice::DeviceDisconnected) {
qCDebug(detectLog) << "disconnecting device " << iosDev->uniqueDeviceID();
devManager->setDeviceState(iosDev->id(), IDevice::DeviceDisconnected);
}
}
}
// Factory
IosDeviceFactory::IosDeviceFactory()
: IDeviceFactory(Constants::IOS_DEVICE_TYPE)
{
setObjectName(QLatin1String("IosDeviceFactory"));
setDisplayName(IosDevice::name());
setCombinedIcon(":/ios/images/iosdevicesmall.png",
":/ios/images/iosdevice.png");
setConstructionFunction([] { return IDevice::Ptr(new IosDevice); });
}
bool IosDeviceFactory::canRestore(const QVariantMap &map) const
{
QVariantMap vMap = map.value(QLatin1String(Constants::EXTRA_INFO_KEY)).toMap();
if (vMap.isEmpty()
|| vMap.value(QLatin1String("deviceName")).toString() == QLatin1String("*unknown*"))
return false; // transient device (probably generated during an activation)
return true;
}
} // namespace Internal
} // namespace Ios