diff --git a/src/afcexplorerwidget.cpp b/src/afcexplorerwidget.cpp index 9d49691..e4f43b3 100644 --- a/src/afcexplorerwidget.cpp +++ b/src/afcexplorerwidget.cpp @@ -204,8 +204,8 @@ void AfcExplorerWidget::loadPath(const QString &path) updateAddressBar(path); updateNavigationButtons(); - AFCFileTree tree = - ServiceManager::safeGetFileTree(m_device, path.toStdString(), m_afc); + AFCFileTree tree = ServiceManager::safeGetFileTree( + m_device, path.toStdString(), true, m_afc); if (!tree.success) { showErrorState(); return; diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 970f8eb..42b7953 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -266,7 +266,6 @@ void AppContext::addDevice(QString udid, .mutex = new std::recursive_mutex(), .imageMounter = initResult->imageMounter, .diagRelay = initResult->diagRelay, - .screenshotrClient = initResult->screenshotrClient, .locationSimulation = initResult->locationSimulation}; m_devices[device->udid] = device; if (addType == AddType::Regular) { diff --git a/src/cableinfowidget.cpp b/src/cableinfowidget.cpp index 0c4a336..3d62c8a 100644 --- a/src/cableinfowidget.cpp +++ b/src/cableinfowidget.cpp @@ -30,7 +30,6 @@ CableInfoWidget::CableInfoWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device), m_response(nullptr) { setupUI(); - initCableInfo(); connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) { if (m_device->udid == udid) { @@ -38,12 +37,13 @@ CableInfoWidget::CableInfoWidget(iDescriptorDevice *device, QWidget *parent) this->deleteLater(); } }); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); } void CableInfoWidget::setupUI() { setWindowTitle("Cable Information - iDescriptor"); - m_mainLayout = new QVBoxLayout(this); + m_mainLayout = new QVBoxLayout(); m_mainLayout->setSpacing(20); m_mainLayout->setContentsMargins(20, 20, 20, 20); @@ -58,7 +58,14 @@ void CableInfoWidget::setupUI() new QLabel("Please wait while we analyze the connected cable."); m_descriptionLabel->setStyleSheet("font-size: 9px;"); + QPushButton *redoButton = new QPushButton("Re-analyze"); + connect(redoButton, &QPushButton::clicked, this, [this]() { + m_loadingWidget->showLoading(); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); + }); headerLayout->addWidget(m_statusLabel); + headerLayout->addStretch(); + headerLayout->addWidget(redoButton); m_mainLayout->addLayout(headerLayout); @@ -71,14 +78,38 @@ void CableInfoWidget::setupUI() m_mainLayout->addWidget(m_descriptionLabel); m_mainLayout->addWidget(m_infoWidget); m_mainLayout->addStretch(); + m_loadingWidget = new ZLoadingWidget(true, this); + m_loadingWidget->setupContentWidget(m_mainLayout); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_loadingWidget); + + QVBoxLayout *errorLayout = new QVBoxLayout(); + m_errorLabel = new QLabel(); + m_errorLabel->setAlignment(Qt::AlignCenter); + errorLayout->addStretch(); + errorLayout->addWidget(m_errorLabel); + + QPushButton *retryButton = new QPushButton("Retry"); + retryButton->setMaximumWidth(retryButton->sizeHint().width()); + connect(retryButton, &QPushButton::clicked, this, [this]() { + m_loadingWidget->showLoading(); + QTimer::singleShot(200, this, &CableInfoWidget::initCableInfo); + }); + errorLayout->addWidget(retryButton, 0, Qt::AlignHCenter); + errorLayout->addStretch(); + + m_loadingWidget->setupErrorWidget(errorLayout); } void CableInfoWidget::initCableInfo() { if (!m_device) { - m_statusLabel->setText("Something went wrong (no device ?)"); - m_statusLabel->setStyleSheet( + m_errorLabel->setText("Something went wrong (no device ?)"); + m_errorLabel->setStyleSheet( "QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }"); + m_loadingWidget->showError(); return; } @@ -104,8 +135,7 @@ void CableInfoWidget::analyzeCableInfo() if (!ioreg.valid()) { return; } - - m_cableInfo.isConnected = true; + m_cableInfo.isConnected = ioreg["ConnectionActive"].getBool(); // Check if genuine (Apple manufacturer and valid model info) m_cableInfo.manufacturer = QString::fromStdString( @@ -197,6 +227,13 @@ void CableInfoWidget::updateUI() delete item; } + if (!m_cableInfo.isConnected) { + m_errorLabel->setText( + "Device does not seem to be connected to any cable."); + m_loadingWidget->showError(); + return; + } + // Update status and icon based on cable type QString statusText; QString statusStyle; @@ -204,6 +241,7 @@ void CableInfoWidget::updateUI() m_descriptionLabel->setText("Please note that this check may not be " "absolute guarantee of authenticity."); if (m_cableInfo.isGenuine) { + // todo: type-c to type-c statusText = QString("✅ Genuine %1") .arg(m_cableInfo.isTypeC ? "USB-C to Lightning Cable" : "Lightning Cable"); @@ -284,6 +322,7 @@ void CableInfoWidget::updateUI() createInfoRow(m_infoLayout, row++, "Supported Transports:", m_cableInfo.supportedTransports.join(", ")); } + m_loadingWidget->stop(true); } void CableInfoWidget::createInfoRow(QGridLayout *layout, int row, diff --git a/src/cableinfowidget.h b/src/cableinfowidget.h index f9aee59..be6de40 100644 --- a/src/cableinfowidget.h +++ b/src/cableinfowidget.h @@ -21,6 +21,7 @@ #define CABLEINFOWIDGET_H #include "iDescriptor.h" +#include "zloadingwidget.h" #include #include #include @@ -73,6 +74,8 @@ private: QLabel *m_descriptionLabel; QGroupBox *m_infoWidget; QGridLayout *m_infoLayout; + ZLoadingWidget *m_loadingWidget; + QLabel *m_errorLabel; // Data iDescriptorDevice *m_device; diff --git a/src/core/services/get_file_tree.cpp b/src/core/services/get_file_tree.cpp index 1bf3021..28afbb8 100644 --- a/src/core/services/get_file_tree.cpp +++ b/src/core/services/get_file_tree.cpp @@ -24,7 +24,8 @@ #include AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, - const std::string &path) + const std::string &path, + std::optional altAfc) { qDebug() << "Getting file tree for path:" << QString::fromStdString(path); AFCFileTree result; @@ -35,8 +36,8 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, size_t count = 0; // Use safe wrapper to read directory - IdeviceFfiError *err = - ServiceManager::safeAfcReadDirectory(device, path.c_str(), &dirs); + IdeviceFfiError *err = ServiceManager::safeAfcReadDirectory( + device, path.c_str(), &dirs, count, altAfc); if (err) { qDebug() << "Failed to read directory:" << path.c_str() @@ -52,7 +53,7 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, // Iterate through directory entries for (int i = 0; dirs[i]; i++) { - qDebug() << "Found entry:" << dirs[i]; + // qDebug() << "Found entry:" << dirs[i]; std::string entryName = dirs[i]; if (entryName == "." || entryName == "..") continue; @@ -69,8 +70,8 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, // Get file info using safe wrapper AfcFileInfo info = {}; - IdeviceFfiError *info_err = - ServiceManager::safeAfcGetFileInfo(device, fullPath.c_str(), &info); + IdeviceFfiError *info_err = ServiceManager::safeAfcGetFileInfo( + device, fullPath.c_str(), &info, altAfc); if (info_err) { qDebug() << "Failed to get file info for:" << fullPath.c_str() @@ -80,18 +81,20 @@ AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, bool isDir = false; if (!info_err) { - qDebug() << "Entry:" << entryName.c_str() << "Type:" << info.st_ifmt - << "Size:" << info.size; + // qDebug() << "Entry:" << entryName.c_str() << "Type:" << + // info.st_ifmt + // << "Size:" << info.size; if (strcmp(info.st_ifmt, "S_IFDIR") == 0) { isDir = true; } else if (strcmp(info.st_ifmt, "S_IFLNK") == 0) { // Check if symlink points to a directory char **dir_contents = nullptr; + size_t count = 0; // FIXME: recursively call safeAfcGetFileInfo to figure out if // it's a dir IdeviceFfiError *link_err = ServiceManager::safeAfcReadDirectory( - device, fullPath.c_str(), &dir_contents); + device, fullPath.c_str(), &dir_contents, count, altAfc); if (!link_err) { isDir = true; diff --git a/src/core/services/init_device.cpp b/src/core/services/init_device.cpp index b502eee..7242920 100644 --- a/src/core/services/init_device.cpp +++ b/src/core/services/init_device.cpp @@ -390,7 +390,6 @@ init_idescriptor_device(const QString &udid, HeartbeatThread *heartbeatThread = nullptr; ImageMounterHandle *image_mounter = nullptr; DiagnosticsRelayClientHandle *diagnostics_relay = nullptr; - ScreenshotrClientHandle *screenshotr_client = nullptr; LocationSimulationHandle *location_simulation = nullptr; plist_t val = nullptr; @@ -515,13 +514,6 @@ init_idescriptor_device(const QString &udid, goto cleanup; } - // err = screenshotr_connect(provider, &screenshotr_client); - - // if (err) { - // qDebug() << "Failed to create Screenshotr client"; - // goto cleanup; - // } - err = afc2_client_connect(provider, &afc2_client); if (err) { qDebug() << "Failed to create AFC2 client"; @@ -547,8 +539,9 @@ init_idescriptor_device(const QString &udid, result.afcClient = afc_client; result.afc2Client = afc2_client; result.lockdown = lockdown; + // TODO:remove, not really required to get some stuff going so it can be + // optional result.imageMounter = image_mounter; - result.screenshotrClient = screenshotr_client; result.diagRelay = std::make_shared( DiagnosticsRelay::adopt(diagnostics_relay)); result.locationSimulation = location_simulation; diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 62e165b..95cab5b 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -18,8 +18,10 @@ */ #include "devdiskimagehelper.h" +#include "appcontext.h" #include "devdiskmanager.h" #include "qprocessindicator.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -29,8 +31,7 @@ DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device, QWidget *parent) - : QDialog(parent), m_device(device), m_isDownloading(false), - m_isMounting(false) + : QDialog(parent), m_device(device) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle("Developer Disk Image - iDescriptor"); @@ -113,7 +114,9 @@ void DevDiskImageHelper::start() }); qDebug() << "isMountAvailable:" << isMountAvailable; if (!isMountAvailable) { - finishWithError("Failed to download compatible image."); + finishWithError( + "There is no compatible developer disk image available for " + + QString::number(deviceMajorVersion) + "."); } } else { finishWithSuccess(); @@ -123,22 +126,20 @@ void DevDiskImageHelper::start() void DevDiskImageHelper::checkAndMount() { - // GetMountedImageResult result = - // DevDiskManager::sharedInstance()->getMountedImage(m_device); - // qDebug() << "checkAndMount result:" << result.success - // << result.message.c_str() << QString::fromStdString(result.sig); - // if (!result.success) { - // showRetryUI(QString::fromStdString(result.message)); - // return; - // } + MountedImageInfo info = ServiceManager::getMountedImage( + AppContext::sharedInstance()->getDevice(m_device->udid)); + if (info.err && info.err->code != NotFoundErrorCode) { + onMountButtonClicked(); + return; + } - // // If image is already mounted - // if (!result.sig.empty()) { - // finishWithSuccess(); - // return; - // } + // If image is already mounted + if (info.signature && info.signature_len) { + finishWithSuccess(); + return; + } - // onMountButtonClicked(); + onMountButtonClicked(); } void DevDiskImageHelper::onMountButtonClicked() @@ -146,7 +147,6 @@ void DevDiskImageHelper::onMountButtonClicked() QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); m_mountButton->setVisible(false); m_loadingIndicator->start(); - m_isMounting = true; // Check if we need to download first unsigned int deviceMajorVersion = @@ -174,32 +174,13 @@ void DevDiskImageHelper::onMountButtonClicked() if (hasDownloadedImage) { // // Mount directly - // showStatus("Mounting developer disk image..."); - - // mobile_image_mounter_error_t err = - // DevDiskManager::sharedInstance()->mountImage(versionToMount, - // m_device); - - // m_isMounting = false; - // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - // showStatus("Developer disk image mounted successfully"); - // finishWithSuccess(); - // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - // showRetryUI( - // "Device is locked. Please unlock your device and try - // again."); - // } else { - // showRetryUI("Failed to mount developer disk image.\n" - // "Please ensure:\n" - // "• Device is unlocked\n" - // "• Using a genuine cable\n" - // "• Developer mode is enabled (iOS 16+)"); - // } + m_downloadingVersion = versionToMount; + showStatus("Mounting developer disk image..."); + onImageDownloadFinished(versionToMount, true, ""); } else { // Need to download first showStatus( "Downloading developer disk image...\nThis may take a moment."); - m_isDownloading = true; // Connect to download signals connect(DevDiskManager::sharedInstance(), @@ -222,36 +203,43 @@ void DevDiskImageHelper::onImageDownloadFinished(const QString &version, bool success, const QString &errorMessage) { - if (!m_isDownloading || version != m_downloadingVersion) { + if (version != m_downloadingVersion) { + qDebug() << "Ignoring download finished for version" << version + << "expected" << m_downloadingVersion; return; } - m_isDownloading = false; - if (!success) { showRetryUI("Failed to download developer disk image:\n" + errorMessage); return; } - // Download successful, now mount showStatus("Download complete. Mounting..."); - // mobile_image_mounter_error_t err = - // DevDiskManager::sharedInstance()->mountImage(version, m_device); + auto paths = DevDiskManager::sharedInstance()->getPathsForVersion(version); - // if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) { - // showStatus("Developer disk image mounted successfully"); - // finishWithSuccess(); - // } else if (err == MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED) { - // showRetryUI( - // "Device is locked. Please unlock your device and try again."); - // } else { - // showRetryUI( - // "Failed to mount developer disk image.\n" - // "Please ensure the device is unlocked and using a genuine - // cable."); - // } + IdeviceFfiError *err = + ServiceManager::mountImage(m_device, paths.first.toStdString().c_str(), + paths.second.toStdString().c_str()); + + if (err == nullptr) { + return finishWithSuccess(); + } + + qDebug() << "onImageDownloadFinished:" << err->code + << QString::fromStdString(err->message); + + if (err->code == DeviceLockedMountErrorCode) { + showRetryUI( + "Device is locked. Please unlock your device and try again."); + + } else { + showRetryUI( + "Failed to mount developer disk image.\n" + "Please ensure the device is unlocked and using a genuine cable."); + } + idevice_error_free(err); } void DevDiskImageHelper::showRetryUI(const QString &errorMessage) diff --git a/src/devdiskimagehelper.h b/src/devdiskimagehelper.h index 9bfbedf..a589448 100644 --- a/src/devdiskimagehelper.h +++ b/src/devdiskimagehelper.h @@ -66,8 +66,6 @@ private: QPushButton *m_retryButton; QPushButton *m_cancelButton; - bool m_isDownloading; - bool m_isMounting; QString m_downloadingVersion; }; diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 5404f58..93a72bb 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -606,17 +606,11 @@ void DevDiskImagesWidget::mountImage(const QString &version) .arg(m_deviceComboBox->currentText())); return updateUI(); } else if (info.err->code == DeviceLockedMountErrorCode) { - QMessageBox::critical(this, "Device Locked", - "The device is locked. Please unlock it and try" - " again."); - mounted_image_info_free(info); - return updateUI(); + /* Never returns DeviceLockedMountErrorCode when doing + image_mounter_lookup_image but maybe used in future */ } else if (info.err->code == NotFoundErrorCode) { - QMessageBox::critical( - this, "No Mounted Image", - "No developer disk image is mounted on the device."); - mounted_image_info_free(info); - return updateUI(); + // OK, no image mounted + qDebug() << "Mount image: no mounted image found"; } else { QMessageBox::critical( this, "Mount Check Failed", @@ -697,10 +691,6 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event) event->accept(); } -// Toolbox clicked: "Developer Disk Images" -// terminate called after throwing an instance of 'std::logic_error' -// what(): basic_string: construction from null is not valid - void DevDiskImagesWidget::checkMountedImage() { iDescriptorDevice *currentDevice = diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp index 60d89a3..2dd3596 100644 --- a/src/diskusagewidget.cpp +++ b/src/diskusagewidget.cpp @@ -400,92 +400,80 @@ void DiskUsageWidget::fetchData() QFuture future = QtConcurrent::run([this]() -> QVariantMap { QVariantMap result; - // if (!m_device || !m_device->provider) { - // result["error"] = "Invalid device."; - // return result; - // } + if (!m_device || !m_device->provider) { + result["error"] = "Invalid device."; + return result; + } - // // Pre-populate with known info - // result["totalCapacity"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalDiskCapacity); - // result["freeSpace"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalDataAvailable); - // result["systemUsage"] = QVariant::fromValue( - // m_device->deviceInfo.diskInfo.totalSystemCapacity); + // Pre-populate with known info + result["totalCapacity"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDiskCapacity); + result["freeSpace"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDataAvailable); + result["systemUsage"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalSystemCapacity); - // // Create provider wrapper from existing handle - // TODO:remove - // Provider provider = Provider::adopt(m_device->provider); + // Apps usage + uint64_t totalAppsSpace = 0; - // // Apps usage - // uint64_t totalAppsSpace = 0; - // auto instproxy_res = - // IdeviceFFI::InstallationProxy::connect(provider); if - // (instproxy_res.is_err()) { - // result["error"] = - // "Could not connect to installation proxy: " + - // QString::fromStdString(instproxy_res.unwrap_err().message); - // return result; - // } - // auto instproxy = std::move(instproxy_res.unwrap()); + InstallationProxyClientHandle *installationProxyClientHandle = nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = + IdeviceFFI::InstallationProxy::adopt(installationProxyClientHandle); - // plist_t client_opts = plist_new_dict(); - // plist_dict_set_item(client_opts, "ApplicationType", - // plist_new_string("User")); + plist_t client_opts = plist_new_dict(); + plist_dict_set_item(client_opts, "ApplicationType", + plist_new_string("User")); - // plist_t return_attrs = plist_new_array(); - // plist_array_append_item(return_attrs, - // plist_new_string("StaticDiskUsage")); - // plist_array_append_item(return_attrs, - // plist_new_string("DynamicDiskUsage")); - // plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); + plist_t return_attrs = plist_new_array(); + plist_array_append_item(return_attrs, + plist_new_string("StaticDiskUsage")); + plist_array_append_item(return_attrs, + plist_new_string("DynamicDiskUsage")); + plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); - // auto apps_result = instproxy.browse(client_opts); - // if (apps_result.is_ok()) { - // auto apps = std::move(apps_result.unwrap()); - // for (const auto &app_info : apps) { - // plist_t static_usage = - // plist_dict_get_item(app_info, "StaticDiskUsage"); - // if (static_usage && - // plist_get_node_type(static_usage) == PLIST_UINT) { - // uint64_t static_size = 0; - // plist_get_uint_val(static_usage, &static_size); - // totalAppsSpace += static_size; - // } + auto apps_result = instproxy.browse(client_opts); + if (apps_result.is_ok()) { + auto apps = std::move(apps_result.unwrap()); + for (const auto &app_info : apps) { + plist_t static_usage = + plist_dict_get_item(app_info, "StaticDiskUsage"); + if (static_usage && + plist_get_node_type(static_usage) == PLIST_UINT) { + uint64_t static_size = 0; + plist_get_uint_val(static_usage, &static_size); + totalAppsSpace += static_size; + } - // plist_t dynamic_usage = - // plist_dict_get_item(app_info, "DynamicDiskUsage"); - // if (dynamic_usage && - // plist_get_node_type(dynamic_usage) == PLIST_UINT) { - // uint64_t dynamic_size = 0; - // plist_get_uint_val(dynamic_usage, &dynamic_size); - // totalAppsSpace += dynamic_size; - // } - // } - // } - // result["appsUsage"] = QVariant::fromValue(totalAppsSpace); - // plist_free(client_opts); // client_opts is consumed by browse, but + plist_t dynamic_usage = + plist_dict_get_item(app_info, "DynamicDiskUsage"); + if (dynamic_usage && + plist_get_node_type(dynamic_usage) == PLIST_UINT) { + uint64_t dynamic_size = 0; + plist_get_uint_val(dynamic_usage, &dynamic_size); + totalAppsSpace += dynamic_size; + } + } + } + result["appsUsage"] = QVariant::fromValue(totalAppsSpace); + plist_free(client_opts); // client_opts is consumed by browse, but // Media usage - // uint64_t mediaSpace = 0; - // IdeviceFFI::Lockdown lockdown = - // IdeviceFFI::Lockdown::adopt(m_device->lockdown); - // auto itunes_info_res = - // lockdown.get_value("com.apple.mobile.iTunes", nullptr); - // if (itunes_info_res.is_ok()) { - // auto itunes_dict = std::move(itunes_info_res.unwrap()); - // if (itunes_dict) { - // plist_t media_node = - // plist_dict_get_item(itunes_dict, "MediaLibrarySize"); - // if (media_node && - // plist_get_node_type(media_node) == PLIST_UINT) { - // plist_get_uint_val(media_node, &mediaSpace); - // } - // } - // } - // result["mediaUsage"] = QVariant::fromValue(mediaSpace); + uint64_t mediaSpace = 0; + plist_t out = nullptr; + + IdeviceFfiError *err = lockdownd_get_value( + m_device->lockdown, "com.apple.mobile.iTunes", nullptr, &out); + if (!err && out) { + plist_t media_node = plist_dict_get_item(out, "MediaLibrarySize"); + if (media_node && plist_get_node_type(media_node) == PLIST_UINT) { + plist_get_uint_val(media_node, &mediaSpace); + } + } + result["mediaUsage"] = QVariant::fromValue(mediaSpace); return result; }); - // watcher->setFuture(future); + watcher->setFuture(future); } diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index c855088..3c5b1d8 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -52,18 +52,14 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) : QWidget{parent}, m_device(device), m_model(nullptr), - m_stackedWidget(nullptr), m_albumSelectionWidget(nullptr), - m_albumListView(nullptr), m_photoGalleryWidget(nullptr), - m_listView(nullptr), m_backButton(nullptr) + m_albumSelectionWidget(nullptr), m_albumListView(nullptr), + m_photoGalleryWidget(nullptr), m_listView(nullptr), m_backButton(nullptr) { m_mainLayout = new QVBoxLayout(this); m_mainLayout->setContentsMargins(0, 0, 0, 0); - - // Setup controls at the top (outside of stacked widget) + m_loadingWidget = new ZLoadingWidget(true, this); setupControlsLayout(); - - // Create stacked widget for different views - m_stackedWidget = new QStackedWidget(this); + m_mainLayout->addWidget(m_loadingWidget); // Setup album selection view setupAlbumSelectionView(); @@ -72,11 +68,7 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) setupPhotoGalleryView(); // Add stacked widget to main layout - m_mainLayout->addWidget(m_stackedWidget); setLayout(m_mainLayout); - m_loadingWidget = new ZLoadingWidget(true, this); - m_stackedWidget->addWidget(m_loadingWidget); - m_stackedWidget->setCurrentWidget(m_loadingWidget); QVBoxLayout *errorLayout = new QVBoxLayout(); errorLayout->setAlignment(Qt::AlignCenter); @@ -84,16 +76,13 @@ GalleryWidget::GalleryWidget(iDescriptorDevice *device, QWidget *parent) errorLabel->setStyleSheet("font-weight: bold; color: red;"); errorLayout->addWidget(errorLabel); m_retryButton = new QPushButton("Retry", this); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + m_loadingWidget->setupErrorWidget(errorLayout); connect(m_retryButton, &QPushButton::clicked, this, [this]() { - m_stackedWidget->setCurrentWidget(m_loadingWidget); + m_loadingWidget->showLoading(); QTimer::singleShot(100, this, &GalleryWidget::reload); }); - errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); - m_errorWidget = new QWidget(); - m_errorWidget->setLayout(errorLayout); - m_stackedWidget->addWidget(m_errorWidget); - m_stackedWidget->setCurrentWidget(m_loadingWidget); setControlsEnabled(false); // Disable controls until album is selected } @@ -374,7 +363,7 @@ void GalleryWidget::setupAlbumSelectionView() layout->addWidget(m_albumListView); - m_stackedWidget->addWidget(m_albumSelectionWidget); + m_loadingWidget->setupContentWidget(m_albumSelectionWidget); connect(m_albumListView, &QListView::doubleClicked, this, [this](const QModelIndex &index) { @@ -417,7 +406,7 @@ void GalleryWidget::setupPhotoGalleryView() layout->addWidget(m_listView); // Add the photo gallery widget to stacked widget - m_stackedWidget->addWidget(m_photoGalleryWidget); + m_loadingWidget->setupAditionalWidget(m_photoGalleryWidget); // Connect double-click to open preview dialog connect(m_listView, &QListView::doubleClicked, this, @@ -445,7 +434,7 @@ void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) { if (!dcimTree.success) { qDebug() << "Failed to read DCIM directory"; - m_stackedWidget->setCurrentWidget(m_errorWidget); + m_loadingWidget->showError(); QMessageBox::warning(this, "Error", "Could not access DCIM directory on device."); return; @@ -480,7 +469,8 @@ void GalleryWidget::loadAlbumList(const AFCFileTree &dcimTree) } m_albumListView->setModel(albumModel); - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + m_loadingWidget->stop(); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); } void GalleryWidget::onAlbumSelected(const QString &albumPath) @@ -507,8 +497,7 @@ void GalleryWidget::onAlbumSelected(const QString &albumPath) m_model->setAlbumPath(albumPath); // Switch to photo gallery view - m_stackedWidget->setCurrentWidget(m_photoGalleryWidget); - + m_loadingWidget->switchToWidget(m_photoGalleryWidget); // Enable controls and show back button setControlsEnabled(true); m_backButton->show(); @@ -522,7 +511,8 @@ void GalleryWidget::onBackToAlbums() } // Switch back to album selection view - m_stackedWidget->setCurrentWidget(m_albumSelectionWidget); + m_loadingWidget->switchToWidget(m_albumSelectionWidget); + if (m_model) { m_model->clear(); } diff --git a/src/gallerywidget.h b/src/gallerywidget.h index d1976ee..2e152bb 100644 --- a/src/gallerywidget.h +++ b/src/gallerywidget.h @@ -77,9 +77,7 @@ private: // UI components QVBoxLayout *m_mainLayout; QHBoxLayout *m_controlsLayout; - QStackedWidget *m_stackedWidget; ZLoadingWidget *m_loadingWidget; - QWidget *m_errorWidget; QPushButton *m_retryButton; QPushButton *m_importButton; // Album selection view diff --git a/src/iDescriptor.h b/src/iDescriptor.h index be62981..1768d78 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -69,6 +69,7 @@ #define DeviceLockedMountErrorCode -21 #define NotFoundErrorCode -14 +#define ServiceNotFoundErrorCode -15 #define DISK_IMAGE_TYPE_DEVELOPER "Developer" #define HEARTBEAT_RETRY_LIMIT 2 @@ -217,7 +218,6 @@ struct iDescriptorDevice { std::recursive_mutex *mutex; ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; - ScreenshotrClientHandle *screenshotrClient; LocationSimulationHandle *locationSimulation; }; @@ -231,7 +231,6 @@ struct iDescriptorInitDeviceResult { LockdowndClientHandle *lockdown; ImageMounterHandle *imageMounter; std::shared_ptr diagRelay; - ScreenshotrClientHandle *screenshotrClient; LocationSimulationHandle *locationSimulation; }; // #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT @@ -414,8 +413,10 @@ struct AFCFileTree { std::string currentPath; }; -AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir, - const std::string &path = "/"); +AFCFileTree +get_file_tree(const iDescriptorDevice *device, bool checkDir, + const std::string &path = "/", + std::optional altAfc = std::nullopt); bool detect_jailbroken(AfcClientHandle *afc); @@ -438,8 +439,6 @@ init_idescriptor_device(const QString &udid, // bool shutdown(idevice_t device); -// TakeScreenshotResult take_screenshot(screenshotr_client_t shotr); - IdeviceFfiError *mount_dev_image(const iDescriptorDevice *device, const char *image_file, const char *signature_file); diff --git a/src/installedappswidget.cpp b/src/installedappswidget.cpp index 89d8778..460d79a 100644 --- a/src/installedappswidget.cpp +++ b/src/installedappswidget.cpp @@ -22,6 +22,7 @@ #include "iDescriptor-ui.h" #include "iDescriptor.h" #include "qprocessindicator.h" +#include "servicemanager.h" #include "zlineedit.h" #include #include @@ -36,7 +37,8 @@ #include AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, - const QString &version, QWidget *parent) + const QString &version, const QPixmap &icon, + QWidget *parent) : QGroupBox(parent), m_appName(appName), m_bundleId(bundleId), m_version(version), m_selected(false) { @@ -45,31 +47,7 @@ AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId, setCursor(Qt::PointingHandCursor); setupUI(); - fetchAppIcon(); -} - -void AppTabWidget::fetchAppIcon() -{ - ::fetchAppIconFromApple( - m_networkManager, m_bundleId, [this](const QPixmap &pixmap) { - if (!pixmap.isNull()) { - QPixmap scaled = - pixmap.scaled(32, 32, Qt::KeepAspectRatioByExpanding, - Qt::SmoothTransformation); - QPixmap rounded(32, 32); - rounded.fill(Qt::transparent); - - QPainter painter(&rounded); - painter.setRenderHint(QPainter::Antialiasing); - QPainterPath path; - path.addRoundedRect(QRectF(0, 0, 32, 32), 8, 8); - painter.setClipPath(path); - painter.drawPixmap(0, 0, scaled); - painter.end(); - - m_iconLabel->setPixmap(rounded); - } - }); + m_iconLabel->setPixmap(icon); } void AppTabWidget::setSelected(bool selected) @@ -290,177 +268,132 @@ void InstalledAppsWidget::fetchInstalledApps() showErrorState("Invalid device"); return; } + // todo maybe clear m_watcher ? + QFuture future = QtConcurrent::run([this]() -> QVariantMap { + QVariantMap result; + QVariantList apps; - // QFuture future = QtConcurrent::run([this]() -> QVariantMap { - // QVariantMap result; - // QVariantList apps; + try { + InstallationProxyClientHandle *installationProxyClientHandle = + nullptr; + installation_proxy_connect(m_device->provider, + &installationProxyClientHandle); + auto instproxy = IdeviceFFI::InstallationProxy::adopt( + installationProxyClientHandle); - // // result["success"] = true; - // // result["apps"] = apps; - // // return result; + // Get both User and System apps + QStringList appTypes = {"User", "System"}; - // instproxy_client_t instproxy = nullptr; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t lockdowndService = nullptr; + for (const QString &appType : appTypes) { + plist_t client_opts = plist_new_dict(); + plist_dict_set_item( + client_opts, "ApplicationType", + plist_new_string(appType.toUtf8().constData())); - // try { - // if (lockdownd_client_new_with_handshake( - // m_device->device, &lockdownClient, APP_LABEL) != - // LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not connect to lockdown service"; - // return result; - // } + plist_t return_attrs = plist_new_array(); + plist_array_append_item(return_attrs, + plist_new_string("CFBundleIdentifier")); + plist_array_append_item( + return_attrs, plist_new_string("CFBundleDisplayName")); + plist_array_append_item( + return_attrs, + plist_new_string("CFBundleShortVersionString")); + plist_array_append_item(return_attrs, + plist_new_string("CFBundleVersion")); + plist_array_append_item( + return_attrs, plist_new_string("UIFileSharingEnabled")); - // if (lockdownd_start_service( - // lockdownClient, "com.apple.mobile.installation_proxy", - // &lockdowndService) != LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not start installation proxy - // service"; lockdownd_client_free(lockdownClient); return - // result; - // } + plist_dict_set_item(client_opts, "ReturnAttributes", + return_attrs); - // if (instproxy_client_new(m_device->device, lockdowndService, - // &instproxy) != INSTPROXY_E_SUCCESS) { - // result["error"] = "Could not connect to installation proxy"; - // lockdownd_service_descriptor_free(lockdowndService); - // lockdownd_client_free(lockdownClient); - // return result; - // } + auto installedApps = instproxy.browse(client_opts); + if (installedApps.is_ok()) { + plist_t apps_plist; + installedApps.unwrap(); - // lockdownd_service_descriptor_free(lockdowndService); - // lockdowndService = nullptr; + // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { + for (const auto &app_info : installedApps.unwrap()) { + if (!app_info) + continue; - // // Get both User and System apps - // QStringList appTypes = {"User", "System"}; + QVariantMap appData; - // for (const QString &appType : appTypes) { - // plist_t client_opts = plist_new_dict(); - // plist_dict_set_item( - // client_opts, "ApplicationType", - // plist_new_string(appType.toUtf8().constData())); + // Get bundle identifier + plist_t bundle_id = + plist_dict_get_item(app_info, "CFBundleIdentifier"); + if (bundle_id && + plist_get_node_type(bundle_id) == PLIST_STRING) { + char *bundle_id_str = nullptr; + plist_get_string_val(bundle_id, &bundle_id_str); + if (bundle_id_str) { + appData["bundleId"] = QString(bundle_id_str); + free(bundle_id_str); + } + } - // plist_t return_attrs = plist_new_array(); - // plist_array_append_item(return_attrs, - // plist_new_string("CFBundleIdentifier")); - // plist_array_append_item( - // return_attrs, plist_new_string("CFBundleDisplayName")); - // plist_array_append_item( - // return_attrs, - // plist_new_string("CFBundleShortVersionString")); - // plist_array_append_item(return_attrs, - // plist_new_string("CFBundleVersion")); - // plist_array_append_item( - // return_attrs, plist_new_string("UIFileSharingEnabled")); + // Get display name + plist_t display_name = plist_dict_get_item( + app_info, "CFBundleDisplayName"); + if (display_name && + plist_get_node_type(display_name) == PLIST_STRING) { + char *display_name_str = nullptr; + plist_get_string_val(display_name, + &display_name_str); + if (display_name_str) { + appData["displayName"] = + QString(display_name_str); + free(display_name_str); + } + } - // plist_dict_set_item(client_opts, "ReturnAttributes", - // return_attrs); + // Get version + plist_t version = plist_dict_get_item( + app_info, "CFBundleShortVersionString"); + if (version && + plist_get_node_type(version) == PLIST_STRING) { + char *version_str = nullptr; + plist_get_string_val(version, &version_str); + if (version_str) { + appData["version"] = QString(version_str); + free(version_str); + } + } - // plist_t apps_plist = nullptr; - // if (instproxy_browse(instproxy, client_opts, &apps_plist) == - // INSTPROXY_E_SUCCESS && - // apps_plist) { - // if (plist_get_node_type(apps_plist) == PLIST_ARRAY) { - // for (uint32_t i = 0; - // i < plist_array_get_size(apps_plist); i++) { - // plist_t app_info = - // plist_array_get_item(apps_plist, i); - // if (!app_info) - // continue; + // Get file sharing enabled status + plist_t file_sharing = plist_dict_get_item( + app_info, "UIFileSharingEnabled"); + if (file_sharing && plist_get_node_type(file_sharing) == + PLIST_BOOLEAN) { + uint8_t file_sharing_enabled = 0; + plist_get_bool_val(file_sharing, + &file_sharing_enabled); + appData["fileSharingEnabled"] = + (file_sharing_enabled != 0); + } else { + appData["fileSharingEnabled"] = false; + } - // QVariantMap appData; + appData["type"] = appType; - // // Get bundle identifier - // plist_t bundle_id = plist_dict_get_item( - // app_info, "CFBundleIdentifier"); - // if (bundle_id && plist_get_node_type(bundle_id) - // == - // PLIST_STRING) { - // char *bundle_id_str = nullptr; - // plist_get_string_val(bundle_id, - // &bundle_id_str); if (bundle_id_str) { - // appData["bundleId"] = - // QString(bundle_id_str); - // free(bundle_id_str); - // } - // } + if (!appData["bundleId"].toString().isEmpty()) { + apps.append(appData); + } + } - // // Get display name - // plist_t display_name = plist_dict_get_item( - // app_info, "CFBundleDisplayName"); - // if (display_name && - // plist_get_node_type(display_name) == - // PLIST_STRING) { - // char *display_name_str = nullptr; - // plist_get_string_val(display_name, - // &display_name_str); - // if (display_name_str) { - // appData["displayName"] = - // QString(display_name_str); - // free(display_name_str); - // } - // } + plist_free(apps_plist); + } + plist_free(client_opts); + } + result["apps"] = apps; + result["success"] = true; + } catch (const std::exception &e) { + result["error"] = QString("Exception: %1").arg(e.what()); + } - // // Get version - // plist_t version = plist_dict_get_item( - // app_info, "CFBundleShortVersionString"); - // if (version && - // plist_get_node_type(version) == PLIST_STRING) - // { char *version_str = nullptr; - // plist_get_string_val(version, &version_str); - // if (version_str) { - // appData["version"] = - // QString(version_str); free(version_str); - // } - // } + return result; + }); - // // Get file sharing enabled status - // plist_t file_sharing = plist_dict_get_item( - // app_info, "UIFileSharingEnabled"); - // if (file_sharing && - // plist_get_node_type(file_sharing) == - // PLIST_BOOLEAN) { - // uint8_t file_sharing_enabled = 0; - // plist_get_bool_val(file_sharing, - // &file_sharing_enabled); - // appData["fileSharingEnabled"] = - // (file_sharing_enabled != 0); - // } else { - // appData["fileSharingEnabled"] = false; - // } - - // appData["type"] = appType; - - // if (!appData["bundleId"].toString().isEmpty()) { - // apps.append(appData); - // } - // } - // } - // plist_free(apps_plist); - // } - // plist_free(client_opts); - // } - - // instproxy_client_free(instproxy); - // lockdownd_client_free(lockdownClient); - - // result["apps"] = apps; - // result["success"] = true; - - // } catch (const std::exception &e) { - // if (instproxy) - // instproxy_client_free(instproxy); - // if (lockdownClient) - // lockdownd_client_free(lockdownClient); - // if (lockdowndService) - // lockdownd_service_descriptor_free(lockdowndService); - - // result["error"] = QString("Exception: %1").arg(e.what()); - // } - - // return result; - // }); - - // m_watcher->setFuture(future); + m_watcher->setFuture(future); } void InstalledAppsWidget::onAppsDataReady() @@ -498,6 +431,18 @@ void InstalledAppsWidget::onAppsDataReady() m_appTabs.clear(); m_selectedTab = nullptr; + // fetch icon from springboard service + SpringBoardServicesClientHandle *springboardClient = nullptr; + IdeviceFfiError *err = nullptr; + err = springboard_services_connect(m_device->provider, &springboardClient); + + if (err != nullptr) { + qDebug() << "Error connecting to SpringBoard services:" + << QString::fromUtf8(err->message); + } else { + qDebug() << "Successfully connected to SpringBoard services."; + } + // Create tabs for each app for (const QVariant &appVariant : apps) { QVariantMap appData = appVariant.toMap(); @@ -523,24 +468,41 @@ void InstalledAppsWidget::onAppsDataReady() tabName += " (System)"; } - createAppTab(tabName, bundleId, version); - } + if (springboardClient) { + void *out_result; + size_t out_result_len; - // m_contentLabel->setText( - // QString("Found %1 installed apps").arg(apps.count())); + err = springboard_services_get_icon(springboardClient, + bundleId.toUtf8().constData(), + &out_result, &out_result_len); + if (err != nullptr) { + qWarning() << "Error getting icon for" << bundleId << ":" + << QString::fromUtf8(err->message); + createAppTab(tabName, bundleId, version); + } else { + QByteArray byteArray(reinterpret_cast(out_result), + static_cast(out_result_len)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); + createAppTab(tabName, bundleId, version, pixmap); + } + } - // Select first tab if available - if (!m_appTabs.isEmpty()) { - selectAppTab(m_appTabs.first()); + // Select first tab if available + if (!m_appTabs.isEmpty()) { + selectAppTab(m_appTabs.first()); + } } } void InstalledAppsWidget::createAppTab(const QString &appName, const QString &bundleId, - const QString &version) + const QString &version, + const QPixmap &icon) { AppTabWidget *tabWidget = - new AppTabWidget(appName, bundleId, version, this); + new AppTabWidget(appName, bundleId, version, icon, this); connect(tabWidget, &AppTabWidget::clicked, this, &InstalledAppsWidget::onAppTabClicked); @@ -575,6 +537,7 @@ void InstalledAppsWidget::selectAppTab(AppTabWidget *tab) QString bundleId = tab->getBundleId(); // Load app container data + // FIXME: handle quickly repeated selections loadAppContainer(bundleId); } @@ -635,188 +598,138 @@ void InstalledAppsWidget::loadAppContainer(const QString &bundleId) m_containerLayout->addWidget(loadingWidget); - // QFuture future = QtConcurrent::run([this, bundleId]() - // -> QVariantMap { - // QVariantMap result; + QFuture future = + QtConcurrent::run([this, bundleId]() -> QVariantMap { + QVariantMap result; + IdeviceFfiError *err = nullptr; + HouseArrestClientHandle *houseArrestClient = nullptr; + AfcClientHandle *afcClient = nullptr; - // afc_client_t afcClient = nullptr; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t lockdowndService = nullptr; - // house_arrest_client_t houseArrestClient = nullptr; - // try { - // if (lockdownd_client_new_with_handshake( - // m_device->device, &lockdownClient, APP_LABEL) != - // LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not connect to lockdown service"; - // return result; - // } + try { + err = house_arrest_client_connect(m_device->provider, + &houseArrestClient); - // if (lockdownd_start_service( - // lockdownClient, "com.apple.mobile.house_arrest", - // &lockdowndService) != LOCKDOWN_E_SUCCESS) { - // result["error"] = "Could not start house arrest service"; - // lockdownd_client_free(lockdownClient); - // return result; - // } + if (err != nullptr) { + qDebug() + << "Error connecting to House Arrest for" << bundleId + << ":" << QString::fromUtf8(err->message); + result["error"] = + QString("Error connecting to House Arrest: %1") + .arg(QString::fromUtf8(err->message)); + return result; + } - // if (house_arrest_client_new(m_device->device, lockdowndService, - // &houseArrestClient) != - // HOUSE_ARREST_E_SUCCESS) { - // result["error"] = "Could not connect to house arrest"; - // lockdownd_service_descriptor_free(lockdowndService); - // lockdownd_client_free(lockdownClient); - // return result; - // } + err = house_arrest_vend_documents(houseArrestClient, + bundleId.toUtf8().constData(), + &afcClient); + if (err != nullptr) { + qDebug() << "Error vending documents for" << bundleId << ":" + << QString::fromUtf8(err->message); + result["error"] = QString("Error vending documents: %1") + .arg(QString::fromUtf8(err->message)); + house_arrest_client_free(houseArrestClient); + return result; + } - // lockdownd_service_descriptor_free(lockdowndService); - // lockdowndService = nullptr; + char **dirs = nullptr; + size_t count = 0; - // // Send vendor container command - // if (house_arrest_send_command( - // houseArrestClient, "VendDocuments", - // // if (house_arrest_send_command(houseArrestClient, - // // "VendDocuments", - // bundleId.toUtf8().constData()) != HOUSE_ARREST_E_SUCCESS) - // { - // result["error"] = "Could not send VendDocuments command"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // // Use safe wrapper to read directory + // IdeviceFfiError *err = ServiceManager::safeAfcReadDirectory( + // m_device, "/Documents", &dirs, count, afcClient); - // // Get result - // plist_t dict = nullptr; - // if (house_arrest_get_result(houseArrestClient, &dict) != - // HOUSE_ARREST_E_SUCCESS || - // !dict) { - // result["error"] = "App container not available for this app"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // if (err != nullptr) { + // qDebug() << "Error reading Documents dir:" + // << QString::fromUtf8(err->message); + // result["error"] = QString("Error reading Documents dir: + // %1") + // .arg(QString::fromUtf8(err->message)); + // if (afcClient) + // afc_client_free(afcClient); + // if (houseArrestClient) + // house_arrest_client_free(houseArrestClient); + // return result; + // } - // // Check for error in response - // plist_t error_node = plist_dict_get_item(dict, "Error"); - // if (error_node) { - // char *error_str = nullptr; - // plist_get_string_val(error_node, &error_str); - // if (error_str) { - // result["error"] = - // QString("Container access denied: - // %1").arg(error_str); - // free(error_str); - // } else { - // result["error"] = "Container access denied"; - // } - // plist_free(dict); - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + // QStringList files; + // if (dirs) { + // for (int i = 0; dirs[i]; i++) { + // QString fileName = QString::fromUtf8(dirs[i]); + // if (fileName != "." && fileName != "..") { + // qDebug() << "Found file:" << fileName; + // files.append(fileName); + // } + // } + // } - // plist_free(dict); + // free_directory_listing(dirs, count); + // qDebug() << "Total files found:" << files.size(); + // result["files"] = files; + result["afcClient"] = + QVariant::fromValue(reinterpret_cast(afcClient)); + result["houseArrestClient"] = QVariant::fromValue( + reinterpret_cast(houseArrestClient)); + result["success"] = true; - // // Get AFC client for file access - // if (afc_client_new_from_house_arrest_client( - // houseArrestClient, &afcClient) != AFC_E_SUCCESS) { - // result["error"] = - // "Could not create AFC client for app container"; - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + } catch (const std::exception &e) { + qDebug() << "Exception while loading app container:" + << e.what(); + if (afcClient) + afc_client_free(afcClient); + if (houseArrestClient) + house_arrest_client_free(houseArrestClient); - // // List root directory contents - // char **list = nullptr; - // if (afc_read_directory(afcClient, "/Documents", &list) != - // AFC_E_SUCCESS) { - // result["error"] = "Could not read app container directory"; - // afc_client_free(afcClient); - // house_arrest_client_free(houseArrestClient); - // lockdownd_client_free(lockdownClient); - // return result; - // } + result["error"] = QString("Exception: %1").arg(e.what()); + } - // QStringList files; - // if (list) { - // for (int i = 0; list[i]; i++) { - // QString fileName = QString::fromUtf8(list[i]); - // if (fileName != "." && fileName != "..") { - // qDebug() << "Found file:" << fileName; - // files.append(fileName); - // } - // } - // afc_dictionary_free(list); - // } - // result["files"] = files; - // result["afcClient"] = - // QVariant::fromValue(reinterpret_cast(afcClient)); - // result["houseArrestClient"] = QVariant::fromValue( - // reinterpret_cast(houseArrestClient)); - // result["success"] = true; + return result; + }); - // lockdownd_client_free(lockdownClient); - - // } catch (const std::exception &e) { - // if (afcClient) - // afc_client_free(afcClient); - // if (houseArrestClient) - // house_arrest_client_free(houseArrestClient); - // if (lockdownClient) - // lockdownd_client_free(lockdownClient); - // if (lockdowndService) - // lockdownd_service_descriptor_free(lockdowndService); - - // result["error"] = QString("Exception: %1").arg(e.what()); - // } - - // return result; - // }); - - // m_containerWatcher->setFuture(future); + m_containerWatcher->setFuture(future); } void InstalledAppsWidget::onContainerDataReady() { QVariantMap result = m_containerWatcher->result(); - // // todo - // // Clear loading state - // QLayoutItem *item; - // while ((item = m_containerLayout->takeAt(0)) != nullptr) { - // if (item->widget()) { - // item->widget()->deleteLater(); - // } - // delete item; - // } + // todo + // Clear loading state + QLayoutItem *item; + while ((item = m_containerLayout->takeAt(0)) != nullptr) { + if (item->widget()) { + item->widget()->deleteLater(); + } + delete item; + } - // if (!result.value("success", false).toBool()) { - // qDebug() << "Error loading app container:" - // << result.value("error").toString(); - // QLabel *errorLabel = new QLabel("No data available for this app"); - // errorLabel->setAlignment(Qt::AlignCenter); - // m_containerLayout->addWidget(errorLabel); - // return; - // } + if (!result.value("success", false).toBool()) { + qDebug() << "Error loading app container:" + << result.value("error").toString(); + QLabel *errorLabel = new QLabel("No data available for this app"); + errorLabel->setAlignment(Qt::AlignCenter); + m_containerLayout->addWidget(errorLabel); + return; + } - // // Get the AFC clients from the result and store them as member variables - // m_houseArrestAfcClient = reinterpret_cast( - // result.value("afcClient").value()); - // m_houseArrestClient = reinterpret_cast( - // result.value("houseArrestClient").value()); + // Get the AFC clients from the result and store them as member + // variables + m_houseArrestAfcClient = reinterpret_cast( + result.value("afcClient").value()); + m_houseArrestClient = reinterpret_cast( + result.value("houseArrestClient").value()); - // if (!m_houseArrestAfcClient) { - // QLabel *errorLabel = - // new QLabel("Failed to get AFC client for app container"); - // m_containerLayout->addWidget(errorLabel); - // return; - // } + if (!m_houseArrestAfcClient) { + QLabel *errorLabel = + new QLabel("Failed to get AFC client for app container"); + m_containerLayout->addWidget(errorLabel); + return; + } - // // Create AfcExplorerWidget with the house arrest AFC client - // AfcExplorerWidget *explorer = new AfcExplorerWidget( - // m_device, true, m_houseArrestAfcClient, "/Documents", this); - // explorer->setStyleSheet("border :none;"); - // m_containerLayout->addWidget(explorer); + // Create AfcExplorerWidget with the house arrest AFC client + AfcExplorerWidget *explorer = new AfcExplorerWidget( + m_device, true, m_houseArrestAfcClient, "/Documents", this); + explorer->setStyleSheet("border :none;"); + m_containerLayout->addWidget(explorer); } void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) @@ -828,15 +741,16 @@ void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled) void InstalledAppsWidget::cleanupHouseArrestClients() { - // if (m_houseArrestAfcClient) { - // afc_client_free(m_houseArrestAfcClient); - // m_houseArrestAfcClient = nullptr; - // } + if (m_houseArrestAfcClient) { + // FIXME: create an issue afc_client_free crashes + // afc_client_free(m_houseArrestAfcClient); + m_houseArrestAfcClient = nullptr; + } - // if (m_houseArrestClient) { - // house_arrest_client_free(m_houseArrestClient); - // m_houseArrestClient = nullptr; - // } + if (m_houseArrestClient) { + house_arrest_client_free(m_houseArrestClient); + m_houseArrestClient = nullptr; + } } void InstalledAppsWidget::createLeftPanel() diff --git a/src/installedappswidget.h b/src/installedappswidget.h index 77ad366..596b399 100644 --- a/src/installedappswidget.h +++ b/src/installedappswidget.h @@ -49,7 +49,8 @@ class AppTabWidget : public QGroupBox public: AppTabWidget(const QString &appName, const QString &bundleId, - const QString &version, QWidget *parent = nullptr); + const QString &version, const QPixmap &icon = QPixmap(), + QWidget *parent = nullptr); void setSelected(bool selected); bool isSelected() const { return m_selected; } @@ -66,7 +67,6 @@ protected: void mousePressEvent(QMouseEvent *event) override; private: - void fetchAppIcon(); void setupUI(); QString m_appName; @@ -105,7 +105,7 @@ private: void createRightPanel(); void fetchInstalledApps(); void createAppTab(const QString &appName, const QString &bundleId, - const QString &version); + const QString &version, const QPixmap &icon = QPixmap()); void showLoadingState(); void showErrorState(const QString &error); void selectAppTab(AppTabWidget *tab); @@ -132,8 +132,8 @@ private: QFutureWatcher *m_watcher; QFutureWatcher *m_containerWatcher; QSplitter *m_splitter; - // house_arrest_client_t m_houseArrestClient = nullptr; - // afc_client_t m_houseArrestAfcClient = nullptr; + HouseArrestClientHandle *m_houseArrestClient = nullptr; + AfcClientHandle *m_houseArrestAfcClient = nullptr; // App data storage QList m_appTabs; AppTabWidget *m_selectedTab = nullptr; diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index 30f6aee..b0338b2 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -29,9 +29,10 @@ #include #include #include + // todo add a retry button when failed LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) - : QWidget{parent}, m_device(device), m_timer(nullptr), m_fps(15) + : QWidget{parent}, m_device(device) { setWindowTitle("Live Screen - iDescriptor"); @@ -71,19 +72,9 @@ LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent) m_imageLabel = new QLabel(); m_imageLabel->setMinimumSize(300, 600); m_imageLabel->setAlignment(Qt::AlignCenter); - m_imageLabel->setFrameStyle(QFrame::Box | QFrame::Plain); mainLayout->addWidget(m_imageLabel, 1); - // Timer for periodic screenshots - m_timer = new QTimer(this); - m_timer->setInterval(1000 / m_fps); - connect(m_timer, &QTimer::timeout, this, - &LiveScreenWidget::updateScreenshot); - - startCapturing(); - - // Defer the initialization to allow the main widget to show first - // QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); + QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization); } void LiveScreenWidget::startInitialization() @@ -117,153 +108,55 @@ void LiveScreenWidget::startInitialization() LiveScreenWidget::~LiveScreenWidget() { - if (m_timer) { - m_timer->stop(); + if (m_thread) { + m_thread->requestInterruption(); + m_thread->wait(); + m_thread->deleteLater(); + m_thread = nullptr; + } + if (m_screenshotrClient) { + screenshotr_client_free(m_screenshotrClient); + m_screenshotrClient = nullptr; } - - // if (m/*_shotrClient) { - // screenshotr_client_free(m_shotrClient); - // m_shotrClient = nullptr; - // }*/ } bool LiveScreenWidget::initializeScreenshotService(bool notify) { - return true; - // lockdownd_client_t lockdownClient = nullptr; - // lockdownd_service_descriptor_t service = nullptr; - - // try { - // m_statusLabel->setText("Connecting to screenshot service..."); - - // IdeviceFfiError * = - // screenshotr_take_screenshot(m_device->screenshotrClient,) - - // if (ldret != LOCKDOWN_E_SUCCESS) { - // m_statusLabel->setText("Failed to connect to lockdown service"); - // if (notify) - // QMessageBox::critical(this, "Connection Failed", - // "Could not connect to lockdown - // service.\n" "Error code: " + - // QString::number(ldret)); - // return false; - // } - - // lockdownd_error_t lerr = lockdownd_start_service( - // lockdownClient, SCREENSHOTR_SERVICE_NAME, &service); - - // lockdownd_client_free(lockdownClient); - // lockdownClient = nullptr; - - // if (lerr != LOCKDOWN_E_SUCCESS) { - // m_statusLabel->setText("Failed to start screenshot service"); - // qDebug() << lerr << "lockdownd_start_service"; - // if (notify) - // QMessageBox::critical( - // this, "Service Failed", - // "Could not start screenshot service on device.\n" - // "Please ensure the developer disk image is properly " - // "mounted."); - // if (service) { - // lockdownd_service_descriptor_free(service); - // } - // return false; - // } - - // screenshotr_error_t screrr = - // screenshotr_client_new(m_device->device, service, - // &m_shotrClient); - - // lockdownd_service_descriptor_free(service); - // service = nullptr; - // qDebug() << screrr << "screenshotr_client_new"; - // if (screrr != SCREENSHOTR_E_SUCCESS) { - // m_statusLabel->setText("Failed to create screenshot client"); - // if (notify) - // QMessageBox::critical(this, "Client Failed", - // "Could not create screenshot client.\n" - // "Error code: " + - // QString::number(screrr)); - // return false; - // } - - // // Successfully initialized, start capturing - // m_statusLabel->setText("Capturing"); - // startCapturing(); - // return true; - // } catch (const std::exception &e) { - // m_statusLabel->setText("Exception occurred"); - // if (notify) - // QMessageBox::critical( - // this, "Exception", - // QString("Exception occurred: %1").arg(e.what())); - - // if (lockdownClient) { - // lockdownd_client_free(lockdownClient); - // } - // if (service) { - // lockdownd_service_descriptor_free(service); - // } - // } + try { + m_statusLabel->setText("Connecting to screenshot service..."); + IdeviceFfiError *err = + screenshotr_connect(m_device->provider, &m_screenshotrClient); + if (err) { + qDebug() << "Failed to create Screenshotr client"; + return false; // proceed to mount image + } + // Successfully initialized, start capturing + m_statusLabel->setText("Capturing"); + startCapturing(); + return true; + } catch (const std::exception &e) { + m_statusLabel->setText("Exception occurred"); + if (notify) + QMessageBox::critical( + this, "Exception", + QString("Exception occurred: %1").arg(e.what())); + } } void LiveScreenWidget::startCapturing() { - // if (!m_shotrClient) { - // qWarning() - // << "Cannot start capturing: screenshot client not initialized"; - // return; - // } - - if (m_timer) { - m_timer->start(); - qDebug() << "Started capturing"; + if (!m_screenshotrClient) { + qWarning() + << "Cannot start capturing: screenshot client not initialized"; + return; } -} -void LiveScreenWidget::updateScreenshot() -{ - // if (!m_shotrClient) { - // qWarning() << "Screenshot client not initialized"; - // return; - // } - qDebug() << "Updating screenshot..."; - // FIXME: move to services - try { - // TakeScreenshotResult result = take_screenshot(m_shotrClient); - ScreenshotData screenshot; - IdeviceFfiError *err = screenshotr_take_screenshot( - m_device->screenshotrClient, &screenshot); - if (!err && screenshot.data && screenshot.length > 0) { - qDebug() << "Screenshot captured, size:" << screenshot.length; - // QImage img(screenshot.data, // data - // static_cast(screenshot.length), // width - // 1, // height - // QImage::Format_ARGB32); // format - - QByteArray byteArray( - reinterpret_cast(screenshot.data), - static_cast(screenshot.length)); - QImage image; - image.loadFromData(byteArray); - QPixmap pixmap = QPixmap::fromImage(image); - m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - } else { - qDebug() << "Failed to capture screenshot"; - } - - // if (result.success && !result.img.isNull()) { - // QPixmap pixmap = QPixmap::fromImage(result.img); - // m_imageLabel->setPixmap(pixmap.scaled(m_imageLabel->size(), - // Qt::KeepAspectRatio, - // Qt::SmoothTransformation)); - // } else { - // qWarning() << "Failed to capture screenshot"; - // } - } catch (const std::exception &e) { - qWarning() << "Exception in updateScreenshot:" << e.what(); - m_statusLabel->setText("Error capturing screenshot"); - } -} + m_thread = new ScreenshotrThread(m_screenshotrClient, m_device, this); + connect(m_thread, &ScreenshotrThread::screenshotCaptured, this, + [this](const QPixmap &pixmap) { + m_imageLabel->setPixmap( + pixmap.scaled(m_imageLabel->size(), Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + }); + m_thread->start(); +} \ No newline at end of file diff --git a/src/livescreenwidget.h b/src/livescreenwidget.h index bc4c1f8..f3714ac 100644 --- a/src/livescreenwidget.h +++ b/src/livescreenwidget.h @@ -21,10 +21,57 @@ #define LIVESCREEN_H #include "iDescriptor.h" +#include "servicemanager.h" #include +#include #include #include +class ScreenshotrThread : public QThread +{ + Q_OBJECT +public: + explicit ScreenshotrThread(ScreenshotrClientHandle *client, + iDescriptorDevice *device, + QObject *parent = nullptr) + : QThread(parent), m_device(device), m_client(client), m_fps(15) + { + } + +protected: + void run() override + { + qDebug() << "Started capturing"; + + // Thread loop to continuously fetch screenshots + while (!isInterruptionRequested()) { + ScreenshotData screenshotData; + IdeviceFfiError *err = ServiceManager::takeScreenshot( + m_device, m_client, &screenshotData); + if (!err && screenshotData.data && screenshotData.length > 0) { + QByteArray byteArray( + reinterpret_cast(screenshotData.data), + static_cast(screenshotData.length)); + QImage image; + image.loadFromData(byteArray); + QPixmap pixmap = QPixmap::fromImage(image); + emit screenshotCaptured(pixmap); + screenshotr_screenshot_free(screenshotData); + } else { + qDebug() << "Failed to capture screenshot"; + } + msleep(1000 / m_fps); // Capture at ~m_fps FPS + } + } +signals: + void screenshotCaptured(const QPixmap &pixmap); + +private: + ScreenshotrClientHandle *m_client; + int m_fps; + iDescriptorDevice *m_device; +}; + class LiveScreenWidget : public QWidget { Q_OBJECT @@ -39,13 +86,13 @@ private: void startCapturing(); iDescriptorDevice *m_device; - QTimer *m_timer; QLabel *m_imageLabel; QLabel *m_statusLabel; - int m_fps; + ScreenshotrClientHandle *m_screenshotrClient = nullptr; + ScreenshotrThread *m_thread = nullptr; private: - void startInitialization(); // Add this line + void startInitialization(); }; #endif // LIVESCREEN_H diff --git a/src/photomodel.cpp b/src/photomodel.cpp index f5304f9..c961fda 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -683,7 +683,9 @@ void PhotoModel::populatePhotoPaths() qDebug() << "Photo directory C string:" << photoDir; char **files = nullptr; - err = ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files); + size_t count = 0; + err = + ServiceManager::safeAfcReadDirectory(m_device, photoDir, &files, count); if (err) { qDebug() << "Failed to read photo directory:" << photoDir << "Error:" << err->message; @@ -711,8 +713,7 @@ void PhotoModel::populatePhotoPaths() m_allPhotos.append(info); } } - // free(files); - // afc_dictionary_free(files); + free_directory_listing(files, count); } // Apply initial filtering and sorting, which will also reset the model @@ -743,16 +744,11 @@ void PhotoModel::applyFilterAndSort() { beginResetModel(); - // int i = 0; // Filter photos m_photos.clear(); for (const PhotoInfo &info : m_allPhotos) { if (matchesFilter(info)) { m_photos.append(info); - // if (i == 3) { - // break; - // } - // i++; } } diff --git a/src/querymobilegestaltwidget.cpp b/src/querymobilegestaltwidget.cpp index 6365f71..ba01781 100644 --- a/src/querymobilegestaltwidget.cpp +++ b/src/querymobilegestaltwidget.cpp @@ -23,19 +23,20 @@ #include #include #include +#include #include QueryMobileGestaltWidget::QueryMobileGestaltWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), m_device(device) { - // todo: not tested on iOS 17,18 but it's deprecated on iOS 26 + // FIXME: not tested on iOS 17,18 but it's deprecated on iOS 26 // i am assuming it won't work if (m_device->deviceInfo.parsedDeviceVersion.major > 16) { QMessageBox::warning(this, "Unsupported iOS Version", "Apple deprecated this protocol for Devices " "running iOS 17 or later"); - close(); + QTimer::singleShot(0, this, &QWidget::close); return; } setupUI(); diff --git a/src/servicemanager.cpp b/src/servicemanager.cpp index f814586..4c07e13 100644 --- a/src/servicemanager.cpp +++ b/src/servicemanager.cpp @@ -21,12 +21,10 @@ #include "iDescriptor.h" #include -IdeviceFfiError * -ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device, - const char *path, char ***dirs, - std::optional altAfc) +IdeviceFfiError *ServiceManager::safeAfcReadDirectory( + const iDescriptorDevice *device, const char *path, char ***dirs, + size_t count, std::optional altAfc) { - size_t count = 0; return executeAfcClientOperation( device, [path, dirs, &count, device](AfcClientHandle *client) { @@ -121,32 +119,44 @@ ServiceManager::safeAfcFileTell(const iDescriptorDevice *device, handle); } -QByteArray -ServiceManager::safeReadAfcFileToByteArray(const iDescriptorDevice *device, - const char *path) +QByteArray ServiceManager::safeReadAfcFileToByteArray( + const iDescriptorDevice *device, const char *path, + std::optional altAfc) { - return executeOperation(device, [path, device]() -> QByteArray { - return read_afc_file_to_byte_array(device, path); - }); + return executeOperation( + device, + [path, device]() -> QByteArray { + return read_afc_file_to_byte_array(device, path); + }, + altAfc); } -AFCFileTree ServiceManager::safeGetFileTree(const iDescriptorDevice *device, - const std::string &path, - bool checkDir) +AFCFileTree +ServiceManager::safeGetFileTree(const iDescriptorDevice *device, + const std::string &path, bool checkDir, + std::optional altAfc) { return executeOperation( - device, [path, device, checkDir]() -> AFCFileTree { - return get_file_tree(device, checkDir, path); - }); + device, + [path, device, checkDir](AfcClientHandle *afc) -> AFCFileTree { + return get_file_tree(device, checkDir, path, afc); + }, + altAfc); } QFuture ServiceManager::getFileTreeAsync(const iDescriptorDevice *device, - const std::string &path, bool checkDir) + const std::string &path, bool checkDir, + std::optional altAfc) { - return QtConcurrent::run([device, path, checkDir]() { - return get_file_tree(device, checkDir, path); - }); + return executeOperation>( + device, + [device, path, checkDir]() -> QFuture { + return QtConcurrent::run([device, path, checkDir]() { + return get_file_tree(device, checkDir, path); + }); + }, + altAfc); } MountedImageInfo @@ -320,3 +330,51 @@ IdeviceFfiError *ServiceManager::exportFileToPath( return nullptr; // Success }); } + +IdeviceFfiError * +ServiceManager::takeScreenshot(const iDescriptorDevice *device, + ScreenshotrClientHandle *screenshotrClient, + ScreenshotData *screenshot) +{ + return executeOperation( + device, [device, screenshotrClient, screenshot]() -> IdeviceFfiError * { + return screenshotr_take_screenshot(screenshotrClient, screenshot); + }); +} + +// requires iOS 17+ +IdeviceFfiError *ServiceManager::enableDevMode(const iDescriptorDevice *device) +{ + return executeOperation( + device, [device]() -> IdeviceFfiError * { + IdeviceFfiError *err = nullptr; + AmfiClientHandle *amfi = nullptr; + err = amfi_connect(device->provider, &amfi); + if (err == NULL) { + // Show developer mode option in settings + err = amfi_reveal_developer_mode_option_in_ui(amfi); + if (err != NULL) { + return err; + } + qDebug() << "Developer mode option revealed in UI."; + // // Enable developer mode (triggers reboot) + err = amfi_accept_developer_mode(amfi); + if (err != NULL) { + qDebug() << "Failed to accept developer mode." + << err->message << "Code:" << err->code; + return err; + } + + err = amfi_enable_developer_mode(amfi); + + if (err != NULL) { + qDebug() << "Failed to enable developer mode." + << err->message << "Code:" << err->code; + return err; + } + qDebug() << "Developer mode enabled, device will reboot."; + // // After reboot, accept developer mode + } + return err; + }); +} \ No newline at end of file diff --git a/src/servicemanager.h b/src/servicemanager.h index 695684e..1c892f5 100644 --- a/src/servicemanager.h +++ b/src/servicemanager.h @@ -216,7 +216,7 @@ public: // Specific AFC operation wrappers static IdeviceFfiError *safeAfcReadDirectory( const iDescriptorDevice *device, const char *path, char ***dirs, - std::optional altAfc = std::nullopt); + size_t count, std::optional altAfc = std::nullopt); static IdeviceFfiError * safeAfcGetFileInfo(const iDescriptorDevice *device, const char *path, @@ -245,14 +245,17 @@ public: AfcFileHandle *handle, off_t *position); // Utility functions - static QByteArray - safeReadAfcFileToByteArray(const iDescriptorDevice *device, - const char *path); - static AFCFileTree safeGetFileTree(const iDescriptorDevice *device, - const std::string &path, bool checkDir); + static QByteArray safeReadAfcFileToByteArray( + const iDescriptorDevice *device, const char *path, + std::optional altAfc = std::nullopt); + static AFCFileTree + safeGetFileTree(const iDescriptorDevice *device, const std::string &path, + bool checkDir, + std::optional altAfc = std::nullopt); static QFuture getFileTreeAsync(const iDescriptorDevice *device, const std::string &path, - bool checkDir); + bool checkDir, + std::optional altAfc = std::nullopt); static MountedImageInfo getMountedImage(const iDescriptorDevice *device); static IdeviceFfiError *mountImage(const iDescriptorDevice *device, const char *image_file, @@ -271,6 +274,13 @@ public: const char *local_path, std::function progressCallback = nullptr, std::atomic *cancelRequested = nullptr); + + static IdeviceFfiError * + takeScreenshot(const iDescriptorDevice *device, + ScreenshotrClientHandle *screenshotrClient, + ScreenshotData *screenshot); + + static IdeviceFfiError *enableDevMode(const iDescriptorDevice *device); }; #endif // SERVICEMANAGER_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index a8d56f1..8c9d47a 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -79,9 +79,8 @@ void ToolboxWidget::setupUI() m_scrollArea = new QScrollArea(); m_scrollArea->setWidgetResizable(true); m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setStyleSheet( - "QScrollArea { background: transparent; border: none; }"); - m_scrollArea->viewport()->setStyleSheet("background: transparent;"); + m_scrollArea->setFrameStyle(QFrame::NoFrame); + m_scrollArea->viewport()->setAutoFillBackground(false); m_contentWidget = new QWidget(); QVBoxLayout *contentLayout = new QVBoxLayout(m_contentWidget); diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index c94fbf8..198b161 100644 --- a/src/virtuallocationwidget.cpp +++ b/src/virtuallocationwidget.cpp @@ -22,6 +22,7 @@ #include "devdiskimagehelper.h" #include "devdiskmanager.h" #include "iDescriptor.h" +#include "servicemanager.h" #include "settingsmanager.h" #include #include @@ -140,9 +141,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent) // Register this object with QML context so QML can call our slots m_quickWidget->rootContext()->setContextProperty("cppHandler", this); - qDebug() << "QuickWidget status:" << m_quickWidget->status(); - qDebug() << "QuickWidget errors:" << m_quickWidget->errors(); - connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, [this](const std::string &udid) { if (m_device->udid == udid) { @@ -347,20 +345,138 @@ void VirtualLocation::onApplyClicked() // }, // Qt::SingleShotConnection); // devDiskImageHelper->start(); - // FIXME: create issue for c bindings - IdeviceFfiError *err = location_simulation_set(m_device->locationSimulation, - latitude, longitude); - if (err != nullptr) { - QMessageBox::warning(this, "Error", - "Failed to set location on device:\n" + - QString::fromStdString(err->message)); - // idevice_ffi_error_free(err); - } else { - // SettingsManager::sharedInstance()->saveRecentLocation( - // latitude, longitude); - QMessageBox::information(this, "Success", - "Location applied successfully!"); + + int major = m_device->deviceInfo.parsedDeviceVersion.major; + + if (major < 17) { + QMessageBox::warning(this, "TODO", "TODO"); + m_applyButton->setEnabled(true); + return; } + + IdeviceFfiError *err = nullptr; + // Connect to CoreDeviceProxy + CoreDeviceProxyHandle *core_device = NULL; + err = core_device_proxy_connect(m_device->provider, &core_device); + if (err != NULL) { + fprintf(stderr, "Failed to connect to CoreDeviceProxy: [%d] %s", + err->code, err->message); + idevice_error_free(err); + } + + // Get server RSD port + uint16_t rsd_port; + err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port); + if (err != NULL) { + fprintf(stderr, "Failed to get server RSD port: [%d] %s", err->code, + err->message); + idevice_error_free(err); + core_device_proxy_free(core_device); + } + + // Create TCP adapter and connect to RSD port + AdapterHandle *adapter = NULL; + err = core_device_proxy_create_tcp_adapter(core_device, &adapter); + if (err != NULL) { + fprintf(stderr, "Failed to create TCP adapter: [%d] %s", err->code, + err->message); + idevice_error_free(err); + } + + // Connect to RSD port + ReadWriteOpaque *stream = NULL; + err = adapter_connect(adapter, rsd_port, &stream); + if (err != NULL) { + fprintf(stderr, "Failed to connect to RSD port: [%d] %s", err->code, + err->message); + idevice_error_free(err); + adapter_free(adapter); + } + + RsdHandshakeHandle *handshake = NULL; + err = rsd_handshake_new(stream, &handshake); + if (err != NULL) { + fprintf(stderr, "Failed to perform RSD handshake: [%d] %s", err->code, + err->message); + idevice_error_free(err); + // adapter_close(stream); + idevice_stream_free(stream); + adapter_free(adapter); + } + + // Create RemoteServerClient + RemoteServerHandle *remote_server = NULL; + err = remote_server_connect_rsd(adapter, handshake, &remote_server); + if (err != NULL) { + // needs dev mode + fprintf(stderr, "Failed to create remote server: [%d] %s", err->code, + err->message); + if (err->code == ServiceNotFoundErrorCode) { + auto res = QMessageBox::question( + this, "Enable Developer Mode?", + "Location Simulation service not found. Enable Developer " + "Mode on the device?", + QMessageBox::Yes | QMessageBox::No); + if (res == QMessageBox::Yes) { + IdeviceFfiError *devmodeErr = + ServiceManager::enableDevMode(m_device); + if (devmodeErr != NULL) { + QMessageBox::warning( + this, "Error", + QString("Failed to enable Developer Mode:\n%1") + .arg(devmodeErr->message)); + idevice_error_free(devmodeErr); + } else { + QMessageBox::information( + this, "Success", + "Developer Mode enabled successfully. Please try " + "applying the location again."); + } + } + + idevice_error_free(err); + adapter_free(adapter); + rsd_handshake_free(handshake); + } + + // Create LocationSimulationClient + LocationSimulationHandle *location_sim = NULL; + err = location_simulation_new(remote_server, &location_sim); + if (err != NULL) { + fprintf(stderr, + "Failed to create location simulation client: [%d] %s", + err->code, err->message); + idevice_error_free(err); + remote_server_free(remote_server); + } + + // Set location + err = location_simulation_set(location_sim, latitude, longitude); + if (err != NULL) { + fprintf(stderr, "Failed to set location: [%d] %s", err->code, + err->message); + idevice_error_free(err); + } else { + printf("Successfully set location to %.6f, %.6f\n", latitude, + longitude); + } + } + + // // FIXME: create issue for c bindings + // IdeviceFfiError *err = + // location_simulation_set(m_device->locationSimulation, + // latitude, longitude); + // if (err != nullptr) { + // QMessageBox::warning(this, "Error", + // "Failed to set location on device:\n" + + // QString::fromStdString(err->message)); + // // idevice_ffi_error_free(err); + // } else { + // // SettingsManager::sharedInstance()->saveRecentLocation( + // // latitude, longitude); + // QMessageBox::information(this, "Success", + // "Location applied successfully!"); + // } } void VirtualLocation::loadRecentLocations(QVBoxLayout *layout) diff --git a/src/zloadingwidget.cpp b/src/zloadingwidget.cpp index 65debd0..fdbfd04 100644 --- a/src/zloadingwidget.cpp +++ b/src/zloadingwidget.cpp @@ -3,28 +3,106 @@ #include "qprocessindicator.h" #include #include +#include -ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) : QWidget{parent} +ZLoadingWidget::ZLoadingWidget(bool start, QWidget *parent) + : QStackedWidget{parent}, m_loadingIndicator(new QProcessIndicator()) { - m_loadingIndicator = new QProcessIndicator(); m_loadingIndicator->setType(QProcessIndicator::line_rotate); m_loadingIndicator->setFixedSize(64, 32); if (start) { m_loadingIndicator->start(); } - QHBoxLayout *loadingLayout = new QHBoxLayout(); + // Create a proper container widget for the loading indicator + QWidget *loadingWidget = new QWidget(this); + QHBoxLayout *loadingLayout = new QHBoxLayout(loadingWidget); loadingLayout->setSpacing(1); - + loadingLayout->addStretch(); loadingLayout->addWidget(m_loadingIndicator); - setLayout(loadingLayout); + loadingLayout->addStretch(); + + addWidget(loadingWidget); // Loading widget at index 0 } -void ZLoadingWidget::stop() +void ZLoadingWidget::setupContentWidget(QWidget *contentWidget) +{ + addWidget(contentWidget); // Content widget at index 1 +} + +void ZLoadingWidget::setupContentWidget(QLayout *contentLayout) +{ + QWidget *contentWidget = new QWidget(); + contentWidget->setLayout(contentLayout); + + addWidget(contentWidget); // Content widget at index 1 +} + +void ZLoadingWidget::setupErrorWidget(QWidget *errorWidget) +{ + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupErrorWidget(QLayout *errorLayout) +{ + QWidget *errorWidget = new QWidget(); + errorWidget->setLayout(errorLayout); + + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupErrorWidget(const QString &errorMessage) +{ + QWidget *errorWidget = new QWidget(); + QVBoxLayout *errorLayout = new QVBoxLayout(errorWidget); + errorLayout->setAlignment(Qt::AlignCenter); + + QLabel *errorLabel = new QLabel(errorMessage); + errorLabel->setAlignment(Qt::AlignCenter); + errorLabel->setStyleSheet("QLabel { color: red; }"); + errorLayout->addWidget(errorLabel); + + addWidget(errorWidget); // Error widget at index 2 +} + +void ZLoadingWidget::setupAditionalWidget(QWidget *customWidget) +{ + addWidget(customWidget); +} + +void ZLoadingWidget::switchToWidget(QWidget *widget) +{ + int index = indexOf(widget); + if (index != -1) { + setCurrentIndex(index); + } +} + +void ZLoadingWidget::stop(bool showContent) { if (m_loadingIndicator) { m_loadingIndicator->stop(); } + if (showContent) { + // FIXME: dont use hardcoded index + setCurrentIndex(1); + } +} + +void ZLoadingWidget::showError() +{ + m_loadingIndicator->stop(); + // FIXME: dont use hardcoded index + setCurrentIndex(2); +} + +void ZLoadingWidget::showLoading() +{ + if (m_loadingIndicator) { + m_loadingIndicator->start(); + } + // FIXME: dont use hardcoded index + setCurrentIndex(0); } ZLoadingWidget::~ZLoadingWidget() diff --git a/src/zloadingwidget.h b/src/zloadingwidget.h index 1c8050e..9ddabf4 100644 --- a/src/zloadingwidget.h +++ b/src/zloadingwidget.h @@ -1,15 +1,25 @@ #ifndef ZLOADINGWIDGET_H #define ZLOADINGWIDGET_H +#include #include -class ZLoadingWidget : public QWidget +class ZLoadingWidget : public QStackedWidget { Q_OBJECT public: explicit ZLoadingWidget(bool start = true, QWidget *parent = nullptr); ~ZLoadingWidget(); - void stop(); + void stop(bool showContent = true); + void showLoading(); + void setupContentWidget(QWidget *contentWidget); + void setupContentWidget(QLayout *contentLayout); + void setupErrorWidget(QWidget *errorWidget); + void setupErrorWidget(const QString &errorMessage); + void setupErrorWidget(QLayout *errorLayout); + void setupAditionalWidget(QWidget *customWidget); + void switchToWidget(QWidget *widget); + void showError(); private: class QProcessIndicator *m_loadingIndicator = nullptr;