forked from qt-creator/qt-creator
Changed the algorithm to find the appropriate DeveloperDiskImage Task-number: QTCREATORBUG-18380 Change-Id: I4e3ddbc8cfd856f6a503cfa42cb9cc319a21a0c9 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
2065 lines
80 KiB
C++
2065 lines
80 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 "iosdevicemanager.h"
|
|
#include <QLibrary>
|
|
#include <QDebug>
|
|
#include <QStringList>
|
|
#include <QString>
|
|
#include <QHash>
|
|
#include <QMultiHash>
|
|
#include <QTimer>
|
|
#include <QThread>
|
|
#include <QSettings>
|
|
#include <QRegExp>
|
|
#include <QUrl>
|
|
#include <mach/error.h>
|
|
|
|
/* // annoying to import, do without
|
|
#include <QtCore/private/qcore_mac_p.h>
|
|
*/
|
|
/* standard calling convention under Win32 is __stdcall */
|
|
/* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */
|
|
/* _WIN32 symbol is defined by the compiler even though it's NOT compiling code for Windows32 */
|
|
#if defined(_WIN32) && !defined(EFI32) && !defined(EFI64)
|
|
#define MDEV_API __stdcall
|
|
#else
|
|
#define MDEV_API
|
|
#endif
|
|
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <CoreFoundation/CFRunLoop.h>
|
|
#include <CoreFoundation/CFDictionary.h>
|
|
#include <CoreFoundation/CFData.h>
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QProcess>
|
|
#include <QRegularExpression>
|
|
#include <QVersionNumber>
|
|
|
|
#ifdef MOBILE_DEV_DIRECT_LINK
|
|
#include "MobileDevice.h"
|
|
#endif
|
|
|
|
static const bool debugGdbServer = false;
|
|
static const bool debugAll = false;
|
|
static const bool verbose = true;
|
|
static const bool noWifi = true;
|
|
|
|
// ------- MobileDeviceLib interface --------
|
|
namespace {
|
|
|
|
#ifndef MOBILE_DEV_DIRECT_LINK
|
|
/* Messages passed to device notification callbacks: passed as part of
|
|
* AMDeviceNotificationCallbackInfo. */
|
|
enum ADNCI_MSG {
|
|
ADNCI_MSG_CONNECTED = 1,
|
|
ADNCI_MSG_DISCONNECTED = 2,
|
|
ADNCI_MSG_UNSUBSCRIBED = 3
|
|
};
|
|
#endif
|
|
|
|
extern "C" {
|
|
typedef unsigned int ServiceSocket; // match_port_t (i.e. natural_t) or socket (on windows, i.e sock_t)
|
|
typedef unsigned int am_res_t; // mach_error_t
|
|
|
|
#ifndef MOBILE_DEV_DIRECT_LINK
|
|
class AMDeviceNotification;
|
|
typedef const AMDeviceNotification *AMDeviceNotificationRef;
|
|
class AMDevice;
|
|
|
|
struct AMDeviceNotificationCallbackInfo {
|
|
AMDevice *_device;
|
|
unsigned int _message;
|
|
AMDeviceNotification *_subscription;
|
|
};
|
|
|
|
enum DeviceInterfaceType {
|
|
UNKNOWN = 0,
|
|
WIRED,
|
|
WIFI
|
|
};
|
|
|
|
typedef void (MDEV_API *AMDeviceNotificationCallback)(AMDeviceNotificationCallbackInfo *, void *);
|
|
typedef am_res_t (MDEV_API *AMDeviceInstallApplicationCallback)(CFDictionaryRef, void *);
|
|
typedef mach_error_t (MDEV_API *AMDeviceSecureInstallApplicationCallback)(CFDictionaryRef, int);
|
|
|
|
|
|
typedef AMDevice *AMDeviceRef;
|
|
#endif
|
|
typedef void (MDEV_API *AMDeviceMountImageCallback)(CFDictionaryRef, int);
|
|
|
|
|
|
|
|
typedef void (MDEV_API *AMDSetLogLevelPtr)(int);
|
|
typedef am_res_t (MDEV_API *AMDeviceNotificationSubscribePtr)(AMDeviceNotificationCallback,
|
|
unsigned int, unsigned int, void *,
|
|
const AMDeviceNotification **);
|
|
typedef am_res_t (MDEV_API *AMDeviceNotificationUnsubscribePtr)(void *);
|
|
typedef int (MDEV_API* AMDeviceGetInterfaceTypePtr)(AMDeviceRef device);
|
|
typedef CFPropertyListRef (MDEV_API *AMDeviceCopyValuePtr)(AMDeviceRef,CFStringRef,CFStringRef);
|
|
typedef unsigned int (MDEV_API *AMDeviceGetConnectionIDPtr)(AMDeviceRef);
|
|
typedef CFStringRef (MDEV_API *AMDeviceCopyDeviceIdentifierPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceConnectPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDevicePairPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceIsPairedPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceValidatePairingPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceStartSessionPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceStopSessionPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceDisconnectPtr)(AMDeviceRef);
|
|
typedef am_res_t (MDEV_API *AMDeviceMountImagePtr)(AMDeviceRef, CFStringRef, CFDictionaryRef,
|
|
AMDeviceMountImageCallback, void *);
|
|
typedef am_res_t (MDEV_API *AMDeviceStartServicePtr)(AMDeviceRef, CFStringRef, ServiceSocket *, void *);
|
|
typedef am_res_t (MDEV_API *AMDeviceUninstallApplicationPtr)(ServiceSocket, CFStringRef, CFDictionaryRef,
|
|
AMDeviceInstallApplicationCallback,
|
|
void*);
|
|
typedef am_res_t (MDEV_API *AMDeviceLookupApplicationsPtr)(AMDeviceRef, CFDictionaryRef, CFDictionaryRef *);
|
|
typedef char * (MDEV_API *AMDErrorStringPtr)(am_res_t);
|
|
typedef CFStringRef (MDEV_API *MISCopyErrorStringForErrorCodePtr)(am_res_t);
|
|
typedef am_res_t (MDEV_API *USBMuxConnectByPortPtr)(unsigned int, int, ServiceSocket*);
|
|
// secure Api's
|
|
typedef am_res_t (MDEV_API *AMDeviceSecureStartServicePtr)(AMDeviceRef, CFStringRef, void *, ServiceSocket *);
|
|
typedef int (MDEV_API *AMDeviceSecureTransferPathPtr)(int, AMDeviceRef, CFURLRef, CFDictionaryRef, AMDeviceSecureInstallApplicationCallback, int);
|
|
typedef int (MDEV_API *AMDeviceSecureInstallApplicationPtr)(int, AMDeviceRef, CFURLRef, CFDictionaryRef, AMDeviceSecureInstallApplicationCallback, int);
|
|
|
|
|
|
} // extern C
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace Ios {
|
|
namespace Internal {
|
|
|
|
static const am_res_t kAMDMobileImageMounterImageMountFailed = 0xe8000076;
|
|
|
|
class MobileDeviceLib {
|
|
public :
|
|
MobileDeviceLib();
|
|
|
|
bool load();
|
|
bool isLoaded();
|
|
QStringList errors();
|
|
//
|
|
|
|
void setLogLevel(int i) ;
|
|
am_res_t deviceNotificationSubscribe(AMDeviceNotificationCallback callback,
|
|
unsigned int v1, unsigned int v2, void *v3,
|
|
const AMDeviceNotification **handle);
|
|
am_res_t deviceNotificationUnsubscribe(void *handle);
|
|
int deviceGetInterfaceType(AMDeviceRef device);
|
|
CFPropertyListRef deviceCopyValue(AMDeviceRef,CFStringRef,CFStringRef);
|
|
unsigned int deviceGetConnectionID(AMDeviceRef);
|
|
CFStringRef deviceCopyDeviceIdentifier(AMDeviceRef);
|
|
am_res_t deviceConnect(AMDeviceRef);
|
|
am_res_t devicePair(AMDeviceRef);
|
|
am_res_t deviceIsPaired(AMDeviceRef);
|
|
am_res_t deviceValidatePairing(AMDeviceRef);
|
|
am_res_t deviceStartSession(AMDeviceRef);
|
|
am_res_t deviceStopSession(AMDeviceRef);
|
|
am_res_t deviceDisconnect(AMDeviceRef);
|
|
am_res_t deviceMountImage(AMDeviceRef, CFStringRef, CFDictionaryRef,
|
|
AMDeviceMountImageCallback, void *);
|
|
am_res_t deviceStartService(AMDeviceRef, CFStringRef, ServiceSocket *, void *);
|
|
am_res_t deviceUninstallApplication(int, CFStringRef, CFDictionaryRef,
|
|
AMDeviceInstallApplicationCallback,
|
|
void*);
|
|
am_res_t deviceLookupApplications(AMDeviceRef, CFDictionaryRef, CFDictionaryRef *);
|
|
char *errorString(am_res_t error);
|
|
CFStringRef misErrorStringForErrorCode(am_res_t error);
|
|
am_res_t connectByPort(unsigned int connectionId, int port, ServiceSocket *resFd);
|
|
|
|
void addError(const QString &msg);
|
|
void addError(const char *msg);
|
|
|
|
// Secure API's
|
|
am_res_t deviceSecureStartService(AMDeviceRef, CFStringRef, void *, ServiceSocket *);
|
|
int deviceSecureTransferApplicationPath(int, AMDeviceRef, CFURLRef,
|
|
CFDictionaryRef, AMDeviceSecureInstallApplicationCallback callback, int);
|
|
int deviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url,
|
|
CFDictionaryRef options, AMDeviceSecureInstallApplicationCallback callback, int arg);
|
|
|
|
QStringList m_errors;
|
|
private:
|
|
QLibrary lib;
|
|
QList<QLibrary *> deps;
|
|
AMDSetLogLevelPtr m_AMDSetLogLevel;
|
|
AMDeviceNotificationSubscribePtr m_AMDeviceNotificationSubscribe;
|
|
AMDeviceNotificationUnsubscribePtr m_AMDeviceNotificationUnsubscribe;
|
|
AMDeviceGetInterfaceTypePtr m_AMDeviceGetInterfaceType;
|
|
AMDeviceCopyValuePtr m_AMDeviceCopyValue;
|
|
AMDeviceGetConnectionIDPtr m_AMDeviceGetConnectionID;
|
|
AMDeviceCopyDeviceIdentifierPtr m_AMDeviceCopyDeviceIdentifier;
|
|
AMDeviceConnectPtr m_AMDeviceConnect;
|
|
AMDevicePairPtr m_AMDevicePair;
|
|
AMDeviceIsPairedPtr m_AMDeviceIsPaired;
|
|
AMDeviceValidatePairingPtr m_AMDeviceValidatePairing;
|
|
AMDeviceStartSessionPtr m_AMDeviceStartSession;
|
|
AMDeviceStopSessionPtr m_AMDeviceStopSession;
|
|
AMDeviceDisconnectPtr m_AMDeviceDisconnect;
|
|
AMDeviceMountImagePtr m_AMDeviceMountImage;
|
|
AMDeviceStartServicePtr m_AMDeviceStartService;
|
|
AMDeviceSecureStartServicePtr m_AMDeviceSecureStartService;
|
|
AMDeviceSecureTransferPathPtr m_AMDeviceSecureTransferPath;
|
|
AMDeviceSecureInstallApplicationPtr m_AMDeviceSecureInstallApplication;
|
|
AMDeviceUninstallApplicationPtr m_AMDeviceUninstallApplication;
|
|
AMDeviceLookupApplicationsPtr m_AMDeviceLookupApplications;
|
|
AMDErrorStringPtr m_AMDErrorString;
|
|
MISCopyErrorStringForErrorCodePtr m_MISCopyErrorStringForErrorCode;
|
|
USBMuxConnectByPortPtr m_USBMuxConnectByPort;
|
|
};
|
|
|
|
static QString mobileDeviceErrorString(MobileDeviceLib *lib, am_res_t code)
|
|
{
|
|
QString s = QStringLiteral("Unknown error (0x%08x)").arg(code);
|
|
|
|
// AMDErrors, 0x0, 0xe8000001-0xe80000db
|
|
if (char *ptr = lib->errorString(code)) {
|
|
CFStringRef key = QString::fromLatin1(ptr).toCFString();
|
|
|
|
CFURLRef url = QUrl::fromLocalFile(
|
|
QStringLiteral("/System/Library/PrivateFrameworks/MobileDevice.framework")).toCFURL();
|
|
CFBundleRef mobileDeviceBundle = CFBundleCreate(kCFAllocatorDefault, url);
|
|
CFRelease(url);
|
|
|
|
if (mobileDeviceBundle) {
|
|
CFStringRef str = CFCopyLocalizedStringFromTableInBundle(key, CFSTR("Localizable"),
|
|
mobileDeviceBundle, nil);
|
|
s = QString::fromCFString(str);
|
|
CFRelease(str);
|
|
}
|
|
|
|
CFRelease(key);
|
|
} else if (CFStringRef str = lib->misErrorStringForErrorCode(code)) {
|
|
// MIS errors, 0xe8008001-0xe800801e
|
|
s = QString::fromCFString(str);
|
|
CFRelease(str);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
qint64 toBuildNumber(const QString &versionStr)
|
|
{
|
|
QString buildNumber;
|
|
const QRegularExpression re("\\s\\((\\X+)\\)");
|
|
const QRegularExpressionMatch m = re.match(versionStr);
|
|
if (m.hasMatch())
|
|
buildNumber = m.captured(1);
|
|
return buildNumber.toLongLong(nullptr, 16);
|
|
}
|
|
|
|
static bool findXcodePath(QString *xcodePath)
|
|
{
|
|
if (!xcodePath)
|
|
return false;
|
|
|
|
QProcess process;
|
|
process.start("/bin/bash", QStringList({"-c", "xcode-select -p"}));
|
|
if (!process.waitForFinished(3000))
|
|
return false;
|
|
|
|
*xcodePath = QString::fromLatin1(process.readAllStandardOutput()).trimmed();
|
|
return (process.exitStatus() == QProcess::NormalExit && QFile::exists(*xcodePath));
|
|
}
|
|
|
|
/*!
|
|
* \brief Finds the \e DeveloperDiskImage.dmg path corresponding to \a versionStr and \a buildStr.
|
|
*
|
|
* The algorithm sorts the \e DeviceSupport directories with decreasing order of their version and
|
|
* build number to find the appropriate \e DeviceSupport directory corresponding to \a versionStr
|
|
* and \a buildStr. Directories starting with major build number of \a versionStr are considered
|
|
* only, rest are filtered out.
|
|
*
|
|
* Returns \c true if \e DeveloperDiskImage.dmg is found, otherwise returns \c false. The absolute
|
|
* path to the \e DeveloperDiskImage.dmg is enumerated in \a path.
|
|
*/
|
|
static bool findDeveloperDiskImage(const QString &versionStr, const QString &buildStr,
|
|
QString *path = nullptr)
|
|
{
|
|
const QVersionNumber deviceVersion = QVersionNumber::fromString(versionStr);
|
|
if (deviceVersion.isNull())
|
|
return false;
|
|
|
|
QString xcodePath;
|
|
if (!findXcodePath(&xcodePath)) {
|
|
if (debugAll)
|
|
qDebug() << "Error getting xcode installation path.";
|
|
return false;
|
|
}
|
|
|
|
// Returns device support directories matching the major version.
|
|
const auto findDeviceSupportDirs = [xcodePath, deviceVersion](const QString &subFolder) {
|
|
const QDir rootDir(QString("%1/%2").arg(xcodePath).arg(subFolder));
|
|
const QStringList filter(QString("%1.*").arg(deviceVersion.majorVersion()));
|
|
return rootDir.entryInfoList(filter, QDir::Dirs | QDir::NoDotAndDotDot);
|
|
};
|
|
|
|
// Compares version strings(considers build number) and return true if versionStrA > versionStrB.
|
|
const auto compareVersion = [](const QString &versionStrA, const QString &versionStrB) {
|
|
const auto versionA = QVersionNumber::fromString(versionStrA);
|
|
const auto versionB = QVersionNumber::fromString(versionStrB);
|
|
if (versionA == versionB)
|
|
return toBuildNumber(versionStrA) > toBuildNumber(versionStrB);
|
|
return versionA > versionB;
|
|
};
|
|
|
|
// To filter dirs without DeveloperDiskImage.dmg and dirs having higher version.
|
|
const auto filterDir = [compareVersion, versionStr, buildStr](const QFileInfo d) {
|
|
const auto devDiskImage = QString("%1/DeveloperDiskImage.dmg").arg(d.absoluteFilePath());
|
|
if (!QFile::exists(devDiskImage))
|
|
return true; // Dir's without DeveloperDiskImage.dmg
|
|
return compareVersion(d.fileName(), QString("%1 (%2)").arg(versionStr).arg(buildStr));
|
|
};
|
|
|
|
QFileInfoList deviceSupportDirs(findDeviceSupportDirs("iOS DeviceSupport"));
|
|
deviceSupportDirs << findDeviceSupportDirs("Platforms/iPhoneOS.platform/DeviceSupport");
|
|
|
|
// Remove dirs without DeveloperDiskImage.dmg and dirs having higher version.
|
|
auto endItr = std::remove_if(deviceSupportDirs.begin(), deviceSupportDirs.end(), filterDir);
|
|
deviceSupportDirs.erase(endItr, deviceSupportDirs.end());
|
|
|
|
if (deviceSupportDirs.isEmpty())
|
|
return false;
|
|
|
|
// Sort device support directories.
|
|
std::sort(deviceSupportDirs.begin(), deviceSupportDirs.end(),
|
|
[compareVersion](const QFileInfo &a, const QFileInfo &b) {
|
|
return compareVersion(a.fileName(), b.fileName());
|
|
});
|
|
|
|
if (path)
|
|
*path = QString("%1/DeveloperDiskImage.dmg").arg(deviceSupportDirs[0].absoluteFilePath());
|
|
return true;
|
|
}
|
|
|
|
extern "C" {
|
|
typedef void (*DeviceAvailableCallback)(QString deviceId, AMDeviceRef, void *userData);
|
|
}
|
|
|
|
class PendingDeviceLookup {
|
|
public:
|
|
QTimer timer;
|
|
DeviceAvailableCallback callback;
|
|
void *userData;
|
|
};
|
|
|
|
class CommandSession : public DeviceSession
|
|
{
|
|
public:
|
|
virtual ~CommandSession();
|
|
explicit CommandSession(const QString &deviceId);
|
|
|
|
void internalDeviceAvailableCallback(QString deviceId, AMDeviceRef device);
|
|
virtual void deviceCallbackReturned() { }
|
|
virtual am_res_t appTransferCallback(CFDictionaryRef) { return 0; }
|
|
virtual am_res_t appInstallCallback(CFDictionaryRef) { return 0; }
|
|
virtual void reportProgress(CFDictionaryRef dict);
|
|
virtual void reportProgress2(int progress, const QString &status);
|
|
virtual QString commandName();
|
|
|
|
bool connectDevice();
|
|
bool disconnectDevice();
|
|
bool startService(const QString &service, ServiceSocket &fd);
|
|
void stopService(ServiceSocket fd);
|
|
void startDeviceLookup(int timeout);
|
|
bool connectToPort(quint16 port, ServiceSocket *fd) override;
|
|
int qmljsDebugPort() const override;
|
|
void addError(const QString &msg);
|
|
bool writeAll(ServiceSocket fd, const char *cmd, qptrdiff len = -1);
|
|
bool mountDeveloperDiskImage();
|
|
bool sendGdbCommand(ServiceSocket fd, const char *cmd, qptrdiff len = -1);
|
|
QByteArray readGdbReply(ServiceSocket fd);
|
|
bool expectGdbReply(ServiceSocket gdbFd, QByteArray expected);
|
|
bool expectGdbOkReply(ServiceSocket gdbFd);
|
|
bool startServiceSecure(const QString &serviceName, ServiceSocket &fd);
|
|
|
|
MobileDeviceLib *lib();
|
|
|
|
AMDeviceRef device;
|
|
int progressBase;
|
|
int unexpectedChars;
|
|
bool aknowledge;
|
|
|
|
private:
|
|
bool developerDiskImagePath(QString *path, QString *signaturePath);
|
|
|
|
private:
|
|
bool checkRead(qptrdiff nRead, int &maxRetry);
|
|
int handleChar(int sock, QByteArray &res, char c, int status);
|
|
};
|
|
|
|
// ------- IosManagerPrivate interface --------
|
|
|
|
class IosDeviceManagerPrivate {
|
|
public:
|
|
static IosDeviceManagerPrivate *instance();
|
|
explicit IosDeviceManagerPrivate (IosDeviceManager *q);
|
|
bool watchDevices();
|
|
void requestAppOp(const QString &bundlePath, const QStringList &extraArgs,
|
|
Ios::IosDeviceManager::AppOp appOp, const QString &deviceId, int timeout);
|
|
void requestDeviceInfo(const QString &deviceId, int timeout);
|
|
QStringList errors();
|
|
void addError(QString errorMsg);
|
|
void addDevice(AMDeviceRef device);
|
|
void removeDevice(AMDeviceRef device);
|
|
void checkPendingLookups();
|
|
MobileDeviceLib *lib();
|
|
void didTransferApp(const QString &bundlePath, const QString &deviceId,
|
|
Ios::IosDeviceManager::OpStatus status);
|
|
void didStartApp(const QString &bundlePath, const QString &deviceId,
|
|
Ios::IosDeviceManager::OpStatus status, int gdbFd, DeviceSession *deviceSession);
|
|
void isTransferringApp(const QString &bundlePath, const QString &deviceId, int progress,
|
|
const QString &info);
|
|
void deviceWithId(QString deviceId, int timeout, DeviceAvailableCallback callback, void *userData);
|
|
int processGdbServer(int fd);
|
|
void stopGdbServer(int fd, int phase);
|
|
private:
|
|
IosDeviceManager *q;
|
|
QMutex m_sendMutex;
|
|
QHash<QString, AMDeviceRef> m_devices;
|
|
QMultiHash<QString, PendingDeviceLookup *> m_pendingLookups;
|
|
AMDeviceNotificationRef m_notification;
|
|
MobileDeviceLib m_lib;
|
|
};
|
|
|
|
class DevInfoSession: public CommandSession {
|
|
public:
|
|
DevInfoSession(const QString &deviceId);
|
|
|
|
void deviceCallbackReturned();
|
|
QString commandName();
|
|
};
|
|
|
|
class AppOpSession: public CommandSession {
|
|
public:
|
|
QString bundlePath;
|
|
QStringList extraArgs;
|
|
Ios::IosDeviceManager::AppOp appOp;
|
|
|
|
|
|
AppOpSession(const QString &deviceId, const QString &bundlePath,
|
|
const QStringList &extraArgs, Ios::IosDeviceManager::AppOp appOp);
|
|
|
|
void deviceCallbackReturned() override;
|
|
bool installApp();
|
|
bool runApp();
|
|
int qmljsDebugPort() const override;
|
|
am_res_t appTransferCallback(CFDictionaryRef dict) override;
|
|
am_res_t appInstallCallback(CFDictionaryRef dict) override;
|
|
void reportProgress2(int progress, const QString &status) override;
|
|
QString appPathOnDevice();
|
|
QString appId();
|
|
QString commandName() override;
|
|
};
|
|
|
|
}
|
|
|
|
DeviceSession::DeviceSession(const QString &deviceId) :
|
|
deviceId(deviceId)
|
|
{
|
|
}
|
|
|
|
DeviceSession::~DeviceSession()
|
|
{
|
|
}
|
|
|
|
// namespace Internal
|
|
} // namespace Ios
|
|
|
|
|
|
namespace {
|
|
// ------- callbacks --------
|
|
|
|
extern "C" void deviceNotificationCallback(AMDeviceNotificationCallbackInfo *info, void *user)
|
|
{
|
|
if (info == 0)
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addError(QLatin1String("null info in deviceNotificationCallback"));
|
|
if (debugAll) {
|
|
QDebug dbg=qDebug();
|
|
dbg << "device_notification_callback(";
|
|
if (info)
|
|
dbg << " dev:" << info->_device << " msg:" << info->_message << " subscription:" << info->_subscription;
|
|
else
|
|
dbg << "*NULL*";
|
|
dbg << "," << user << ")";
|
|
}
|
|
switch (info->_message) {
|
|
case ADNCI_MSG_CONNECTED:
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addDevice(info->_device);
|
|
break;
|
|
case ADNCI_MSG_DISCONNECTED:
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->removeDevice(info->_device);
|
|
break;
|
|
case ADNCI_MSG_UNSUBSCRIBED:
|
|
break;
|
|
default:
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addError(QLatin1String("unexpected notification message value ")
|
|
+ QString::number(info->_message));
|
|
}
|
|
}
|
|
|
|
extern "C" void deviceAvailableSessionCallback(QString deviceId, AMDeviceRef device, void *userData)
|
|
{
|
|
if (debugAll)
|
|
qDebug() << "deviceAvailableSessionCallback" << QThread::currentThread();
|
|
if (userData == 0) {
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addError(QLatin1String("deviceAvailableSessionCallback called with null userData"));
|
|
return;
|
|
}
|
|
Ios::Internal::CommandSession *session = static_cast<Ios::Internal::CommandSession *>(userData);
|
|
session->internalDeviceAvailableCallback(deviceId, device);
|
|
}
|
|
|
|
extern "C" am_res_t appTransferSessionCallback(CFDictionaryRef dict, void *userData)
|
|
{
|
|
if (debugAll) {
|
|
qDebug() << "appTransferSessionCallback" << QThread::currentThread();
|
|
CFShow(dict);
|
|
}
|
|
if (userData == 0) {
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addError(QLatin1String("appTransferSessionCallback called with null userData"));
|
|
return 0; // return -1?
|
|
}
|
|
Ios::Internal::CommandSession *session = static_cast<Ios::Internal::CommandSession *>(userData);
|
|
return session->appTransferCallback(dict);
|
|
}
|
|
|
|
|
|
extern "C" mach_error_t appSecureTransferSessionCallback(CFDictionaryRef dict, int arg)
|
|
{
|
|
Q_UNUSED(arg)
|
|
CFStringRef cfStatus = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(dict, CFSTR("Status")));
|
|
const QString status = QString::fromCFString(cfStatus);
|
|
|
|
quint32 percent = 0;
|
|
CFNumberRef cfProgress;
|
|
if (CFDictionaryGetValueIfPresent(dict, CFSTR("PercentComplete"), reinterpret_cast<const void **>(&cfProgress))) {
|
|
if (cfProgress && CFGetTypeID(cfProgress) == CFNumberGetTypeID())
|
|
CFNumberGetValue(cfProgress, kCFNumberSInt32Type, reinterpret_cast<const void **>(&percent));
|
|
}
|
|
|
|
QString path;
|
|
if (status == "CopyingFile") {
|
|
CFStringRef cfPath = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(dict, CFSTR("Path")));
|
|
path = QString::fromCFString(cfPath);
|
|
}
|
|
|
|
if (debugAll) {
|
|
qDebug() << "["<<percent<<"]" << status << path;
|
|
} else {
|
|
static QString oldStatus;
|
|
if (oldStatus != status) {
|
|
qDebug() << status;
|
|
oldStatus = status;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
extern "C" am_res_t appInstallSessionCallback(CFDictionaryRef dict, void *userData)
|
|
{
|
|
if (debugAll) {
|
|
qDebug() << "appInstallSessionCallback" << QThread::currentThread();
|
|
CFShow(dict);
|
|
}
|
|
if (userData == 0) {
|
|
Ios::Internal::IosDeviceManagerPrivate::instance()->addError(QLatin1String("appInstallSessionCallback called with null userData"));
|
|
return 0; // return -1?
|
|
}
|
|
Ios::Internal::CommandSession *session = static_cast<Ios::Internal::CommandSession *>(userData);
|
|
return session->appInstallCallback(dict);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
namespace Ios {
|
|
namespace Internal {
|
|
|
|
// ------- IosManagerPrivate implementation --------
|
|
|
|
IosDeviceManagerPrivate *IosDeviceManagerPrivate::instance()
|
|
{
|
|
return IosDeviceManager::instance()->d;
|
|
}
|
|
|
|
IosDeviceManagerPrivate::IosDeviceManagerPrivate (IosDeviceManager *q) : q(q), m_notification(0) { }
|
|
|
|
bool IosDeviceManagerPrivate::watchDevices()
|
|
{
|
|
if (!m_lib.load())
|
|
addError(QLatin1String("Error loading MobileDevice.framework"));
|
|
if (!m_lib.errors().isEmpty())
|
|
foreach (const QString &msg, m_lib.errors())
|
|
addError(msg);
|
|
m_lib.setLogLevel(5);
|
|
am_res_t e = m_lib.deviceNotificationSubscribe(&deviceNotificationCallback, 0, 0,
|
|
0, &m_notification);
|
|
if (e != 0) {
|
|
addError(QLatin1String("AMDeviceNotificationSubscribe failed"));
|
|
return false;
|
|
}
|
|
if (debugAll)
|
|
qDebug() << "AMDeviceNotificationSubscribe successful, m_notificationIter:" << m_notification << QThread::currentThread();
|
|
|
|
return true;
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::requestAppOp(const QString &bundlePath,
|
|
const QStringList &extraArgs,
|
|
IosDeviceManager::AppOp appOp,
|
|
const QString &deviceId, int timeout)
|
|
{
|
|
AppOpSession *session = new AppOpSession(deviceId, bundlePath, extraArgs, appOp);
|
|
session->startDeviceLookup(timeout);
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::requestDeviceInfo(const QString &deviceId, int timeout)
|
|
{
|
|
DevInfoSession *session = new DevInfoSession(deviceId);
|
|
session->startDeviceLookup(timeout);
|
|
}
|
|
|
|
QStringList IosDeviceManagerPrivate::errors()
|
|
{
|
|
return m_lib.errors();
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::addError(QString errorMsg)
|
|
{
|
|
if (debugAll)
|
|
qDebug() << "IosManagerPrivate ERROR: " << errorMsg;
|
|
emit q->errorMsg(errorMsg);
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::addDevice(AMDeviceRef device)
|
|
{
|
|
CFStringRef s = m_lib.deviceCopyDeviceIdentifier(device);
|
|
QString devId = QString::fromCFString(s);
|
|
if (s) CFRelease(s);
|
|
CFRetain(device);
|
|
|
|
DeviceInterfaceType interfaceType = static_cast<DeviceInterfaceType>(lib()->deviceGetInterfaceType(device));
|
|
if (interfaceType == DeviceInterfaceType::UNKNOWN) {
|
|
if (debugAll)
|
|
qDebug() << "Skipping device." << devId << "Interface type: Unknown.";
|
|
return;
|
|
}
|
|
|
|
// Skip the wifi connections as debugging over wifi is not supported.
|
|
if (noWifi && interfaceType == DeviceInterfaceType::WIFI) {
|
|
if (debugAll)
|
|
qDebug() << "Skipping device." << devId << "Interface type: WIFI. Debugging over WIFI is not supported.";
|
|
return;
|
|
}
|
|
|
|
if (debugAll)
|
|
qDebug() << "addDevice " << devId;
|
|
if (m_devices.contains(devId)) {
|
|
if (m_devices.value(devId) == device) {
|
|
addError(QLatin1String("double add of device ") + devId);
|
|
return;
|
|
}
|
|
addError(QLatin1String("device change without remove ") + devId);
|
|
removeDevice(m_devices.value(devId));
|
|
}
|
|
m_devices.insert(devId, device);
|
|
emit q->deviceAdded(devId);
|
|
if (m_pendingLookups.contains(devId) || m_pendingLookups.contains(QString())) {
|
|
QList<PendingDeviceLookup *> devices = m_pendingLookups.values(devId);
|
|
m_pendingLookups.remove(devId);
|
|
devices << m_pendingLookups.values(QString());
|
|
m_pendingLookups.remove(QString());
|
|
foreach (PendingDeviceLookup *devLookup, devices) {
|
|
if (debugAll) qDebug() << "found pending op";
|
|
devLookup->timer.stop();
|
|
devLookup->callback(devId, device, devLookup->userData);
|
|
delete devLookup;
|
|
}
|
|
}
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::removeDevice(AMDeviceRef device)
|
|
{
|
|
CFStringRef s = m_lib.deviceCopyDeviceIdentifier(device);
|
|
QString devId = QString::fromCFString(s);
|
|
if (s)
|
|
CFRelease(s);
|
|
if (debugAll)
|
|
qDebug() << "removeDevice " << devId;
|
|
if (m_devices.contains(devId)) {
|
|
if (m_devices.value(devId) == device) {
|
|
CFRelease(device);
|
|
m_devices.remove(devId);
|
|
emit q->deviceRemoved(devId);
|
|
return;
|
|
}
|
|
addError(QLatin1String("ignoring remove of changed device ") + devId);
|
|
} else {
|
|
addError(QLatin1String("removal of unknown device ") + devId);
|
|
}
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::checkPendingLookups()
|
|
{
|
|
foreach (const QString &deviceId, m_pendingLookups.keys()) {
|
|
foreach (PendingDeviceLookup *deviceLookup, m_pendingLookups.values(deviceId)) {
|
|
if (!deviceLookup->timer.isActive()) {
|
|
m_pendingLookups.remove(deviceId, deviceLookup);
|
|
deviceLookup->callback(deviceId, 0, deviceLookup->userData);
|
|
delete deviceLookup;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MobileDeviceLib *IosDeviceManagerPrivate::lib()
|
|
{
|
|
return &m_lib;
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::didTransferApp(const QString &bundlePath, const QString &deviceId,
|
|
IosDeviceManager::OpStatus status)
|
|
{
|
|
emit IosDeviceManagerPrivate::instance()->q->didTransferApp(bundlePath, deviceId, status);
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::didStartApp(const QString &bundlePath, const QString &deviceId,
|
|
IosDeviceManager::OpStatus status, int gdbFd,
|
|
DeviceSession *deviceSession)
|
|
{
|
|
emit IosDeviceManagerPrivate::instance()->q->didStartApp(bundlePath, deviceId, status, gdbFd,
|
|
deviceSession);
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::isTransferringApp(const QString &bundlePath, const QString &deviceId,
|
|
int progress, const QString &info)
|
|
{
|
|
emit IosDeviceManagerPrivate::instance()->q->isTransferringApp(bundlePath, deviceId, progress, info);
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::deviceWithId(QString deviceId, int timeout,
|
|
DeviceAvailableCallback callback,
|
|
void *userData)
|
|
{
|
|
if (!m_notification) {
|
|
qDebug() << "null notification!!";
|
|
/*if (!watchDevices()) {
|
|
callback(deviceId, 0, userData);
|
|
return;
|
|
}*/
|
|
}
|
|
if (deviceId.isEmpty() && !m_devices.isEmpty()) {
|
|
QHash<QString,AMDeviceRef>::iterator i = m_devices.begin();
|
|
callback(i.key(), i.value() , userData);
|
|
return;
|
|
}
|
|
if (m_devices.contains(deviceId)) {
|
|
callback(deviceId, m_devices.value(deviceId), userData);
|
|
return;
|
|
}
|
|
if (timeout <= 0) {
|
|
callback(deviceId, 0, userData);
|
|
return;
|
|
}
|
|
PendingDeviceLookup *pendingLookup = new PendingDeviceLookup;
|
|
pendingLookup->callback = callback;
|
|
pendingLookup->userData = userData;
|
|
pendingLookup->timer.setSingleShot(true);
|
|
pendingLookup->timer.setInterval(timeout);
|
|
QObject::connect(&(pendingLookup->timer), &QTimer::timeout, q, &IosDeviceManager::checkPendingLookups);
|
|
m_pendingLookups.insertMulti(deviceId, pendingLookup);
|
|
pendingLookup->timer.start();
|
|
}
|
|
enum GdbServerStatus {
|
|
NORMAL_PROCESS,
|
|
PROTOCOL_ERROR,
|
|
STOP_FOR_SIGNAL,
|
|
INFERIOR_EXITED,
|
|
PROTOCOL_UNHANDLED
|
|
};
|
|
|
|
int IosDeviceManagerPrivate::processGdbServer(int fd)
|
|
{
|
|
CommandSession session((QString()));
|
|
{
|
|
QMutexLocker l(&m_sendMutex);
|
|
session.sendGdbCommand(fd, "vCont;c"); // resume all threads
|
|
}
|
|
GdbServerStatus state = NORMAL_PROCESS;
|
|
int maxRetry = 10;
|
|
int maxSignal = 5;
|
|
while (state == NORMAL_PROCESS) {
|
|
QByteArray repl = session.readGdbReply(fd);
|
|
int signal = 0;
|
|
if (repl.size() > 0) {
|
|
state = PROTOCOL_ERROR;
|
|
switch (repl.at(0)) {
|
|
case 'S':
|
|
if (repl.size() < 3) {
|
|
addError(QString::fromLatin1("invalid S signal message %1")
|
|
.arg(QString::fromLatin1(repl.constData(), repl.size())));
|
|
state = PROTOCOL_ERROR;
|
|
} else {
|
|
signal = QByteArray::fromHex(repl.mid(1,2)).at(0);
|
|
addError(QString::fromLatin1("program received signal %1")
|
|
.arg(signal));
|
|
state = STOP_FOR_SIGNAL;
|
|
}
|
|
break;
|
|
case 'T':
|
|
if (repl.size() < 3) {
|
|
addError(QString::fromLatin1("invalid T signal message %1")
|
|
.arg(QString::fromLatin1(repl.constData(), repl.size())));
|
|
state = PROTOCOL_ERROR;
|
|
} else {
|
|
signal = QByteArray::fromHex(repl.mid(1,2)).at(0);
|
|
addError(QString::fromLatin1("program received signal %1, %2")
|
|
.arg(signal)
|
|
.arg(QString::fromLatin1(repl.mid(3,repl.size()-3))));
|
|
state = STOP_FOR_SIGNAL;
|
|
}
|
|
break;
|
|
case 'W':
|
|
if (repl.size() < 3) {
|
|
addError(QString::fromLatin1("invalid W signal message %1")
|
|
.arg(QString::fromLatin1(repl.constData(), repl.size())));
|
|
state = PROTOCOL_ERROR;
|
|
} else {
|
|
int exitCode = QByteArray::fromHex(repl.mid(1,2)).at(0);
|
|
addError(QString::fromLatin1("exited with exit code %1, %2")
|
|
.arg(exitCode)
|
|
.arg(QString::fromLatin1(repl.mid(3,repl.size()-3))));
|
|
state = INFERIOR_EXITED;
|
|
}
|
|
break;
|
|
case 'X':
|
|
if (repl.size() < 3) {
|
|
addError(QString::fromLatin1("invalid X signal message %1")
|
|
.arg(QString::fromLatin1(repl.constData(), repl.size())));
|
|
state = PROTOCOL_ERROR;
|
|
} else {
|
|
int exitCode = QByteArray::fromHex(repl.mid(1,2)).at(0);
|
|
addError(QString::fromLatin1("exited due to signal %1, %2")
|
|
.arg(exitCode)
|
|
.arg(QString::fromLatin1(repl.mid(3,repl.size()-3))));
|
|
state = INFERIOR_EXITED;
|
|
}
|
|
break;
|
|
case 'O':
|
|
emit q->appOutput(QString::fromLocal8Bit(QByteArray::fromHex(repl.mid(1))));
|
|
state = NORMAL_PROCESS;
|
|
break;
|
|
default:
|
|
addError(QString::fromLatin1("non handled response:")
|
|
+ QString::fromLatin1(repl.constData(), repl.size()));
|
|
state = PROTOCOL_UNHANDLED;
|
|
}
|
|
if (state == STOP_FOR_SIGNAL) {
|
|
QList<int> okSig = QList<int>() << SIGCHLD << SIGCONT << SIGALRM << SIGURG
|
|
<< SIGUSR1 << SIGUSR2 << SIGPIPE
|
|
<< SIGPROF << SIGWINCH << SIGINFO;
|
|
if (signal == 9) {
|
|
break;
|
|
} else if (!okSig.contains(signal) && --maxSignal < 0) {
|
|
addError(QLatin1String("hit maximum number of consecutive signals, stopping"));
|
|
break;
|
|
}
|
|
{
|
|
if (signal == 17) {
|
|
state = NORMAL_PROCESS; // Ctrl-C to kill the process
|
|
} else {
|
|
QMutexLocker l(&m_sendMutex);
|
|
if (session.sendGdbCommand(fd, "vCont;c"))
|
|
state = NORMAL_PROCESS;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
maxSignal = 5;
|
|
}
|
|
maxRetry = 10;
|
|
} else {
|
|
if (--maxRetry < 0)
|
|
break;
|
|
}
|
|
}
|
|
return state != INFERIOR_EXITED;
|
|
}
|
|
|
|
void IosDeviceManagerPrivate::stopGdbServer(int fd, int phase)
|
|
{
|
|
CommandSession session((QString()));
|
|
QMutexLocker l(&m_sendMutex);
|
|
if (phase == 0)
|
|
session.writeAll(fd,"\x03",1);
|
|
else
|
|
session.sendGdbCommand(fd, "k", 1);
|
|
}
|
|
|
|
// ------- ConnectSession implementation --------
|
|
|
|
CommandSession::CommandSession(const QString &deviceId) : DeviceSession(deviceId), device(0),
|
|
progressBase(0), unexpectedChars(0), aknowledge(true)
|
|
{ }
|
|
|
|
CommandSession::~CommandSession() { }
|
|
|
|
bool CommandSession::connectDevice()
|
|
{
|
|
if (!device)
|
|
return false;
|
|
|
|
if (am_res_t error1 = lib()->deviceConnect(device)) {
|
|
addError(QString::fromLatin1("connectDevice %1 failed, AMDeviceConnect returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error1)).arg(error1));
|
|
return false;
|
|
}
|
|
if (lib()->deviceIsPaired(device) == 0) { // not paired
|
|
if (am_res_t error = lib()->devicePair(device)) {
|
|
addError(QString::fromLatin1("connectDevice %1 failed, AMDevicePair returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(error));
|
|
return false;
|
|
}
|
|
}
|
|
if (am_res_t error2 = lib()->deviceValidatePairing(device)) {
|
|
addError(QString::fromLatin1("connectDevice %1 failed, AMDeviceValidatePairing returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error2)).arg(error2));
|
|
return false;
|
|
}
|
|
if (am_res_t error3 = lib()->deviceStartSession(device)) {
|
|
addError(QString::fromLatin1("connectDevice %1 failed, AMDeviceStartSession returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error3)).arg(error3));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CommandSession::disconnectDevice()
|
|
{
|
|
if (am_res_t error = lib()->deviceStopSession(device)) {
|
|
addError(QString::fromLatin1("stopSession %1 failed, AMDeviceStopSession returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(error));
|
|
return false;
|
|
}
|
|
if (am_res_t error = lib()->deviceDisconnect(device)) {
|
|
addError(QString::fromLatin1("disconnectDevice %1 failed, AMDeviceDisconnect returned %2 (0x%3)")
|
|
.arg(deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(error));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CommandSession::startService(const QString &serviceName, ServiceSocket &fd)
|
|
{
|
|
bool success = true;
|
|
|
|
// Connect device. AMDeviceConnect + AMDeviceIsPaired + AMDeviceValidatePairing + AMDeviceStartSession
|
|
if (connectDevice()) {
|
|
fd = 0;
|
|
CFStringRef cfsService = serviceName.toCFString();
|
|
if (am_res_t error = lib()->deviceStartService(device, cfsService, &fd, 0)) {
|
|
addError(QString::fromLatin1("Starting service \"%1\" on device %2 failed, AMDeviceStartService returned %3 (0x%4)")
|
|
.arg(serviceName).arg(deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(QString::number(error, 16)));
|
|
success = false;
|
|
fd = -1;
|
|
}
|
|
disconnectDevice();
|
|
CFRelease(cfsService);
|
|
} else {
|
|
addError(QString::fromLatin1("Starting service \"%1\" on device %2 failed. Cannot connect to device.")
|
|
.arg(serviceName).arg(deviceId));
|
|
success = false;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool CommandSession::startServiceSecure(const QString &serviceName, ServiceSocket &fd)
|
|
{
|
|
bool success = true;
|
|
|
|
// Connect device. AMDeviceConnect + AMDeviceIsPaired + AMDeviceValidatePairing + AMDeviceStartSession
|
|
if (connectDevice()) {
|
|
fd = 0;
|
|
CFStringRef cfsService = serviceName.toCFString();
|
|
if (am_res_t error = lib()->deviceSecureStartService(device, cfsService, &fd, 0)) {
|
|
addError(QString::fromLatin1("Starting(Secure) service \"%1\" on device %2 failed, AMDeviceStartSecureService returned %3 (0x%4)")
|
|
.arg(serviceName).arg(deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(QString::number(error, 16)));
|
|
success = false;
|
|
fd = -1;
|
|
}
|
|
disconnectDevice();
|
|
CFRelease(cfsService);
|
|
} else {
|
|
addError(QString::fromLatin1("Starting(Secure) service \"%1\" on device %2 failed. Cannot connect to device.")
|
|
.arg(serviceName).arg(deviceId));
|
|
success = false;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool CommandSession::connectToPort(quint16 port, ServiceSocket *fd)
|
|
{
|
|
if (!fd)
|
|
return false;
|
|
bool failure = false;
|
|
*fd = 0;
|
|
ServiceSocket fileDescriptor;
|
|
if (!connectDevice())
|
|
return false;
|
|
if (am_res_t error = lib()->connectByPort(lib()->deviceGetConnectionID(device), htons(port), &fileDescriptor)) {
|
|
addError(QString::fromLatin1("connectByPort on device %1 port %2 failed, AMDeviceStartService returned %3")
|
|
.arg(deviceId).arg(port).arg(error));
|
|
failure = true;
|
|
*fd = -1;
|
|
} else {
|
|
*fd = fileDescriptor;
|
|
}
|
|
disconnectDevice();
|
|
return !failure;
|
|
}
|
|
|
|
int CommandSession::qmljsDebugPort() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void CommandSession::stopService(ServiceSocket fd)
|
|
{
|
|
// would be close socket on windows
|
|
close(fd);
|
|
}
|
|
|
|
void CommandSession::startDeviceLookup(int timeout)
|
|
{
|
|
IosDeviceManagerPrivate::instance()->deviceWithId(deviceId, timeout,
|
|
&deviceAvailableSessionCallback, this);
|
|
}
|
|
|
|
void CommandSession::addError(const QString &msg)
|
|
{
|
|
if (verbose)
|
|
qDebug() << "CommandSession ERROR: " << msg;
|
|
IosDeviceManagerPrivate::instance()->addError(commandName() + msg);
|
|
}
|
|
|
|
bool CommandSession::writeAll(ServiceSocket fd, const char *cmd, qptrdiff len)
|
|
{
|
|
if (len == -1)
|
|
len = strlen(cmd);
|
|
if (debugGdbServer) {
|
|
QByteArray cmdBA(cmd,len);
|
|
qDebug() << "writeAll(" << fd << "," << QString::fromLocal8Bit(cmdBA.constData(), cmdBA.size())
|
|
<< " (" << cmdBA.toHex() << "))";
|
|
}
|
|
qptrdiff i = 0;
|
|
int maxRetry = 10;
|
|
while (i < len) {
|
|
qptrdiff nWritten = write(fd, cmd, len - i);
|
|
if (nWritten < 1) {
|
|
--maxRetry;
|
|
if (nWritten == -1 && errno != 0 && errno != EINTR) {
|
|
char buf[256];
|
|
if (!strerror_r(errno, buf, sizeof(buf))) {
|
|
buf[sizeof(buf)-1] = 0;
|
|
addError(QString::fromLocal8Bit(buf));
|
|
} else {
|
|
addError(QLatin1String("Unknown writeAll error"));
|
|
}
|
|
return false;
|
|
}
|
|
if (maxRetry <= 0) {
|
|
addError(QLatin1String("Hit maximum retries in writeAll"));
|
|
return false;
|
|
}
|
|
} else {
|
|
maxRetry = 10;
|
|
}
|
|
i += nWritten;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void mountCallback(CFDictionaryRef dict, int arg)
|
|
{
|
|
Q_UNUSED(arg)
|
|
CFStringRef cfStatus = reinterpret_cast<CFStringRef>(CFDictionaryGetValue(dict, CFSTR("Status")));
|
|
qDebug() << "Mounting dev Image :"<<QString::fromCFString(cfStatus);
|
|
}
|
|
|
|
bool CommandSession::mountDeveloperDiskImage() {
|
|
bool success = false;
|
|
QString imagePath;
|
|
QString signaturePath;
|
|
if (developerDiskImagePath(&imagePath, &signaturePath)) {
|
|
QFile sigFile(signaturePath);
|
|
if (sigFile.open(QFile::ReadOnly)) {
|
|
// Read the signature.
|
|
const QByteArray signatureData = sigFile.read(128);
|
|
sigFile.close();
|
|
|
|
CFDataRef sig_data = signatureData.toRawCFData();
|
|
CFTypeRef keys[] = { CFSTR("ImageSignature"), CFSTR("ImageType") };
|
|
CFTypeRef values[] = { sig_data, CFSTR("Developer") };
|
|
CFDictionaryRef options = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
|
|
2, &kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
|
|
if (connectDevice()) {
|
|
CFStringRef cfImgPath = imagePath.toCFString();
|
|
am_res_t result = lib()->deviceMountImage(device, cfImgPath, options, &mountCallback, 0);
|
|
if (result == 0 || result == kAMDMobileImageMounterImageMountFailed) {
|
|
// Mounting succeeded or developer image already installed
|
|
success = true;
|
|
} else {
|
|
addError(QString::fromLatin1("Mount Developer Disk Image \"%1\" failed, AMDeviceMountImage returned %2 (0x%3)")
|
|
.arg(imagePath).arg(mobileDeviceErrorString(lib(), result)).arg(QString::number(result, 16)));
|
|
}
|
|
CFRelease(cfImgPath);
|
|
disconnectDevice();
|
|
} else
|
|
addError(QString::fromLatin1("Mount Developer Disk Image \"%1\" failed. Cannot connect to device \"%2\".")
|
|
.arg(imagePath).arg(deviceId));
|
|
} else {
|
|
addError(QString::fromLatin1("Mount Developer Disk Image \"%1\" failed. Unable to open disk image.")
|
|
.arg(imagePath));
|
|
}
|
|
} else {
|
|
addError(QString::fromLatin1("Mount Developer Disk Image failed. Unable to fetch developer disk image path."));
|
|
}
|
|
return success;
|
|
}
|
|
|
|
bool CommandSession::sendGdbCommand(ServiceSocket fd, const char *cmd, qptrdiff len)
|
|
{
|
|
if (len == -1)
|
|
len = strlen(cmd);
|
|
unsigned char checkSum = 0;
|
|
for (int i = 0; i < len; ++i)
|
|
checkSum += static_cast<unsigned char>(cmd[i]);
|
|
bool failure = !writeAll(fd, "$", 1);
|
|
if (!failure)
|
|
failure = !writeAll(fd, cmd, len);
|
|
char buf[3];
|
|
buf[0] = '#';
|
|
const char *hex = "0123456789abcdef";
|
|
buf[1] = hex[(checkSum >> 4) & 0xF];
|
|
buf[2] = hex[checkSum & 0xF];
|
|
if (!failure)
|
|
failure = !writeAll(fd, buf, 3);
|
|
return !failure;
|
|
}
|
|
|
|
bool CommandSession::checkRead(qptrdiff nRead, int &maxRetry)
|
|
{
|
|
if (nRead < 1 || nRead > 4) {
|
|
--maxRetry;
|
|
if ((nRead < 0 || nRead > 4) && errno != 0 && errno != EINTR) {
|
|
char buf[256];
|
|
if (!strerror_r(errno, buf, sizeof(buf))) {
|
|
buf[sizeof(buf)-1] = 0;
|
|
addError(QString::fromLocal8Bit(buf));
|
|
} else {
|
|
addError(QLatin1String("Unknown writeAll error"));
|
|
}
|
|
return false;
|
|
}
|
|
if (maxRetry <= 0) {
|
|
addError(QLatin1String("Hit maximum retries in readGdbReply"));
|
|
return false;
|
|
}
|
|
} else {
|
|
maxRetry = 10;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int CommandSession::handleChar(int fd, QByteArray &res, char c, int status)
|
|
{
|
|
switch (status) {
|
|
case 0:
|
|
if (c == '$')
|
|
return 1;
|
|
if (c != '+' && c != '-') {
|
|
if (unexpectedChars < 10) {
|
|
addError(QString::fromLatin1("unexpected char %1 in readGdbReply looking for $")
|
|
.arg(QChar::fromLatin1(c)));
|
|
++unexpectedChars;
|
|
} else if (unexpectedChars == 10) {
|
|
addError(QString::fromLatin1("hit maximum number of unexpected chars, ignoring them in readGdbReply looking for $"));
|
|
++unexpectedChars;
|
|
}
|
|
}
|
|
return 0;
|
|
case 1:
|
|
if (c != '#') {
|
|
res.append(c);
|
|
return 1;
|
|
}
|
|
return 2;
|
|
case 2:
|
|
case 3:
|
|
if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' | c > 'F')) {
|
|
if (unexpectedChars < 15) {
|
|
addError(QString::fromLatin1("unexpected char %1 in readGdbReply as checksum")
|
|
.arg(QChar::fromLatin1(c)));
|
|
++unexpectedChars;
|
|
} else if (unexpectedChars == 15) {
|
|
addError(QString::fromLatin1("hit maximum number of unexpected chars in checksum, ignoring them in readGdbReply"));
|
|
++unexpectedChars;
|
|
}
|
|
}
|
|
if (status == 3 && aknowledge)
|
|
writeAll(fd, "+", 1);
|
|
return status + 1;
|
|
case 4:
|
|
addError(QString::fromLatin1("gone past end in readGdbReply"));
|
|
return 5;
|
|
case 5:
|
|
return 5;
|
|
default:
|
|
addError(QString::fromLatin1("unexpected status readGdbReply"));
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
QByteArray CommandSession::readGdbReply(ServiceSocket fd)
|
|
{
|
|
// read with minimal buffering because we might want to give the socket away...
|
|
QByteArray res;
|
|
char buf[5];
|
|
int maxRetry = 10;
|
|
int status = 0;
|
|
int toRead = 4;
|
|
while (status < 4 && toRead > 0) {
|
|
qptrdiff nRead = read(fd, buf, toRead);
|
|
if (!checkRead(nRead, maxRetry))
|
|
return QByteArray();
|
|
if (debugGdbServer) {
|
|
buf[nRead] = 0;
|
|
qDebug() << "gdbReply read " << buf;
|
|
}
|
|
for (qptrdiff i = 0; i< nRead; ++i)
|
|
status = handleChar(fd, res, buf[i], status);
|
|
toRead = 4 - status;
|
|
}
|
|
if (status != 4) {
|
|
addError(QString::fromLatin1("unexpected parser status %1 in readGdbReply").arg(status));
|
|
return QByteArray();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void CommandSession::reportProgress(CFDictionaryRef dict)
|
|
{
|
|
QString status;
|
|
CFStringRef cfStatus;
|
|
if (CFDictionaryGetValueIfPresent(dict, CFSTR("Status"), reinterpret_cast<const void **>(&cfStatus))) {
|
|
if (cfStatus && CFGetTypeID(cfStatus) == CFStringGetTypeID())
|
|
status = QString::fromCFString(cfStatus);
|
|
}
|
|
quint32 progress = 0;
|
|
CFNumberRef cfProgress;
|
|
if (CFDictionaryGetValueIfPresent(dict, CFSTR("PercentComplete"), reinterpret_cast<const void **>(&cfProgress))) {
|
|
if (cfProgress && CFGetTypeID(cfProgress) == CFNumberGetTypeID())
|
|
CFNumberGetValue(cfProgress,kCFNumberSInt32Type, reinterpret_cast<const void **>(&progress));
|
|
}
|
|
reportProgress2(progressBase + progress, status);
|
|
}
|
|
|
|
void CommandSession::reportProgress2(int progress, const QString &status)
|
|
{
|
|
Q_UNUSED(progress);
|
|
Q_UNUSED(status);
|
|
}
|
|
|
|
QString CommandSession::commandName()
|
|
{
|
|
return QString::fromLatin1("CommandSession(%1)").arg(deviceId);
|
|
}
|
|
|
|
bool CommandSession::expectGdbReply(ServiceSocket gdbFd, QByteArray expected)
|
|
{
|
|
QByteArray repl = readGdbReply(gdbFd);
|
|
if (repl != expected) {
|
|
addError(QString::fromLatin1("Unexpected reply: %1 (%2) vs %3 (%4)")
|
|
.arg(QString::fromLocal8Bit(repl.constData(), repl.size()))
|
|
.arg(QString::fromLatin1(repl.toHex().constData(), 2*repl.size()))
|
|
.arg(QString::fromLocal8Bit(expected.constData(), expected.size()))
|
|
.arg(QString::fromLocal8Bit(expected.toHex().constData(), 2*expected.size())));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CommandSession::expectGdbOkReply(ServiceSocket gdbFd)
|
|
{
|
|
return expectGdbReply(gdbFd, QByteArray("OK"));
|
|
}
|
|
|
|
MobileDeviceLib *CommandSession::lib()
|
|
{
|
|
return IosDeviceManagerPrivate::instance()->lib();
|
|
}
|
|
|
|
bool CommandSession::developerDiskImagePath(QString *path, QString *signaturePath)
|
|
{
|
|
if (device && path && connectDevice()) {
|
|
CFPropertyListRef cfProductVersion = lib()->deviceCopyValue(device, 0, CFSTR("ProductVersion"));
|
|
QString versionString;
|
|
if (cfProductVersion && CFGetTypeID(cfProductVersion) == CFStringGetTypeID()) {
|
|
versionString = QString::fromCFString(reinterpret_cast<CFStringRef>(cfProductVersion));
|
|
}
|
|
CFRelease(cfProductVersion);
|
|
|
|
CFPropertyListRef cfBuildVersion = lib()->deviceCopyValue(device, 0, CFSTR("BuildVersion"));
|
|
QString buildString;
|
|
if (cfBuildVersion && CFGetTypeID(cfBuildVersion) == CFStringGetTypeID()) {
|
|
buildString = QString::fromCFString(reinterpret_cast<CFStringRef>(cfBuildVersion));
|
|
}
|
|
CFRelease(cfBuildVersion);
|
|
disconnectDevice();
|
|
|
|
if (findDeveloperDiskImage(versionString, buildString, path)) {
|
|
if (debugAll)
|
|
qDebug() << "Developers disk image found at" << path;
|
|
if (signaturePath) {
|
|
*signaturePath = QString("%1.%2").arg(*path).arg("signature");
|
|
return QFile::exists(*signaturePath);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
AppOpSession::AppOpSession(const QString &deviceId, const QString &bundlePath,
|
|
const QStringList &extraArgs, IosDeviceManager::AppOp appOp):
|
|
CommandSession(deviceId), bundlePath(bundlePath), extraArgs(extraArgs), appOp(appOp)
|
|
{ }
|
|
|
|
QString AppOpSession::commandName()
|
|
{
|
|
return QString::fromLatin1("TransferAppSession(%1, %2)").arg(deviceId, bundlePath);
|
|
}
|
|
|
|
bool AppOpSession::installApp()
|
|
{
|
|
bool success = false;
|
|
if (device != 0) {
|
|
CFURLRef bundleUrl = QUrl::fromLocalFile(bundlePath).toCFURL();
|
|
CFStringRef key[1] = {CFSTR("PackageType")};
|
|
CFStringRef value[1] = {CFSTR("Developer")};
|
|
CFDictionaryRef options = CFDictionaryCreate(0, reinterpret_cast<const void**>(&key[0]),
|
|
reinterpret_cast<const void**>(&value[0]), 1,
|
|
&kCFTypeDictionaryKeyCallBacks,
|
|
&kCFTypeDictionaryValueCallBacks);
|
|
|
|
// Transfer bundle with secure API AMDeviceTransferApplication.
|
|
if (int error = lib()->deviceSecureTransferApplicationPath(0, device, bundleUrl, options,
|
|
&appSecureTransferSessionCallback,0)) {
|
|
addError(QString::fromLatin1("TransferAppSession(%1,%2) failed, AMDeviceTransferApplication returned %3 (0x%4)")
|
|
.arg(bundlePath, deviceId).arg(mobileDeviceErrorString(lib(), error)).arg(error));
|
|
success = false;
|
|
} else {
|
|
// App is transferred. Try installing.
|
|
if (connectDevice()) {
|
|
// Secure install app api requires device to be connected.
|
|
if (am_res_t error = lib()->deviceSecureInstallApplication(0, device, bundleUrl, options,
|
|
&appSecureTransferSessionCallback,0)) {
|
|
const QString errorString = mobileDeviceErrorString(lib(), error);
|
|
if (!errorString.isEmpty()) {
|
|
addError(errorString
|
|
+ QStringLiteral(" (0x")
|
|
+ QString::number(error, 16)
|
|
+ QStringLiteral(")"));
|
|
} else {
|
|
addError(QString::fromLatin1("InstallAppSession(%1,%2) failed, "
|
|
"AMDeviceInstallApplication returned 0x%3")
|
|
.arg(bundlePath, deviceId).arg(QString::number(error, 16)));
|
|
}
|
|
success = false;
|
|
} else {
|
|
// App is installed.
|
|
success = true;
|
|
}
|
|
disconnectDevice();
|
|
}
|
|
}
|
|
|
|
if (debugAll) {
|
|
qDebug() << "AMDeviceSecureTransferApplication finished request with " << (success ? "Success" : "Failure");
|
|
}
|
|
|
|
CFRelease(options);
|
|
CFRelease(bundleUrl);
|
|
|
|
progressBase += 100;
|
|
}
|
|
|
|
|
|
if (success) {
|
|
sleep(5); // after installation the device needs a bit of quiet....
|
|
}
|
|
|
|
if (debugAll) {
|
|
qDebug() << "AMDeviceSecureInstallApplication finished request with " << (success ? "Success" : "Failure");
|
|
}
|
|
|
|
IosDeviceManagerPrivate::instance()->didTransferApp(bundlePath, deviceId,
|
|
(success ? IosDeviceManager::Success : IosDeviceManager::Failure));
|
|
return success;
|
|
}
|
|
|
|
void AppOpSession::deviceCallbackReturned()
|
|
{
|
|
switch (appOp) {
|
|
case IosDeviceManager::None:
|
|
break;
|
|
case IosDeviceManager::Install:
|
|
installApp();
|
|
break;
|
|
case IosDeviceManager::InstallAndRun:
|
|
if (installApp())
|
|
runApp();
|
|
break;
|
|
case IosDeviceManager::Run:
|
|
runApp();
|
|
break;
|
|
}
|
|
}
|
|
|
|
int AppOpSession::qmljsDebugPort() const
|
|
{
|
|
QRegExp qmlPortRe = QRegExp(QLatin1String("-qmljsdebugger=port:([0-9]+)"));
|
|
foreach (const QString &arg, extraArgs) {
|
|
if (qmlPortRe.indexIn(arg) == 0) {
|
|
bool ok;
|
|
int res = qmlPortRe.cap(1).toInt(&ok);
|
|
if (ok && res >0 && res <= 0xFFFF)
|
|
return res;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool AppOpSession::runApp()
|
|
{
|
|
bool failure = (device == 0);
|
|
QString exe = appPathOnDevice();
|
|
ServiceSocket gdbFd = -1;
|
|
if (!mountDeveloperDiskImage()) {
|
|
addError(QString::fromLatin1("Running app \"%1\" failed. Mount developer disk failed.").arg(bundlePath));
|
|
failure = true;
|
|
}
|
|
if (!failure && !startService(QLatin1String("com.apple.debugserver"), gdbFd))
|
|
gdbFd = -1;
|
|
|
|
if (gdbFd > 0) {
|
|
// gdbServer protocol, see http://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html#Remote-Protocol
|
|
// and the lldb handling of that (with apple specific stuff)
|
|
// http://llvm.org/viewvc/llvm-project/lldb/trunk/tools/debugserver/source/RNBRemote.cpp
|
|
//failure = !sendGdbCommand(gdbFd, "QStartNoAckMode"); // avoid and send required aknowledgements?
|
|
//if (!failure) failure = !expectGdbOkReply(gdbFd);
|
|
if (!failure) failure = !sendGdbCommand(gdbFd, "QEnvironmentHexEncoded:"); // send the environment with a series of these commands...
|
|
if (!failure) failure = !sendGdbCommand(gdbFd, "QSetDisableASLR:1"); // avoid address randomization to debug
|
|
if (!failure) failure = !expectGdbOkReply(gdbFd);
|
|
QStringList args = extraArgs;
|
|
QByteArray runCommand("A");
|
|
args.insert(0, exe);
|
|
if (debugAll)
|
|
qDebug() << " trying to start " << args;
|
|
for (int iarg = 0; iarg < args.size(); ++iarg) {
|
|
if (iarg)
|
|
runCommand.append(',');
|
|
QByteArray arg = args.at(iarg).toLocal8Bit().toHex();
|
|
runCommand.append(QString::number(arg.size()).toLocal8Bit());
|
|
runCommand.append(',');
|
|
runCommand.append(QString::number(iarg).toLocal8Bit());
|
|
runCommand.append(',');
|
|
runCommand.append(arg);
|
|
}
|
|
if (!failure) failure = !sendGdbCommand(gdbFd, runCommand.constData(), runCommand.size());
|
|
if (!failure) failure = !expectGdbOkReply(gdbFd);
|
|
if (!failure) failure = !sendGdbCommand(gdbFd, "qLaunchSuccess");
|
|
if (!failure) failure = !expectGdbOkReply(gdbFd);
|
|
}
|
|
IosDeviceManagerPrivate::instance()->didStartApp(
|
|
bundlePath, deviceId,
|
|
(failure ? IosDeviceManager::Failure : IosDeviceManager::Success), gdbFd, this);
|
|
return !failure;
|
|
}
|
|
|
|
void AppOpSession::reportProgress2(int progress, const QString &status)
|
|
{
|
|
IosDeviceManagerPrivate::instance()->isTransferringApp(
|
|
bundlePath, deviceId, progress, status);
|
|
}
|
|
|
|
QString AppOpSession::appId()
|
|
{
|
|
QSettings settings(bundlePath + QLatin1String("/Info.plist"), QSettings::NativeFormat);
|
|
QString res = settings.value(QString::fromLatin1("CFBundleIdentifier")).toString();
|
|
if (debugAll)
|
|
qDebug() << "appId:" << res;
|
|
return res;
|
|
}
|
|
|
|
QString AppOpSession::appPathOnDevice()
|
|
{
|
|
QString res;
|
|
if (!connectDevice())
|
|
return QString();
|
|
CFDictionaryRef apps;
|
|
CFDictionaryRef options;
|
|
const void *attributes[3] = { (const void*)(CFSTR("CFBundleIdentifier")),
|
|
(const void*)(CFSTR("Path")), (const void*)(CFSTR("CFBundleExecutable")) };
|
|
CFArrayRef lookupKeys = CFArrayCreate(kCFAllocatorDefault, (const void**)(&attributes[0]), 3,
|
|
&kCFTypeArrayCallBacks);
|
|
CFStringRef attrKey = CFSTR("ReturnAttributes");
|
|
options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)(&attrKey),
|
|
(const void**)(&lookupKeys), 1,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
CFRelease(lookupKeys);
|
|
if (int err = lib()->deviceLookupApplications(device, options, &apps)) {
|
|
addError(QString::fromLatin1("app lookup failed, AMDeviceLookupApplications returned %1")
|
|
.arg(err));
|
|
}
|
|
CFRelease(options);
|
|
if (debugAll)
|
|
CFShow(apps);
|
|
if (apps && CFGetTypeID(apps) == CFDictionaryGetTypeID()) {
|
|
CFStringRef cfAppId = appId().toCFString();
|
|
CFDictionaryRef cfAppInfo = 0;
|
|
if (CFDictionaryGetValueIfPresent(apps, cfAppId, reinterpret_cast<const void**>(&cfAppInfo))) {
|
|
if (cfAppInfo && CFGetTypeID(cfAppInfo) == CFDictionaryGetTypeID()) {
|
|
CFStringRef cfPath, cfBundleExe;
|
|
QString path, bundleExe;
|
|
if (CFDictionaryGetValueIfPresent(cfAppInfo, CFSTR("Path"), reinterpret_cast<const void **>(&cfPath)))
|
|
path = QString::fromCFString(cfPath);
|
|
if (CFDictionaryGetValueIfPresent(cfAppInfo, CFSTR("CFBundleExecutable"), reinterpret_cast<const void **>(&cfBundleExe)))
|
|
bundleExe = QString::fromCFString(cfBundleExe);
|
|
if (!path.isEmpty() && ! bundleExe.isEmpty())
|
|
res = path + QLatin1Char('/') + bundleExe;
|
|
}
|
|
}
|
|
}
|
|
if (apps)
|
|
CFRelease(apps);
|
|
disconnectDevice();
|
|
if (res.isEmpty())
|
|
addError(QString::fromLatin1("failed to get app Path on device for bundle %1 with appId: %2")
|
|
.arg(bundlePath, appId()));
|
|
return res;
|
|
}
|
|
|
|
am_res_t AppOpSession::appTransferCallback(CFDictionaryRef dict)
|
|
{
|
|
if (debugAll)
|
|
qDebug() << "TransferAppSession::appTransferCallback";
|
|
reportProgress(dict);
|
|
return 0;
|
|
}
|
|
|
|
am_res_t AppOpSession::appInstallCallback(CFDictionaryRef dict)
|
|
{
|
|
if (debugAll)
|
|
qDebug() << "TransferAppSession::appInstallCallback";
|
|
reportProgress(dict);
|
|
return 0;
|
|
}
|
|
|
|
DevInfoSession::DevInfoSession(const QString &deviceId) : CommandSession(deviceId)
|
|
{ }
|
|
|
|
QString DevInfoSession::commandName()
|
|
{
|
|
return QString::fromLatin1("DevInfoSession(%1, %2)").arg(deviceId);
|
|
}
|
|
|
|
void DevInfoSession::deviceCallbackReturned()
|
|
{
|
|
if (debugAll)
|
|
qDebug() << "device available";
|
|
QMap<QString,QString> res;
|
|
QString deviceNameKey = QLatin1String("deviceName");
|
|
QString developerStatusKey = QLatin1String("developerStatus");
|
|
QString deviceConnectedKey = QLatin1String("deviceConnected");
|
|
QString osVersionKey = QLatin1String("osVersion");
|
|
bool failure = !device;
|
|
if (!failure) {
|
|
failure = !connectDevice();
|
|
if (!failure) {
|
|
res[deviceConnectedKey] = QLatin1String("YES");
|
|
CFPropertyListRef cfDeviceName = lib()->deviceCopyValue(device, 0,
|
|
CFSTR("DeviceName"));
|
|
// CFShow(cfDeviceName);
|
|
if (cfDeviceName) {
|
|
if (CFGetTypeID(cfDeviceName) == CFStringGetTypeID())
|
|
res[deviceNameKey] = QString::fromCFString(reinterpret_cast<CFStringRef>(cfDeviceName));
|
|
CFRelease(cfDeviceName);
|
|
}
|
|
if (!res.contains(deviceNameKey))
|
|
res[deviceNameKey] = QString();
|
|
}
|
|
if (!failure) {
|
|
CFPropertyListRef cfDevStatus = lib()->deviceCopyValue(device,
|
|
CFSTR("com.apple.xcode.developerdomain"),
|
|
CFSTR("DeveloperStatus"));
|
|
// CFShow(cfDevStatus);
|
|
if (cfDevStatus) {
|
|
if (CFGetTypeID(cfDevStatus) == CFStringGetTypeID())
|
|
res[developerStatusKey] = QString::fromCFString(reinterpret_cast<CFStringRef>(cfDevStatus));
|
|
CFRelease(cfDevStatus);
|
|
}
|
|
if (!res.contains(developerStatusKey))
|
|
res[developerStatusKey] = QLatin1String("*off*");
|
|
}
|
|
if (!failure) {
|
|
CFPropertyListRef cfProductVersion = lib()->deviceCopyValue(device,
|
|
0,
|
|
CFSTR("ProductVersion"));
|
|
//CFShow(cfProductVersion);
|
|
CFPropertyListRef cfBuildVersion = lib()->deviceCopyValue(device,
|
|
0,
|
|
CFSTR("BuildVersion"));
|
|
//CFShow(cfBuildVersion);
|
|
QString versionString;
|
|
if (cfProductVersion) {
|
|
if (CFGetTypeID(cfProductVersion) == CFStringGetTypeID())
|
|
versionString = QString::fromCFString(reinterpret_cast<CFStringRef>(cfProductVersion));
|
|
CFRelease(cfProductVersion);
|
|
}
|
|
if (cfBuildVersion) {
|
|
if (!versionString.isEmpty() && CFGetTypeID(cfBuildVersion) == CFStringGetTypeID())
|
|
versionString += QString::fromLatin1(" (%1)").arg(
|
|
QString::fromCFString(reinterpret_cast<CFStringRef>(cfBuildVersion)));
|
|
CFRelease(cfBuildVersion);
|
|
}
|
|
if (!versionString.isEmpty())
|
|
res[osVersionKey] = versionString;
|
|
else
|
|
res[osVersionKey] = QLatin1String("*unknown*");
|
|
}
|
|
disconnectDevice();
|
|
}
|
|
if (!res.contains(deviceConnectedKey))
|
|
res[deviceConnectedKey] = QLatin1String("NO");
|
|
if (!res.contains(deviceNameKey))
|
|
res[deviceNameKey] = QLatin1String("*unknown*");
|
|
if (!res.contains(developerStatusKey))
|
|
res[developerStatusKey] = QLatin1String("*unknown*");
|
|
if (debugAll)
|
|
qDebug() << "deviceInfo:" << res << ", failure:" << failure;
|
|
emit Ios::IosDeviceManager::instance()->deviceInfo(deviceId, res);
|
|
/* should we also check the provision profiles??? i.e.
|
|
int fd;
|
|
startService(QLatin1String("com.apple.misagent"), &fd);
|
|
... MISAgentCopyProvisioningProfiles, AMAuthInstallProvisioningGetProvisionedInfo & co still to add */
|
|
}
|
|
|
|
// ------- MobileDeviceLib implementation --------
|
|
|
|
MobileDeviceLib::MobileDeviceLib() { }
|
|
|
|
bool MobileDeviceLib::load()
|
|
{
|
|
#ifdef MOBILE_DEV_DIRECT_LINK
|
|
m_AMDSetLogLevel = &AMDSetLogLevel;
|
|
m_AMDeviceNotificationSubscribe = &AMDeviceNotificationSubscribe;
|
|
//m_AMDeviceNotificationUnsubscribe = &AMDeviceNotificationUnsubscribe;
|
|
m_AMDeviceCopyValue = &AMDeviceCopyValue;
|
|
m_AMDeviceGetConnectionID = &AMDeviceGetConnectionID;
|
|
m_AMDeviceCopyDeviceIdentifier = &AMDeviceCopyDeviceIdentifier;
|
|
m_AMDeviceConnect = &AMDeviceConnect;
|
|
//m_AMDevicePair = &AMDevicePair;
|
|
m_AMDeviceIsPaired = &AMDeviceIsPaired;
|
|
m_AMDeviceValidatePairing = &AMDeviceValidatePairing;
|
|
m_AMDeviceStartSession = &AMDeviceStartSession;
|
|
m_AMDeviceStopSession = &AMDeviceStopSession;
|
|
m_AMDeviceDisconnect = &AMDeviceDisconnect;
|
|
m_AMDeviceMountImage = &AMDeviceMountImage;
|
|
m_AMDeviceStartService = &AMDeviceStartService;
|
|
m_AMDeviceTransferApplication = &AMDeviceTransferApplication;
|
|
m_AMDeviceInstallApplication = &AMDeviceInstallApplication;
|
|
//m_AMDeviceUninstallApplication = &AMDeviceUninstallApplication;
|
|
//m_AMDeviceLookupApplications = &AMDeviceLookupApplications;
|
|
m_USBMuxConnectByPort = &USBMuxConnectByPort;
|
|
#else
|
|
QLibrary *libAppleFSCompression = new QLibrary(QLatin1String("/System/Library/PrivateFrameworks/AppleFSCompression.framework/AppleFSCompression"));
|
|
if (!libAppleFSCompression->load())
|
|
addError("MobileDevice dependency AppleFSCompression failed to load");
|
|
deps << libAppleFSCompression;
|
|
QLibrary *libBom = new QLibrary(QLatin1String("/System/Library/PrivateFrameworks/Bom.framework/Bom"));
|
|
if (!libBom->load())
|
|
addError("MobileDevice dependency Bom failed to load");
|
|
deps << libBom;
|
|
lib.setFileName(QLatin1String("/System/Library/PrivateFrameworks/MobileDevice.framework/MobileDevice"));
|
|
if (!lib.load())
|
|
return false;
|
|
m_AMDSetLogLevel = reinterpret_cast<AMDSetLogLevelPtr>(lib.resolve("AMDSetLogLevel"));
|
|
if (m_AMDSetLogLevel == 0)
|
|
addError("MobileDeviceLib does not define AMDSetLogLevel");
|
|
m_AMDeviceNotificationSubscribe = reinterpret_cast<AMDeviceNotificationSubscribePtr>(lib.resolve("AMDeviceNotificationSubscribe"));
|
|
if (m_AMDeviceNotificationSubscribe == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceNotificationSubscribe");
|
|
m_AMDeviceNotificationUnsubscribe = reinterpret_cast<AMDeviceNotificationUnsubscribePtr>(lib.resolve("AMDeviceNotificationUnsubscribe"));
|
|
if (m_AMDeviceNotificationUnsubscribe == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceNotificationUnsubscribe");
|
|
m_AMDeviceGetInterfaceType = reinterpret_cast<AMDeviceGetInterfaceTypePtr>(lib.resolve("AMDeviceGetInterfaceType"));
|
|
if (m_AMDeviceGetInterfaceType == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceGetInterfaceType");
|
|
m_AMDeviceCopyValue = reinterpret_cast<AMDeviceCopyValuePtr>(lib.resolve("AMDeviceCopyValue"));
|
|
if (m_AMDSetLogLevel == 0)
|
|
addError("MobileDeviceLib does not define AMDSetLogLevel");
|
|
m_AMDeviceGetConnectionID = reinterpret_cast<AMDeviceGetConnectionIDPtr>(lib.resolve("AMDeviceGetConnectionID"));
|
|
if (m_AMDeviceGetConnectionID == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceGetConnectionID");
|
|
m_AMDeviceCopyDeviceIdentifier = reinterpret_cast<AMDeviceCopyDeviceIdentifierPtr>(lib.resolve("AMDeviceCopyDeviceIdentifier"));
|
|
if (m_AMDeviceCopyDeviceIdentifier == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceCopyDeviceIdentifier");
|
|
m_AMDeviceConnect = reinterpret_cast<AMDeviceConnectPtr>(lib.resolve("AMDeviceConnect"));
|
|
if (m_AMDeviceConnect == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceConnect");
|
|
m_AMDevicePair = reinterpret_cast<AMDevicePairPtr>(lib.resolve("AMDevicePair"));
|
|
if (m_AMDevicePair == 0)
|
|
addError("MobileDeviceLib does not define AMDevicePair");
|
|
m_AMDeviceIsPaired = reinterpret_cast<AMDeviceIsPairedPtr>(lib.resolve("AMDeviceIsPaired"));
|
|
if (m_AMDeviceIsPaired == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceIsPaired");
|
|
m_AMDeviceValidatePairing = reinterpret_cast<AMDeviceValidatePairingPtr>(lib.resolve("AMDeviceValidatePairing"));
|
|
if (m_AMDeviceValidatePairing == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceValidatePairing");
|
|
m_AMDeviceStartSession = reinterpret_cast<AMDeviceStartSessionPtr>(lib.resolve("AMDeviceStartSession"));
|
|
if (m_AMDeviceStartSession == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceStartSession");
|
|
m_AMDeviceStopSession = reinterpret_cast<AMDeviceStopSessionPtr>(lib.resolve("AMDeviceStopSession"));
|
|
if (m_AMDeviceStopSession == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceStopSession");
|
|
m_AMDeviceDisconnect = reinterpret_cast<AMDeviceDisconnectPtr>(lib.resolve("AMDeviceDisconnect"));
|
|
if (m_AMDeviceDisconnect == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceDisconnect");
|
|
m_AMDeviceMountImage = reinterpret_cast<AMDeviceMountImagePtr>(lib.resolve("AMDeviceMountImage"));
|
|
if (m_AMDeviceMountImage == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceMountImage");
|
|
m_AMDeviceStartService = reinterpret_cast<AMDeviceStartServicePtr>(lib.resolve("AMDeviceStartService"));
|
|
if (m_AMDeviceStartService == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceStartService");
|
|
m_AMDeviceSecureStartService = reinterpret_cast<AMDeviceSecureStartServicePtr>(lib.resolve("AMDeviceSecureStartService"));
|
|
if (m_AMDeviceSecureStartService == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceSecureStartService");
|
|
m_AMDeviceSecureTransferPath = reinterpret_cast<AMDeviceSecureTransferPathPtr>(lib.resolve("AMDeviceSecureTransferPath"));
|
|
if (m_AMDeviceSecureTransferPath == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceSecureTransferPath");
|
|
m_AMDeviceSecureInstallApplication = reinterpret_cast<AMDeviceSecureInstallApplicationPtr>(lib.resolve("AMDeviceSecureInstallApplication"));
|
|
if (m_AMDeviceSecureInstallApplication == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceSecureInstallApplication");
|
|
m_AMDeviceUninstallApplication = reinterpret_cast<AMDeviceUninstallApplicationPtr>(lib.resolve("AMDeviceUninstallApplication"));
|
|
if (m_AMDeviceUninstallApplication == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceUninstallApplication");
|
|
m_AMDeviceLookupApplications = reinterpret_cast<AMDeviceLookupApplicationsPtr>(lib.resolve("AMDeviceLookupApplications"));
|
|
if (m_AMDeviceLookupApplications == 0)
|
|
addError("MobileDeviceLib does not define AMDeviceLookupApplications");
|
|
m_AMDErrorString = reinterpret_cast<AMDErrorStringPtr>(lib.resolve("AMDErrorString"));
|
|
if (m_AMDErrorString == 0)
|
|
addError("MobileDeviceLib does not define AMDErrorString");
|
|
m_MISCopyErrorStringForErrorCode = reinterpret_cast<MISCopyErrorStringForErrorCodePtr>(lib.resolve("MISCopyErrorStringForErrorCode"));
|
|
if (m_MISCopyErrorStringForErrorCode == 0)
|
|
addError("MobileDeviceLib does not define MISCopyErrorStringForErrorCode");
|
|
m_USBMuxConnectByPort = reinterpret_cast<USBMuxConnectByPortPtr>(lib.resolve("USBMuxConnectByPort"));
|
|
if (m_USBMuxConnectByPort == 0)
|
|
addError("MobileDeviceLib does not define USBMuxConnectByPort");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool MobileDeviceLib::isLoaded()
|
|
{
|
|
return lib.isLoaded();
|
|
}
|
|
|
|
QStringList MobileDeviceLib::errors()
|
|
{
|
|
return m_errors;
|
|
}
|
|
|
|
void MobileDeviceLib::setLogLevel(int i)
|
|
{
|
|
if (m_AMDSetLogLevel)
|
|
m_AMDSetLogLevel(i);
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceNotificationSubscribe(AMDeviceNotificationCallback callback,
|
|
unsigned int v1, unsigned int v2, void *callbackArgs,
|
|
const AMDeviceNotification **handle)
|
|
{
|
|
if (m_AMDeviceNotificationSubscribe)
|
|
return m_AMDeviceNotificationSubscribe(callback,v1,v2,callbackArgs,handle);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceNotificationUnsubscribe(void *handle)
|
|
{
|
|
if (m_AMDeviceNotificationUnsubscribe)
|
|
return m_AMDeviceNotificationUnsubscribe(handle);
|
|
return -1;
|
|
}
|
|
|
|
int MobileDeviceLib::deviceGetInterfaceType(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceGetInterfaceType)
|
|
return m_AMDeviceGetInterfaceType(device);
|
|
return DeviceInterfaceType::UNKNOWN;
|
|
}
|
|
|
|
CFPropertyListRef MobileDeviceLib::deviceCopyValue(AMDeviceRef device,CFStringRef group,CFStringRef key)
|
|
{
|
|
if (m_AMDeviceCopyValue)
|
|
return m_AMDeviceCopyValue(device, group, key);
|
|
return 0;
|
|
}
|
|
|
|
unsigned int MobileDeviceLib::deviceGetConnectionID(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceGetConnectionID)
|
|
return m_AMDeviceGetConnectionID(device);
|
|
return -1;
|
|
}
|
|
|
|
CFStringRef MobileDeviceLib::deviceCopyDeviceIdentifier(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceCopyDeviceIdentifier)
|
|
return m_AMDeviceCopyDeviceIdentifier(device);
|
|
return 0;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceConnect(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceConnect)
|
|
return m_AMDeviceConnect(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::devicePair(AMDeviceRef device)
|
|
{
|
|
if (m_AMDevicePair)
|
|
return m_AMDevicePair(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceIsPaired(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceIsPaired)
|
|
return m_AMDeviceIsPaired(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceValidatePairing(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceValidatePairing)
|
|
return m_AMDeviceValidatePairing(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceStartSession(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceStartSession)
|
|
return m_AMDeviceStartSession(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceStopSession(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceStopSession)
|
|
return m_AMDeviceStopSession(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceDisconnect(AMDeviceRef device)
|
|
{
|
|
if (m_AMDeviceDisconnect)
|
|
return m_AMDeviceDisconnect(device);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceMountImage(AMDeviceRef device, CFStringRef imagePath,
|
|
CFDictionaryRef options,
|
|
AMDeviceMountImageCallback callback,
|
|
void *callbackExtraArgs)
|
|
{
|
|
if (m_AMDeviceMountImage)
|
|
return m_AMDeviceMountImage(device, imagePath, options, callback, callbackExtraArgs);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceStartService(AMDeviceRef device, CFStringRef serviceName,
|
|
ServiceSocket *fdRef, void *extra)
|
|
{
|
|
if (m_AMDeviceStartService)
|
|
return m_AMDeviceStartService(device, serviceName, fdRef, extra);
|
|
return -1;
|
|
}
|
|
|
|
|
|
am_res_t MobileDeviceLib::deviceUninstallApplication(int serviceFd, CFStringRef bundleId,
|
|
CFDictionaryRef options,
|
|
AMDeviceInstallApplicationCallback callback,
|
|
void *callbackExtraArgs)
|
|
{
|
|
if (m_AMDeviceUninstallApplication)
|
|
return m_AMDeviceUninstallApplication(serviceFd, bundleId, options, callback, callbackExtraArgs);
|
|
return -1;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceLookupApplications(AMDeviceRef device, CFDictionaryRef options,
|
|
CFDictionaryRef *res)
|
|
{
|
|
if (m_AMDeviceLookupApplications)
|
|
return m_AMDeviceLookupApplications(device, options, res);
|
|
return -1;
|
|
}
|
|
|
|
char *MobileDeviceLib::errorString(am_res_t error)
|
|
{
|
|
if (m_AMDErrorString)
|
|
return m_AMDErrorString(error);
|
|
return 0;
|
|
}
|
|
|
|
CFStringRef MobileDeviceLib::misErrorStringForErrorCode(am_res_t error)
|
|
{
|
|
if (m_MISCopyErrorStringForErrorCode)
|
|
return m_MISCopyErrorStringForErrorCode(error);
|
|
return NULL;
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::connectByPort(unsigned int connectionId, int port, ServiceSocket *resFd)
|
|
{
|
|
if (m_USBMuxConnectByPort)
|
|
return m_USBMuxConnectByPort(connectionId, port, resFd);
|
|
return -1;
|
|
}
|
|
|
|
void MobileDeviceLib::addError(const QString &msg)
|
|
{
|
|
qDebug() << "MobileDeviceLib ERROR:" << msg;
|
|
m_errors << QLatin1String("MobileDeviceLib ERROR:") << msg;
|
|
}
|
|
|
|
void MobileDeviceLib::addError(const char *msg)
|
|
{
|
|
addError(QLatin1String(msg));
|
|
}
|
|
|
|
am_res_t MobileDeviceLib::deviceSecureStartService(AMDeviceRef device, CFStringRef serviceName, void *extra, ServiceSocket *fdRef)
|
|
{
|
|
int returnCode = -1;
|
|
if (m_AMDeviceSecureStartService)
|
|
returnCode = m_AMDeviceSecureStartService(device, serviceName, extra, fdRef);
|
|
return returnCode;
|
|
}
|
|
|
|
int MobileDeviceLib::deviceSecureTransferApplicationPath(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef dict, AMDeviceSecureInstallApplicationCallback callback, int args)
|
|
{
|
|
int returnCode = -1;
|
|
if (m_AMDeviceSecureTransferPath)
|
|
returnCode = m_AMDeviceSecureTransferPath(zero, device, url, dict, callback, args);
|
|
return returnCode;
|
|
}
|
|
|
|
int MobileDeviceLib::deviceSecureInstallApplication(int zero, AMDeviceRef device, CFURLRef url, CFDictionaryRef options, AMDeviceSecureInstallApplicationCallback callback, int arg)
|
|
{
|
|
int returnCode = -1;
|
|
if (m_AMDeviceSecureInstallApplication) {
|
|
returnCode = m_AMDeviceSecureInstallApplication(zero, device, url, options, callback, arg);
|
|
}
|
|
return returnCode;
|
|
}
|
|
|
|
void CommandSession::internalDeviceAvailableCallback(QString deviceId, AMDeviceRef device)
|
|
{
|
|
if (deviceId != this->deviceId && !this->deviceId.isEmpty())
|
|
addError(QString::fromLatin1("deviceId mismatch in deviceAvailableCallback, %1 vs %2")
|
|
.arg(deviceId, this->deviceId));
|
|
this->deviceId = deviceId;
|
|
if (this->device)
|
|
addError(QString::fromLatin1("session had non null device in deviceAvailableCallback"));
|
|
this->device = device;
|
|
deviceCallbackReturned();
|
|
}
|
|
|
|
} // namespace Internal
|
|
|
|
// ------- IosManager implementation (just forwarding) --------
|
|
|
|
IosDeviceManager *IosDeviceManager::instance()
|
|
{
|
|
static IosDeviceManager instanceVal;
|
|
return &instanceVal;
|
|
}
|
|
|
|
IosDeviceManager::IosDeviceManager(QObject *parent) :
|
|
QObject(parent)
|
|
{
|
|
d = new Internal::IosDeviceManagerPrivate(this);
|
|
}
|
|
|
|
bool IosDeviceManager::watchDevices() {
|
|
return d->watchDevices();
|
|
}
|
|
|
|
void IosDeviceManager::requestAppOp(const QString &bundlePath, const QStringList &extraArgs,
|
|
AppOp appOp, const QString &deviceId, int timeout) {
|
|
d->requestAppOp(bundlePath, extraArgs, appOp, deviceId, timeout);
|
|
}
|
|
|
|
void IosDeviceManager::requestDeviceInfo(const QString &deviceId, int timeout)
|
|
{
|
|
d->requestDeviceInfo(deviceId, timeout);
|
|
}
|
|
|
|
int IosDeviceManager::processGdbServer(int fd)
|
|
{
|
|
return d->processGdbServer(fd);
|
|
}
|
|
|
|
void IosDeviceManager::stopGdbServer(int fd, int phase)
|
|
{
|
|
return d->stopGdbServer(fd, phase);
|
|
}
|
|
|
|
QStringList IosDeviceManager::errors() {
|
|
return d->errors();
|
|
}
|
|
|
|
void IosDeviceManager::checkPendingLookups()
|
|
{
|
|
d->checkPendingLookups();
|
|
}
|
|
|
|
} // namespace Ios
|