Refactor LiveScreenWidget, implement HouseArrest , fix bugs

- Introduced ScreenshotrThread to manage screenshot capturing in a separate thread.
- Updated LiveScreenWidget to utilize the new thread for capturing screenshots.
- Removed unused timer and related code for periodic screenshot updates.
- Enhanced error handling and initialization logic for screenshot service.
- Updated ServiceManager to include methods for taking screenshots and enabling developer mode.
- Refactored various methods in ServiceManager to accept optional parameters for AFC client handling.
- Improved error handling in VirtualLocation for setting device location.
- Enhanced ZLoadingWidget to support multiple content states using QStackedWidget.
- Cleaned up code and comments across multiple files for better readability and maintainability.
This commit is contained in:
uncor3
2026-01-15 02:06:11 +00:00
parent 2a71f011d9
commit ac81ee087c
25 changed files with 901 additions and 791 deletions
+2 -2
View File
@@ -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;
-1
View File
@@ -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) {
+45 -6
View File
@@ -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,
+3
View File
@@ -21,6 +21,7 @@
#define CABLEINFOWIDGET_H
#include "iDescriptor.h"
#include "zloadingwidget.h"
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
@@ -73,6 +74,8 @@ private:
QLabel *m_descriptionLabel;
QGroupBox *m_infoWidget;
QGridLayout *m_infoLayout;
ZLoadingWidget *m_loadingWidget;
QLabel *m_errorLabel;
// Data
iDescriptorDevice *m_device;
+12 -9
View File
@@ -24,7 +24,8 @@
#include <string.h>
AFCFileTree get_file_tree(const iDescriptorDevice *device, bool checkDir,
const std::string &path)
const std::string &path,
std::optional<AfcClientHandle *> 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;
+2 -9
View File
@@ -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>(
DiagnosticsRelay::adopt(diagnostics_relay));
result.locationSimulation = location_simulation;
+46 -58
View File
@@ -18,8 +18,10 @@
*/
#include "devdiskimagehelper.h"
#include "appcontext.h"
#include "devdiskmanager.h"
#include "qprocessindicator.h"
#include "servicemanager.h"
#include "settingsmanager.h"
#include <QDebug>
#include <QHBoxLayout>
@@ -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)
-2
View File
@@ -66,8 +66,6 @@ private:
QPushButton *m_retryButton;
QPushButton *m_cancelButton;
bool m_isDownloading;
bool m_isMounting;
QString m_downloadingVersion;
};
+4 -14
View File
@@ -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 =
+64 -76
View File
@@ -400,92 +400,80 @@ void DiskUsageWidget::fetchData()
QFuture<QVariantMap> 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);
}
+15 -25
View File
@@ -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();
}
-2
View File
@@ -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
+5 -6
View File
@@ -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<DiagnosticsRelay> diagRelay;
ScreenshotrClientHandle *screenshotrClient;
LocationSimulationHandle *locationSimulation;
};
@@ -231,7 +231,6 @@ struct iDescriptorInitDeviceResult {
LockdowndClientHandle *lockdown;
ImageMounterHandle *imageMounter;
std::shared_ptr<DiagnosticsRelay> 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<AfcClientHandle *> 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);
+271 -357
View File
@@ -22,6 +22,7 @@
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "qprocessindicator.h"
#include "servicemanager.h"
#include "zlineedit.h"
#include <QAction>
#include <QApplication>
@@ -36,7 +37,8 @@
#include <QtConcurrent/QtConcurrent>
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<QVariantMap> future = QtConcurrent::run([this]() -> QVariantMap {
QVariantMap result;
QVariantList apps;
// QFuture<QVariantMap> 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<const char *>(out_result),
static_cast<int>(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<QVariantMap> future = QtConcurrent::run([this, bundleId]()
// -> QVariantMap {
// QVariantMap result;
QFuture<QVariantMap> 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<void *>(afcClient));
result["houseArrestClient"] = QVariant::fromValue(
reinterpret_cast<void *>(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<void *>(afcClient));
// result["houseArrestClient"] = QVariant::fromValue(
// reinterpret_cast<void *>(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<afc_client_t>(
// result.value("afcClient").value<void *>());
// m_houseArrestClient = reinterpret_cast<house_arrest_client_t>(
// result.value("houseArrestClient").value<void *>());
// Get the AFC clients from the result and store them as member
// variables
m_houseArrestAfcClient = reinterpret_cast<AfcClientHandle *>(
result.value("afcClient").value<void *>());
m_houseArrestClient = reinterpret_cast<HouseArrestClientHandle *>(
result.value("houseArrestClient").value<void *>());
// 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()
+5 -5
View File
@@ -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<QVariantMap> *m_watcher;
QFutureWatcher<QVariantMap> *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<AppTabWidget *> m_appTabs;
AppTabWidget *m_selectedTab = nullptr;
+44 -151
View File
@@ -29,9 +29,10 @@
#include <QSpinBox>
#include <QTimer>
#include <QVBoxLayout>
// 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<int>(screenshot.length), // width
// 1, // height
// QImage::Format_ARGB32); // format
QByteArray byteArray(
reinterpret_cast<const char *>(screenshot.data),
static_cast<int>(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();
}
+50 -3
View File
@@ -21,10 +21,57 @@
#define LIVESCREEN_H
#include "iDescriptor.h"
#include "servicemanager.h"
#include <QLabel>
#include <QThread>
#include <QTimer>
#include <QWidget>
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<const char *>(screenshotData.data),
static_cast<int>(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
+4 -8
View File
@@ -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++;
}
}
+3 -2
View File
@@ -23,19 +23,20 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QTimer>
#include <sstream>
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();
+79 -21
View File
@@ -21,12 +21,10 @@
#include "iDescriptor.h"
#include <QtConcurrent>
IdeviceFfiError *
ServiceManager::safeAfcReadDirectory(const iDescriptorDevice *device,
const char *path, char ***dirs,
std::optional<AfcClientHandle *> altAfc)
IdeviceFfiError *ServiceManager::safeAfcReadDirectory(
const iDescriptorDevice *device, const char *path, char ***dirs,
size_t count, std::optional<AfcClientHandle *> 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<AfcClientHandle *> altAfc)
{
return executeOperation<QByteArray>(device, [path, device]() -> QByteArray {
return read_afc_file_to_byte_array(device, path);
});
return executeOperation<QByteArray>(
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<AfcClientHandle *> altAfc)
{
return executeOperation<AFCFileTree>(
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<AFCFileTree>
ServiceManager::getFileTreeAsync(const iDescriptorDevice *device,
const std::string &path, bool checkDir)
const std::string &path, bool checkDir,
std::optional<AfcClientHandle *> altAfc)
{
return QtConcurrent::run([device, path, checkDir]() {
return get_file_tree(device, checkDir, path);
});
return executeOperation<QFuture<AFCFileTree>>(
device,
[device, path, checkDir]() -> QFuture<AFCFileTree> {
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<IdeviceFfiError *>(
device, [device, screenshotrClient, screenshot]() -> IdeviceFfiError * {
return screenshotr_take_screenshot(screenshotrClient, screenshot);
});
}
// requires iOS 17+
IdeviceFfiError *ServiceManager::enableDevMode(const iDescriptorDevice *device)
{
return executeOperation<IdeviceFfiError *>(
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;
});
}
+17 -7
View File
@@ -216,7 +216,7 @@ public:
// Specific AFC operation wrappers
static IdeviceFfiError *safeAfcReadDirectory(
const iDescriptorDevice *device, const char *path, char ***dirs,
std::optional<AfcClientHandle *> altAfc = std::nullopt);
size_t count, std::optional<AfcClientHandle *> 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<AfcClientHandle *> altAfc = std::nullopt);
static AFCFileTree
safeGetFileTree(const iDescriptorDevice *device, const std::string &path,
bool checkDir,
std::optional<AfcClientHandle *> altAfc = std::nullopt);
static QFuture<AFCFileTree>
getFileTreeAsync(const iDescriptorDevice *device, const std::string &path,
bool checkDir);
bool checkDir,
std::optional<AfcClientHandle *> 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<void(qint64, qint64)> progressCallback = nullptr,
std::atomic<bool> *cancelRequested = nullptr);
static IdeviceFfiError *
takeScreenshot(const iDescriptorDevice *device,
ScreenshotrClientHandle *screenshotrClient,
ScreenshotData *screenshot);
static IdeviceFfiError *enableDevMode(const iDescriptorDevice *device);
};
#endif // SERVICEMANAGER_H
+2 -3
View File
@@ -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);
+132 -16
View File
@@ -22,6 +22,7 @@
#include "devdiskimagehelper.h"
#include "devdiskmanager.h"
#include "iDescriptor.h"
#include "servicemanager.h"
#include "settingsmanager.h"
#include <QDebug>
#include <QDoubleValidator>
@@ -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)
+84 -6
View File
@@ -3,28 +3,106 @@
#include "qprocessindicator.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QStackedWidget>
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()
+12 -2
View File
@@ -1,15 +1,25 @@
#ifndef ZLOADINGWIDGET_H
#define ZLOADINGWIDGET_H
#include <QStackedWidget>
#include <QWidget>
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;