mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -66,8 +66,6 @@ private:
|
||||
QPushButton *m_retryButton;
|
||||
QPushButton *m_cancelButton;
|
||||
|
||||
bool m_isDownloading;
|
||||
bool m_isMounting;
|
||||
QString m_downloadingVersion;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user