mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
add initial recovery device support for new UI & update styling
- Added QStackedWidget to manage different states in InstalledAppsWidget. - Created separate loading, error, and content widgets for better UI management. - Updated LoginDialog title and adjusted styles for labels. - Enhanced MainWindow by removing unused code and improving device handling. - Updated QueryMobileGestaltWidget UI for better clarity and usability. - Removed deprecated screenshot handling code in RealtimeScreen.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
<file>icons/MdiLightningBolt.png</file>
|
||||
<file>icons/MingcuteSettings7Line.png</file>
|
||||
<file>icons/ClarityHardDiskSolidAlerted.png</file>
|
||||
<file>icons/icon.png</file>
|
||||
<file>qml/MapView.qml</file>
|
||||
<file>resources/dump.js</file>
|
||||
<file>resources/iphone.png</file>
|
||||
|
||||
@@ -33,7 +33,9 @@ AfcExplorerWidget::AfcExplorerWidget(afc_client_t afcClient,
|
||||
|
||||
// Main layout
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
|
||||
mainLayout->addWidget(m_explorer);
|
||||
|
||||
// Initialize
|
||||
@@ -170,7 +172,6 @@ void AfcExplorerWidget::updateBreadcrumb(const QString &path)
|
||||
void AfcExplorerWidget::loadPath(const QString &path)
|
||||
{
|
||||
m_fileList->clear();
|
||||
// pathLabel->setText(path); // removed
|
||||
|
||||
updateBreadcrumb(path);
|
||||
|
||||
@@ -263,16 +264,6 @@ void AfcExplorerWidget::onExportClicked()
|
||||
}
|
||||
}
|
||||
|
||||
void AfcExplorerWidget::onExportDeleteClicked()
|
||||
{
|
||||
// Placeholder for future implementation
|
||||
QList<QListWidgetItem *> selectedItems = m_fileList->selectedItems();
|
||||
if (selectedItems.isEmpty())
|
||||
return;
|
||||
// TODO: Implement export & delete logic
|
||||
return;
|
||||
}
|
||||
|
||||
void AfcExplorerWidget::exportSelectedFile(QListWidgetItem *item,
|
||||
const QString &directory)
|
||||
{
|
||||
@@ -438,17 +429,18 @@ void AfcExplorerWidget::setupFileExplorer()
|
||||
{
|
||||
m_explorer = new QWidget();
|
||||
QVBoxLayout *explorerLayout = new QVBoxLayout(m_explorer);
|
||||
explorerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_explorer->setStyleSheet("border : none;");
|
||||
|
||||
// Export/Import buttons layout
|
||||
QHBoxLayout *exportLayout = new QHBoxLayout();
|
||||
m_exportBtn = new QPushButton("Export");
|
||||
m_exportDeleteBtn = new QPushButton("Export & Delete");
|
||||
m_importBtn = new QPushButton("Import");
|
||||
m_addToFavoritesBtn = new QPushButton("Add to Favorites");
|
||||
exportLayout->addWidget(m_exportBtn);
|
||||
exportLayout->addWidget(m_exportDeleteBtn);
|
||||
exportLayout->addWidget(m_importBtn);
|
||||
exportLayout->addWidget(m_addToFavoritesBtn);
|
||||
exportLayout->setContentsMargins(0, 0, 0, 0);
|
||||
exportLayout->addStretch();
|
||||
explorerLayout->addLayout(exportLayout);
|
||||
|
||||
@@ -473,8 +465,6 @@ void AfcExplorerWidget::setupFileExplorer()
|
||||
&AfcExplorerWidget::onItemDoubleClicked);
|
||||
connect(m_exportBtn, &QPushButton::clicked, this,
|
||||
&AfcExplorerWidget::onExportClicked);
|
||||
connect(m_exportDeleteBtn, &QPushButton::clicked, this,
|
||||
&AfcExplorerWidget::onExportDeleteClicked);
|
||||
connect(m_importBtn, &QPushButton::clicked, this,
|
||||
&AfcExplorerWidget::onImportClicked);
|
||||
connect(m_addToFavoritesBtn, &QPushButton::clicked, this,
|
||||
|
||||
@@ -33,7 +33,6 @@ private slots:
|
||||
void onBreadcrumbClicked();
|
||||
void onFileListContextMenu(const QPoint &pos);
|
||||
void onExportClicked();
|
||||
void onExportDeleteClicked();
|
||||
void onImportClicked();
|
||||
void onSidebarItemClicked(QTreeWidgetItem *item, int column);
|
||||
void onAddToFavoritesClicked();
|
||||
@@ -43,7 +42,6 @@ private:
|
||||
QWidget *m_explorer;
|
||||
QPushButton *m_backBtn;
|
||||
QPushButton *m_exportBtn;
|
||||
QPushButton *m_exportDeleteBtn;
|
||||
QPushButton *m_importBtn;
|
||||
QPushButton *m_addToFavoritesBtn;
|
||||
QListWidget *m_fileList;
|
||||
|
||||
+22
-54
@@ -74,15 +74,7 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
qDebug() << "Failed to initialize device with UDID: " << udid;
|
||||
// return onDeviceInitFailed(udid, initResult.error);
|
||||
if (initResult.error == LOCKDOWN_E_PASSWORD_PROTECTED) {
|
||||
// warn("Device with UDID " + udid +
|
||||
// " is password protected. Please unlock the device
|
||||
// and " "try again.",
|
||||
// "Warning");
|
||||
// TODO: also handle pairing devices
|
||||
// the reason why we don't handle pairing devices here is
|
||||
// because it's less likely that it will be an error typeof
|
||||
// LOCKDOWN_E_PASSWORD_PROTECTED if the device is paired type
|
||||
if (addType == AddType::Regular) { // TODO:IMPLEMENT
|
||||
if (addType == AddType::Regular) {
|
||||
// FIXME: if a device never gets paired, it will stay in
|
||||
// this
|
||||
m_pendingDevices.append(udid);
|
||||
@@ -128,33 +120,6 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type,
|
||||
// processing device information");
|
||||
}
|
||||
}
|
||||
// TODO:WIP
|
||||
void AppContext::instanceRemoveDevice(QString _udid)
|
||||
{
|
||||
const std::string uuid = _udid.toStdString();
|
||||
if (!m_devices.contains(uuid)) {
|
||||
qDebug() << "Device with UUID " + _udid +
|
||||
" not found. Please report this issue.",
|
||||
"Error";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Removing device with UUID:" << QString::fromStdString(uuid);
|
||||
|
||||
// cleanDevice(device);
|
||||
iDescriptorDevice *device = m_devices[uuid];
|
||||
m_devices.remove(uuid);
|
||||
|
||||
emit deviceRemoved(uuid);
|
||||
// TODO: Cleanup now should be done wherever there are initialized
|
||||
// lockdownd_client_free(device->lockdownClient);
|
||||
if (device->afcClient)
|
||||
afc_client_free(device->afcClient);
|
||||
idevice_free(device->device);
|
||||
// lockdownd_service_descriptor_free(device->lockdownService);
|
||||
delete device;
|
||||
// return true;
|
||||
}
|
||||
|
||||
int AppContext::getConnectedDeviceCount() const
|
||||
{
|
||||
@@ -167,36 +132,28 @@ void AppContext::removeDevice(QString _udid)
|
||||
const std::string uuid = _udid.toStdString();
|
||||
if (!m_devices.contains(uuid)) {
|
||||
qDebug() << "Device with UUID " + _udid +
|
||||
" not found. Please report this issue.",
|
||||
"Error";
|
||||
" not found. Please report this issue.";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Removing device with UUID:" << QString::fromStdString(uuid);
|
||||
|
||||
// cleanDevice(device);
|
||||
iDescriptorDevice *device = m_devices[uuid];
|
||||
m_devices.remove(uuid);
|
||||
|
||||
emit deviceRemoved(uuid);
|
||||
// TODO: Cleanup now should be done wherever there are initialized
|
||||
// lockdownd_client_free(device->lockdownClient);
|
||||
if (device->afcClient)
|
||||
afc_client_free(device->afcClient);
|
||||
idevice_free(device->device);
|
||||
// lockdownd_service_descriptor_free(device->lockdownService);
|
||||
delete device;
|
||||
// return true;
|
||||
}
|
||||
|
||||
void AppContext::removeRecoveryDevice(const QString &ecid)
|
||||
void AppContext::removeRecoveryDevice(QString ecid)
|
||||
{
|
||||
std::string std_ecid = ecid.toStdString();
|
||||
if (!m_recoveryDevices.contains(std_ecid)) {
|
||||
// warning popup
|
||||
warn("Device with ECID " + ecid +
|
||||
" not found. Please report this issue.",
|
||||
"Error");
|
||||
qDebug() << "Device with ECID " + ecid +
|
||||
" not found. Please report this issue.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,9 +163,8 @@ void AppContext::removeRecoveryDevice(const QString &ecid)
|
||||
RecoveryDeviceInfo *deviceInfo = m_recoveryDevices[std_ecid];
|
||||
m_recoveryDevices.remove(std_ecid);
|
||||
|
||||
delete deviceInfo;
|
||||
|
||||
emit recoveryDeviceRemoved(ecid);
|
||||
delete deviceInfo;
|
||||
}
|
||||
|
||||
iDescriptorDevice *AppContext::getDevice(const std::string &uuid)
|
||||
@@ -233,7 +189,7 @@ bool AppContext::noDevicesConnected() const
|
||||
m_pendingDevices.isEmpty());
|
||||
}
|
||||
|
||||
std::string AppContext::addRecoveryDevice(RecoveryDeviceInfo *deviceInfo)
|
||||
void AppContext::addRecoveryDevice(RecoveryDeviceInfo *deviceInfo)
|
||||
{
|
||||
// Generate a unique ID for the recovery device
|
||||
// std::string uuid =
|
||||
@@ -243,11 +199,23 @@ std::string AppContext::addRecoveryDevice(RecoveryDeviceInfo *deviceInfo)
|
||||
// uint64_t to std::string
|
||||
m_recoveryDevices[std::to_string(deviceInfo->ecid)] = deviceInfo;
|
||||
|
||||
// emit recoveryDeviceAdded(uuid);
|
||||
return std::to_string(deviceInfo->ecid);
|
||||
emit recoveryDeviceAdded(deviceInfo);
|
||||
}
|
||||
|
||||
AppContext::~AppContext()
|
||||
{
|
||||
// Cleanup if needed
|
||||
for (auto device : m_devices) {
|
||||
emit deviceRemoved(device->udid);
|
||||
if (device->afcClient)
|
||||
afc_client_free(device->afcClient);
|
||||
idevice_free(device->device);
|
||||
delete device;
|
||||
}
|
||||
|
||||
// TODO
|
||||
for (auto recoveryDevice : m_recoveryDevices) {
|
||||
// emit
|
||||
// recoveryDeviceRemoved(QString::fromStdString(recoveryDevice->ecid));
|
||||
// delete recoveryDevice;
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -16,16 +16,10 @@ public:
|
||||
explicit AppContext(QObject *parent = nullptr);
|
||||
void handleDBusSignal(const QDBusMessage &msg);
|
||||
bool noDevicesConnected() const;
|
||||
std::string addDevice(iDescriptorDevice *device);
|
||||
|
||||
std::string addRecoveryDevice(RecoveryDeviceInfo *deviceInfo);
|
||||
// std::string addRecoveryDevice(const RecoveryDeviceInfo& deviceInfo);
|
||||
void removeRecoveryDevice(const QString &udid);
|
||||
|
||||
// Returns whether there are any devices connected (regular or recovery)
|
||||
QList<RecoveryDeviceInfo *> getAllRecoveryDevices();
|
||||
~AppContext();
|
||||
void instanceRemoveDevice(QString _udid);
|
||||
int getConnectedDeviceCount() const;
|
||||
|
||||
private:
|
||||
@@ -46,6 +40,8 @@ public slots:
|
||||
void removeDevice(QString udid);
|
||||
void addDevice(QString udid, idevice_connection_type connType,
|
||||
AddType addType);
|
||||
void addRecoveryDevice(RecoveryDeviceInfo *deviceInfo);
|
||||
void removeRecoveryDevice(QString ecid);
|
||||
};
|
||||
|
||||
#endif // APPCONTEXT_H
|
||||
|
||||
@@ -54,10 +54,6 @@ void AppsWidget::setupUI()
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget);
|
||||
headerLayout->setContentsMargins(20, 10, 20, 10);
|
||||
|
||||
QLabel *titleLabel = new QLabel("App Store");
|
||||
titleLabel->setStyleSheet(
|
||||
"font-size: 24px; font-weight: bold; color: #333;");
|
||||
|
||||
// Create status label first
|
||||
m_statusLabel = new QLabel("Not signed in");
|
||||
m_statusLabel->setStyleSheet("margin-right: 20px;");
|
||||
@@ -97,9 +93,6 @@ void AppsWidget::setupUI()
|
||||
connect(searchAction, &QAction::triggered, this,
|
||||
&AppsWidget::performSearch);
|
||||
|
||||
headerLayout->addWidget(titleLabel);
|
||||
|
||||
headerLayout->addStretch();
|
||||
headerLayout->addWidget(m_searchEdit);
|
||||
headerLayout->addStretch();
|
||||
headerLayout->addWidget(m_statusLabel);
|
||||
|
||||
+56
-83
@@ -1,6 +1,8 @@
|
||||
#include "cableinfowidget.h"
|
||||
#include "appcontext.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QGroupBox>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
|
||||
@@ -9,14 +11,18 @@ CableInfoWidget::CableInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
{
|
||||
setupUI();
|
||||
initCableInfo();
|
||||
|
||||
// Auto-refresh cable info after UI is set up
|
||||
// QTimer::singleShot(100, this, &CableInfoWidget::refreshCableInfo);
|
||||
connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this,
|
||||
[this](const std::string &udid) {
|
||||
if (m_device->udid == udid) {
|
||||
this->close();
|
||||
this->deleteLater();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CableInfoWidget::setupUI()
|
||||
{
|
||||
setWindowTitle("Cable Information");
|
||||
setWindowTitle("Cable Information - iDescriptor");
|
||||
m_mainLayout = new QVBoxLayout(this);
|
||||
m_mainLayout->setSpacing(20);
|
||||
m_mainLayout->setContentsMargins(20, 20, 20, 20);
|
||||
@@ -24,70 +30,50 @@ void CableInfoWidget::setupUI()
|
||||
// Header section
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout();
|
||||
|
||||
m_iconLabel = new QLabel();
|
||||
m_iconLabel->setFixedSize(48, 48);
|
||||
m_iconLabel->setScaledContents(true);
|
||||
m_iconLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_statusLabel = new QLabel("Analyzing cable...");
|
||||
m_statusLabel->setStyleSheet("QLabel { "
|
||||
"font-size: 18px; "
|
||||
"font-weight: bold; "
|
||||
"color: #333; "
|
||||
"}");
|
||||
m_descriptionLabel =
|
||||
new QLabel("Please wait while we analyze the connected cable.");
|
||||
m_descriptionLabel->setStyleSheet("font-size: 9px;");
|
||||
|
||||
headerLayout->addWidget(m_iconLabel);
|
||||
headerLayout->addWidget(m_statusLabel, 1);
|
||||
headerLayout->addWidget(m_statusLabel);
|
||||
|
||||
m_mainLayout->addLayout(headerLayout);
|
||||
|
||||
// Scroll area to make the info section scrollable
|
||||
QScrollArea *scrollArea = new QScrollArea();
|
||||
scrollArea->setWidgetResizable(true);
|
||||
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||
scrollArea->setStyleSheet("QScrollArea { "
|
||||
"background-color: #f8f9fa; "
|
||||
"border: 1px solid #dee2e6; "
|
||||
"border-radius: 8px; "
|
||||
"}");
|
||||
m_infoWidget = new QGroupBox("Cable Information");
|
||||
|
||||
// Info widget that goes inside the scroll area
|
||||
m_infoWidget = new QWidget();
|
||||
m_infoWidget->setStyleSheet("QWidget { "
|
||||
"background: transparent; "
|
||||
"padding: 16px; "
|
||||
"color: #333; "
|
||||
"}");
|
||||
m_infoLayout = new QGridLayout(m_infoWidget);
|
||||
m_infoLayout->setSpacing(12);
|
||||
m_infoLayout->setColumnStretch(1, 1);
|
||||
|
||||
scrollArea->setWidget(m_infoWidget);
|
||||
|
||||
m_mainLayout->addWidget(scrollArea);
|
||||
m_mainLayout->addWidget(m_descriptionLabel);
|
||||
m_mainLayout->addWidget(m_infoWidget);
|
||||
m_mainLayout->addStretch();
|
||||
}
|
||||
|
||||
void CableInfoWidget::initCableInfo()
|
||||
{
|
||||
if (!m_device || !m_device->device) {
|
||||
m_statusLabel->setText("❌ Device not available");
|
||||
m_statusLabel->setText("Something went wrong (no device ?)");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }");
|
||||
return;
|
||||
}
|
||||
|
||||
m_statusLabel->setText("Analyzing cable...");
|
||||
m_statusLabel->setStyleSheet(
|
||||
"QLabel { color: #6c757d; font-size: 18px; font-weight: bold; }");
|
||||
|
||||
// Get cable info
|
||||
get_cable_info(m_device->device, m_response);
|
||||
|
||||
char *xml_string = nullptr;
|
||||
uint32_t xml_length = 0;
|
||||
plist_to_xml(m_response, &xml_string, &xml_length);
|
||||
qDebug() << "Cable info plist:\n"
|
||||
<< QString::fromUtf8(xml_string, xml_length);
|
||||
analyzeCableInfo();
|
||||
updateUI();
|
||||
}
|
||||
|
||||
// FIXME: genuine check is not perfect, still need more research
|
||||
void CableInfoWidget::analyzeCableInfo()
|
||||
{
|
||||
qDebug() << "Analyzing cable info...";
|
||||
@@ -118,12 +104,6 @@ void CableInfoWidget::analyzeCableInfo()
|
||||
m_cableInfo.interfaceModuleSerial = QString::fromStdString(
|
||||
ioreg["IOAccessoryInterfaceModuleSerialNumber"].getString());
|
||||
|
||||
// Determine if genuine based on manufacturer and presence of detailed info
|
||||
m_cableInfo.isGenuine =
|
||||
(m_cableInfo.manufacturer.contains("Apple", Qt::CaseInsensitive) &&
|
||||
!m_cableInfo.modelNumber.isEmpty() &&
|
||||
!m_cableInfo.accessoryName.isEmpty());
|
||||
|
||||
// Check if Type-C (based on accessory name or TriStar class)
|
||||
m_cableInfo.triStarClass =
|
||||
QString::fromStdString(ioreg["TriStarICClass"].getString());
|
||||
@@ -131,6 +111,27 @@ void CableInfoWidget::analyzeCableInfo()
|
||||
(m_cableInfo.accessoryName.contains("USB-C", Qt::CaseInsensitive) ||
|
||||
m_cableInfo.triStarClass.contains("1612")); // CBTL1612 is Type-C
|
||||
|
||||
// Determine if genuine based on manufacturer and presence of detailed info
|
||||
bool preGenuineCheck =
|
||||
(m_cableInfo.manufacturer.contains("Apple", Qt::CaseInsensitive) &&
|
||||
!m_cableInfo.modelNumber.isEmpty() &&
|
||||
!m_cableInfo.accessoryName.isEmpty());
|
||||
|
||||
// Further checks for Type-C cables
|
||||
// if report says it's Type-C, it must match the actual connection type
|
||||
if (m_cableInfo.isTypeC) {
|
||||
bool actuallyTypeC =
|
||||
m_device->deviceInfo.batteryInfo.usbConnectionType ==
|
||||
BatteryInfo::ConnectionType::USB_TYPEC;
|
||||
if (!actuallyTypeC) {
|
||||
// most likely a fake cable with faked info
|
||||
m_cableInfo.isFakeInfo = true;
|
||||
}
|
||||
m_cableInfo.isGenuine = actuallyTypeC && preGenuineCheck;
|
||||
} else {
|
||||
m_cableInfo.isGenuine = preGenuineCheck;
|
||||
}
|
||||
|
||||
// Power information
|
||||
m_cableInfo.currentLimit = ioreg["IOAccessoryUSBCurrentLimit"].getUInt();
|
||||
m_cableInfo.chargingVoltage =
|
||||
@@ -181,46 +182,31 @@ void CableInfoWidget::updateUI()
|
||||
delete item;
|
||||
}
|
||||
|
||||
// if (!m_cableInfo.isConnected) {
|
||||
// m_statusLabel->setText("❌ No cable detected");
|
||||
// m_statusLabel->setStyleSheet(
|
||||
// "QLabel { color: #dc3545; font-size: 18px; font-weight: bold;
|
||||
// }");
|
||||
// m_iconLabel->setText("🔌");
|
||||
// m_iconLabel->setStyleSheet("QLabel { font-size: 32px; }");
|
||||
|
||||
// QLabel *noDataLabel = new QLabel("No cable information available");
|
||||
// noDataLabel->setStyleSheet(
|
||||
// "QLabel { color: #6c757d; font-size: 14px; text-align: center;
|
||||
// }");
|
||||
// m_infoLayout->addWidget(noDataLabel, 0, 0, 1, 2, Qt::AlignCenter);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Update status and icon based on cable type
|
||||
QString statusText;
|
||||
QString statusStyle;
|
||||
QString iconText;
|
||||
// todo: sometimes they fake the manufacturer even if it's not genuine
|
||||
// compare m_cableInfo.isTypeC to the actual values we get from ioreg
|
||||
|
||||
m_descriptionLabel->setText("Please note that this check may not be "
|
||||
"absolute guarantee of authenticity.");
|
||||
if (m_cableInfo.isGenuine) {
|
||||
statusText = QString("Genuine %1")
|
||||
statusText = QString("✅ Genuine %1")
|
||||
.arg(m_cableInfo.isTypeC ? "USB-C to Lightning Cable"
|
||||
: "Lightning Cable");
|
||||
statusStyle =
|
||||
"QLabel { color: #28a745; font-size: 18px; font-weight: bold; }";
|
||||
iconText = m_cableInfo.isTypeC ? "Type-C" : "Lightning";
|
||||
} else {
|
||||
statusText = "⚠️ Third-party Cable";
|
||||
statusStyle =
|
||||
"QLabel { color: #ffc107; font-size: 18px; font-weight: bold; }";
|
||||
iconText = "❓";
|
||||
"QLabel { color: #dc3545; font-size: 18px; font-weight: bold; }";
|
||||
|
||||
if (m_cableInfo.isFakeInfo) {
|
||||
m_descriptionLabel->setText("The cable reports false information. "
|
||||
"It is most likely a fake cable.");
|
||||
}
|
||||
}
|
||||
|
||||
m_statusLabel->setText(statusText);
|
||||
m_statusLabel->setStyleSheet(statusStyle);
|
||||
m_iconLabel->setText(iconText);
|
||||
m_iconLabel->setStyleSheet("QLabel { font-size: 32px; }");
|
||||
|
||||
int row = 0;
|
||||
|
||||
@@ -286,24 +272,11 @@ void CableInfoWidget::updateUI()
|
||||
}
|
||||
|
||||
void CableInfoWidget::createInfoRow(QGridLayout *layout, int row,
|
||||
const QString &label, const QString &value,
|
||||
const QString &style)
|
||||
const QString &label, const QString &value)
|
||||
{
|
||||
qDebug() << "Creating info row:" << label << value;
|
||||
QLabel *labelWidget = new QLabel(label);
|
||||
labelWidget->setStyleSheet("QLabel { "
|
||||
"font-weight: bold; "
|
||||
"color: #495057; "
|
||||
"font-size: 13px; "
|
||||
"}");
|
||||
|
||||
QLabel *valueWidget = new QLabel(value);
|
||||
QString valueStyle = style.isEmpty() ? "QLabel { "
|
||||
"color: #212529; "
|
||||
"font-size: 13px; "
|
||||
"}"
|
||||
: style;
|
||||
valueWidget->setStyleSheet(valueStyle);
|
||||
valueWidget->setWordWrap(true);
|
||||
|
||||
layout->addWidget(labelWidget, row, 0, Qt::AlignTop);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
@@ -27,7 +28,7 @@ private:
|
||||
void analyzeCableInfo();
|
||||
void updateUI();
|
||||
void createInfoRow(QGridLayout *layout, int row, const QString &label,
|
||||
const QString &value, const QString &style = "");
|
||||
const QString &value);
|
||||
|
||||
// Cable information structure
|
||||
struct CableInfo {
|
||||
@@ -45,13 +46,14 @@ private:
|
||||
QString triStarClass;
|
||||
QStringList supportedTransports;
|
||||
QStringList activeTransports;
|
||||
bool isFakeInfo = false;
|
||||
};
|
||||
|
||||
// UI components
|
||||
QVBoxLayout *m_mainLayout;
|
||||
QLabel *m_statusLabel;
|
||||
QLabel *m_iconLabel;
|
||||
QWidget *m_infoWidget;
|
||||
QLabel *m_descriptionLabel;
|
||||
QGroupBox *m_infoWidget;
|
||||
QGridLayout *m_infoLayout;
|
||||
|
||||
// Data
|
||||
|
||||
@@ -29,10 +29,9 @@
|
||||
#include <printf.h>
|
||||
#endif
|
||||
|
||||
QPair<bool, plist_t> _get_mounted_image(const char *udid)
|
||||
plist_t _get_mounted_image(const char *udid)
|
||||
{
|
||||
mobile_image_mounter_client_t mim = NULL;
|
||||
int res = -1;
|
||||
lockdownd_client_t lckd = NULL;
|
||||
lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR;
|
||||
afc_client_t afc = NULL;
|
||||
@@ -48,14 +47,12 @@ QPair<bool, plist_t> _get_mounted_image(const char *udid)
|
||||
|
||||
IDEVICE_LOOKUP_USBMUX)) {
|
||||
qDebug() << "ERROR: Could not create idevice!";
|
||||
res = -1;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(
|
||||
device, &lckd, TOOL_NAME))) {
|
||||
qDebug() << "ERROR: Could not connect to lockdownd service!";
|
||||
res = -1;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
@@ -75,30 +72,14 @@ QPair<bool, plist_t> _get_mounted_image(const char *udid)
|
||||
|
||||
if (!service || service->port == 0) {
|
||||
qDebug() << "ERROR: Could not start mobile_image_mounter service!";
|
||||
res = -1;
|
||||
goto leave;
|
||||
}
|
||||
|
||||
// if locked
|
||||
// {
|
||||
// "Error": "DeviceLocked"
|
||||
// }
|
||||
// will sometimes return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the device
|
||||
// is locked - mostly on older devices
|
||||
err = mobile_image_mounter_lookup_image(mim, imagetype, &result);
|
||||
if (err == MOBILE_IMAGE_MOUNTER_E_SUCCESS) {
|
||||
res = 0;
|
||||
} else {
|
||||
res = -1;
|
||||
printf("Error: lookup_image returned %d\n", err);
|
||||
}
|
||||
|
||||
leave:
|
||||
// if (f) {
|
||||
// fclose(f);
|
||||
// }
|
||||
// TODO:need to free result
|
||||
// if (result) {
|
||||
// plist_free(result);
|
||||
// }
|
||||
if (mim) {
|
||||
mobile_image_mounter_free(mim);
|
||||
}
|
||||
@@ -112,7 +93,7 @@ leave:
|
||||
idevice_free(device);
|
||||
}
|
||||
|
||||
return {res == 0, result};
|
||||
return result;
|
||||
}
|
||||
|
||||
// int main(){return 0;}
|
||||
@@ -49,43 +49,28 @@
|
||||
#include <printf.h>
|
||||
#endif
|
||||
|
||||
static int list_mode = 0;
|
||||
static int use_network = 0;
|
||||
static int xml_mode = 0;
|
||||
static const char *udid = NULL;
|
||||
static const char *imagetype = NULL;
|
||||
|
||||
static const char PKG_PATH[] = "PublicStaging";
|
||||
static const char PATH_PREFIX[] = "/private/var/mobile/Media";
|
||||
|
||||
typedef enum {
|
||||
DISK_IMAGE_UPLOAD_TYPE_AFC,
|
||||
DISK_IMAGE_UPLOAD_TYPE_UPLOAD_IMAGE
|
||||
} disk_image_upload_type_t;
|
||||
|
||||
enum cmd_mode {
|
||||
CMD_NONE = 0,
|
||||
CMD_MOUNT,
|
||||
CMD_UNMOUNT,
|
||||
CMD_LIST,
|
||||
CMD_DEVMODESTATUS
|
||||
};
|
||||
static const char *imagetype = NULL;
|
||||
|
||||
// int cmd = CMD_NONE;
|
||||
static const char PKG_PATH[] = "PublicStaging";
|
||||
static const char PATH_PREFIX[] = "/private/var/mobile/Media";
|
||||
|
||||
#ifndef SOURCE_DIR
|
||||
#define SOURCE_DIR "."
|
||||
#endif
|
||||
|
||||
static ssize_t mim_upload_cb(void *buf, size_t size, void *userdata)
|
||||
{
|
||||
return fread(buf, 1, size, (FILE *)userdata);
|
||||
}
|
||||
// TODO: cleanup
|
||||
// TODO: may not work on a broken ,faulty or fake usb cable
|
||||
// TypeC cables work better
|
||||
// TODO : sometimes ERROR: Device is locked, can't mount. Unlock device and try
|
||||
// again.
|
||||
bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
// extend the mobile_image_mounter_error_t type and return sucess if there is
|
||||
// already a disk image
|
||||
mobile_image_mounter_error_t mount_dev_image(const char *udid,
|
||||
const char *image_dir_path)
|
||||
{
|
||||
mobile_image_mounter_client_t mim = NULL;
|
||||
int res = -1;
|
||||
@@ -111,9 +96,7 @@ bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
size_t sig_length = 0;
|
||||
|
||||
if (IDEVICE_E_SUCCESS !=
|
||||
idevice_new_with_options(&device, udid,
|
||||
(use_network) ? IDEVICE_LOOKUP_NETWORK
|
||||
: IDEVICE_LOOKUP_USBMUX)) {
|
||||
idevice_new_with_options(&device, udid, IDEVICE_LOOKUP_USBMUX)) {
|
||||
qDebug() << "ERROR: Could not create idevice!";
|
||||
res = -1;
|
||||
goto leave;
|
||||
@@ -550,7 +533,6 @@ bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
res = -1;
|
||||
goto leave;
|
||||
}
|
||||
qDebug() << "done.";
|
||||
|
||||
qDebug() << "Mounting...";
|
||||
err = mobile_image_mounter_mount_image_with_options(
|
||||
@@ -565,11 +547,18 @@ bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
if (!strcmp(status, "Complete")) {
|
||||
qDebug() << "Done.";
|
||||
res = 0;
|
||||
} else {
|
||||
err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
|
||||
}
|
||||
free(status);
|
||||
} else {
|
||||
err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
|
||||
}
|
||||
} else {
|
||||
err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
|
||||
}
|
||||
if (res != 0) { // If not complete, log the error
|
||||
err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR;
|
||||
node = plist_dict_get_item(result, "Error");
|
||||
if (node) {
|
||||
char *error = NULL;
|
||||
@@ -580,8 +569,17 @@ bool mount_dev_image(const char *udid, const char *image_dir_path)
|
||||
}
|
||||
node = plist_dict_get_item(result, "DetailedError");
|
||||
if (node) {
|
||||
qDebug() << "DetailedError:"
|
||||
<< plist_get_string_ptr(node, NULL);
|
||||
const char *str = plist_get_string_ptr(node, NULL);
|
||||
auto sd_str = std::string(str);
|
||||
if (sd_str.find("already mounted at /Developer") !=
|
||||
std::string::npos) {
|
||||
// FIXME: need an error code for this
|
||||
qDebug() << "Image is already mounted";
|
||||
// res = 0;
|
||||
// err = MOBILE_IMAGE_MOUNTER_E_SUCCESS;
|
||||
} else {
|
||||
qDebug() << "DetailedError:" << str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -628,7 +626,5 @@ leave:
|
||||
free(mountname);
|
||||
}
|
||||
|
||||
return res == 0;
|
||||
}
|
||||
|
||||
// int main(){return 0;}
|
||||
return err;
|
||||
}
|
||||
+130
-100
@@ -2,6 +2,7 @@
|
||||
#include "appcontext.h"
|
||||
#include "devdiskmanager.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "qprocessindicator.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QCloseEvent>
|
||||
#include <QComboBox>
|
||||
@@ -26,18 +27,14 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QStringList>
|
||||
#include <QVBoxLayout>
|
||||
#include <libimobiledevice/mobile_image_mounter.h>
|
||||
#include <string>
|
||||
|
||||
// TODO:sometimes non authentic cables do not work with img mounting
|
||||
|
||||
DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget{parent}, m_currentDevice(device)
|
||||
{
|
||||
setupUi();
|
||||
|
||||
// Connect to manager signals
|
||||
// TODO: can prevent race condition ?
|
||||
connect(DevDiskManager::sharedInstance(), &DevDiskManager::imageListFetched,
|
||||
this, &DevDiskImagesWidget::onImageListFetched);
|
||||
|
||||
@@ -49,6 +46,12 @@ DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device,
|
||||
connect(m_deviceComboBox,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DevDiskImagesWidget::onDeviceSelectionChanged);
|
||||
|
||||
connect(m_imageListWidget, &QListWidget::itemClicked, this,
|
||||
[this](QListWidgetItem *item) {
|
||||
m_mountButton->setEnabled(item != nullptr);
|
||||
});
|
||||
// connect
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::setupUi()
|
||||
@@ -62,6 +65,7 @@ void DevDiskImagesWidget::setupUi()
|
||||
m_deviceComboBox = new QComboBox(this);
|
||||
mountLayout->addWidget(m_deviceComboBox);
|
||||
m_mountButton = new QPushButton("Mount", this);
|
||||
m_mountButton->setEnabled(false);
|
||||
m_check_mountedButton = new QPushButton("Check Mounted", this);
|
||||
connect(m_mountButton, &QPushButton::clicked, this,
|
||||
&DevDiskImagesWidget::onMountButtonClicked);
|
||||
@@ -76,52 +80,45 @@ void DevDiskImagesWidget::setupUi()
|
||||
// main path/info row (no shadow)
|
||||
auto *pathWidget = new QWidget();
|
||||
pathWidget->setLayout(pathLayout);
|
||||
pathLayout->addWidget(
|
||||
new QLabel("You can change the download path from settings :"));
|
||||
QLabel *tipLabel =
|
||||
new QLabel("You can change the download path from settings :");
|
||||
tipLabel->setStyleSheet("font-size: 9px;");
|
||||
pathLayout->addWidget(tipLabel);
|
||||
QPushButton *openSettingsButton = new QPushButton("Open Settings");
|
||||
openSettingsButton->setSizePolicy(QSizePolicy::Preferred,
|
||||
QSizePolicy::Preferred);
|
||||
pathLayout->addWidget(openSettingsButton);
|
||||
pathLayout->addStretch();
|
||||
connect(openSettingsButton, &QPushButton::clicked, this, [this]() {
|
||||
SettingsManager::sharedInstance()->showSettingsDialog();
|
||||
});
|
||||
pathLayout->setContentsMargins(10, 10, 10, 10);
|
||||
layout->addWidget(pathWidget);
|
||||
|
||||
// thin centered bottom line + shadow (shadow only applied to this line)
|
||||
QWidget *lineContainer = new QWidget();
|
||||
QHBoxLayout *lineLayout = new QHBoxLayout(lineContainer);
|
||||
lineLayout->setContentsMargins(0, 0, 0, 0); // adjust centering / width
|
||||
lineLayout->setSpacing(0);
|
||||
|
||||
QWidget *innerLine = new QWidget();
|
||||
innerLine->setFixedHeight(2); // thickness of the visible border
|
||||
innerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
innerLine->setStyleSheet("background-color: #363d32;");
|
||||
innerLine->setLayout(new QHBoxLayout());
|
||||
innerLine->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
innerLine->layout()->setSpacing(0);
|
||||
|
||||
// apply shadow only to the thin line so shadow appears only under bottom
|
||||
QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this);
|
||||
shadow->setBlurRadius(30);
|
||||
shadow->setColor(QColor(0, 0, 0, 30));
|
||||
shadow->setOffset(0, 6);
|
||||
innerLine->setGraphicsEffect(shadow);
|
||||
|
||||
// If you want the line to be shorter than full width, give it a max width:
|
||||
// innerLine->setMaximumWidth( int(width * 0.8) ); // or manage in
|
||||
// resizeEvent
|
||||
|
||||
lineLayout->addStretch();
|
||||
lineLayout->addWidget(innerLine);
|
||||
lineLayout->addStretch();
|
||||
layout->addWidget(lineContainer);
|
||||
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
layout->addWidget(m_stackedWidget);
|
||||
|
||||
// Create loading page with process indicator
|
||||
auto *loadingPage = new QWidget();
|
||||
auto *loadingLayout = new QVBoxLayout(loadingPage);
|
||||
loadingLayout->addStretch();
|
||||
|
||||
auto *indicatorLayout = new QHBoxLayout();
|
||||
indicatorLayout->addStretch();
|
||||
m_processIndicator = new QProcessIndicator(loadingPage);
|
||||
m_processIndicator->setFixedSize(40, 40);
|
||||
m_processIndicator->setType(QProcessIndicator::line_rotate);
|
||||
indicatorLayout->addWidget(m_processIndicator);
|
||||
indicatorLayout->addStretch();
|
||||
loadingLayout->addLayout(indicatorLayout);
|
||||
|
||||
m_statusLabel = new QLabel("Fetching image list...");
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_stackedWidget->addWidget(m_statusLabel);
|
||||
m_statusLabel->setStyleSheet("QLabel { color: #666; margin-top: 10px; }");
|
||||
loadingLayout->addWidget(m_statusLabel);
|
||||
loadingLayout->addStretch();
|
||||
|
||||
m_stackedWidget->addWidget(loadingPage);
|
||||
|
||||
m_imageListWidget = new QListWidget(this);
|
||||
m_imageListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
@@ -131,15 +128,19 @@ void DevDiskImagesWidget::setupUi()
|
||||
|
||||
m_stackedWidget->addWidget(m_imageListWidget);
|
||||
|
||||
displayImages();
|
||||
if (DevDiskManager::sharedInstance()->isImageListReady()) {
|
||||
displayImages();
|
||||
m_stackedWidget->setCurrentWidget(m_imageListWidget);
|
||||
} else {
|
||||
m_processIndicator->start();
|
||||
m_stackedWidget->setCurrentIndex(0); // Show loading page
|
||||
}
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::fetchImages()
|
||||
{
|
||||
m_stackedWidget->setCurrentWidget(m_statusLabel);
|
||||
m_processIndicator->start();
|
||||
m_stackedWidget->setCurrentIndex(0); // Show loading page
|
||||
m_statusLabel->setText("Fetching image list...");
|
||||
// DevDiskManager::sharedInstance()->fetchImageList();
|
||||
}
|
||||
@@ -147,14 +148,17 @@ void DevDiskImagesWidget::fetchImages()
|
||||
void DevDiskImagesWidget::onImageListFetched(bool success,
|
||||
const QString &errorMessage)
|
||||
{
|
||||
m_processIndicator->stop();
|
||||
|
||||
qDebug() << "Image list fetched successfully";
|
||||
if (!success) {
|
||||
qDebug() << "Error fetching image list:" << errorMessage;
|
||||
m_statusLabel->setText(
|
||||
QString("Error fetching image list: %1").arg(errorMessage));
|
||||
// Keep showing the loading page with error message
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Image list fetched successfully";
|
||||
displayImages();
|
||||
m_stackedWidget->setCurrentWidget(m_imageListWidget);
|
||||
}
|
||||
@@ -165,7 +169,11 @@ void DevDiskImagesWidget::onDeviceSelectionChanged(int index)
|
||||
index >= AppContext::sharedInstance()->getAllDevices().size())
|
||||
return;
|
||||
|
||||
m_currentDevice = AppContext::sharedInstance()->getAllDevices()[index];
|
||||
auto device = AppContext::sharedInstance()->getAllDevices()[index];
|
||||
if (device == nullptr)
|
||||
return;
|
||||
|
||||
m_currentDevice = device;
|
||||
displayImages();
|
||||
}
|
||||
|
||||
@@ -191,7 +199,7 @@ void DevDiskImagesWidget::displayImages()
|
||||
// Parse images using manager
|
||||
GetImagesSortedFinalResult sortedResult =
|
||||
DevDiskManager::sharedInstance()->parseImageList(
|
||||
deviceMajorVersion, deviceMinorVersion, m_mounted_sig,
|
||||
deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(),
|
||||
m_mounted_sig_len);
|
||||
|
||||
auto compatibleImages = sortedResult.compatibleImages;
|
||||
@@ -465,6 +473,16 @@ void DevDiskImagesWidget::onFileDownloadFinished()
|
||||
void DevDiskImagesWidget::updateDeviceList()
|
||||
{
|
||||
auto devices = AppContext::sharedInstance()->getAllDevices();
|
||||
|
||||
if (devices.isEmpty()) {
|
||||
m_currentDevice = nullptr;
|
||||
m_check_mountedButton->setEnabled(false);
|
||||
m_deviceComboBox->setEnabled(false);
|
||||
} else {
|
||||
m_deviceComboBox->setEnabled(true);
|
||||
m_check_mountedButton->setEnabled(true);
|
||||
}
|
||||
|
||||
QString currentUdid = "";
|
||||
if (m_deviceComboBox->count() > 0 &&
|
||||
m_deviceComboBox->currentIndex() >= 0) {
|
||||
@@ -477,9 +495,9 @@ void DevDiskImagesWidget::updateDeviceList()
|
||||
for (int i = 0; i < devices.size(); ++i) {
|
||||
auto *device = devices.at(i);
|
||||
m_deviceComboBox->addItem(
|
||||
QString("%1 (%2)")
|
||||
QString("%1 / (%2)")
|
||||
.arg(QString::fromStdString(device->deviceInfo.deviceName))
|
||||
.arg(QString::fromStdString(device->udid)),
|
||||
.arg(QString::fromStdString(device->deviceInfo.productType)),
|
||||
QString::fromStdString(device->udid));
|
||||
if (QString().fromStdString((device->udid)) == currentUdid) {
|
||||
newIndex = i;
|
||||
@@ -494,6 +512,7 @@ void DevDiskImagesWidget::updateDeviceList()
|
||||
|
||||
void DevDiskImagesWidget::onMountButtonClicked()
|
||||
{
|
||||
qDebug() << "Current index:" << m_deviceComboBox->currentIndex();
|
||||
if (m_deviceComboBox->currentIndex() < 0) {
|
||||
QMessageBox::warning(this, "No Device",
|
||||
"Please select a device to mount the image on.");
|
||||
@@ -514,12 +533,13 @@ void DevDiskImagesWidget::onMountButtonClicked()
|
||||
|
||||
QString version = button->property("version").toString();
|
||||
|
||||
mountImage(version);
|
||||
this->mountImage(version);
|
||||
}
|
||||
|
||||
void DevDiskImagesWidget::mountImage(const QString &version)
|
||||
{
|
||||
QString udid = m_deviceComboBox->currentData().toString();
|
||||
m_deviceComboBox->setEnabled(false);
|
||||
if (udid.isEmpty()) {
|
||||
QMessageBox::warning(this, "No Device", "Please select a device.");
|
||||
return;
|
||||
@@ -538,20 +558,60 @@ void DevDiskImagesWidget::mountImage(const QString &version)
|
||||
m_mountButton->setEnabled(false);
|
||||
m_mountButton->setText("Mounting...");
|
||||
|
||||
bool success = DevDiskManager::sharedInstance()->mountImage(version, udid);
|
||||
mobile_image_mounter_error_t err =
|
||||
DevDiskManager::sharedInstance()->mountImage(version, udid);
|
||||
|
||||
m_mountButton->setEnabled(true);
|
||||
m_mountButton->setText("Mount");
|
||||
auto updateUI = [&]() {
|
||||
m_mountButton->setEnabled(true);
|
||||
m_mountButton->setText("Mount");
|
||||
m_deviceComboBox->setEnabled(true);
|
||||
};
|
||||
|
||||
if (success) {
|
||||
switch (err) {
|
||||
case MOBILE_IMAGE_MOUNTER_E_INVALID_ARG:
|
||||
QMessageBox::critical(this, "Mount Failed",
|
||||
"Invalid argument provided for mounting.");
|
||||
updateUI();
|
||||
return;
|
||||
case MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED:
|
||||
QMessageBox::critical(this, "Mount Failed",
|
||||
"The device is locked. Please unlock it and try "
|
||||
"again.");
|
||||
updateUI();
|
||||
return;
|
||||
case MOBILE_IMAGE_MOUNTER_E_SUCCESS:
|
||||
QMessageBox::information(this, "Success",
|
||||
QString("Image mounted successfully on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
displayImages(); // Refresh to show mounted status
|
||||
} else {
|
||||
QMessageBox::critical(this, "Mount Failed",
|
||||
QString("Failed to mount image on %1.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
updateUI();
|
||||
break;
|
||||
default:
|
||||
GetMountedImageResult result =
|
||||
DevDiskManager::sharedInstance()->getMountedImage(
|
||||
udid.toStdString().c_str());
|
||||
/*
|
||||
* FIXME: there is no error enum like
|
||||
* MOBILE_IMAGE_MOUNTER_E_ALREADY_MOUNTED so we work around here
|
||||
*/
|
||||
qDebug() << "Mount result:" << result.success << result.message.c_str()
|
||||
<< QString::fromStdString(result.sig);
|
||||
if (result.success && !result.sig.empty()) {
|
||||
m_mounted_sig = result.sig;
|
||||
m_mounted_sig_len = result.sig.size();
|
||||
updateUI();
|
||||
displayImages();
|
||||
QMessageBox::information(this, "Already Mounted",
|
||||
"There is already a developer disk image "
|
||||
"mounted on the device.");
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::critical(
|
||||
this, "Mount Failed",
|
||||
QString("Failed to mount image on %1. Try with a genuine cable.")
|
||||
.arg(m_deviceComboBox->currentText()));
|
||||
updateUI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,10 +650,11 @@ void DevDiskImagesWidget::closeEvent(QCloseEvent *event)
|
||||
|
||||
void DevDiskImagesWidget::checkMountedImage()
|
||||
{
|
||||
// just in case
|
||||
if (!m_currentDevice || !m_currentDevice->device) {
|
||||
return;
|
||||
}
|
||||
if (m_deviceComboBox->currentIndex() < 0) {
|
||||
QMessageBox::warning(
|
||||
this, "No Device",
|
||||
"Please select a device to check the mounted image.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -602,53 +663,22 @@ void DevDiskImagesWidget::checkMountedImage()
|
||||
m_currentDevice->udid.c_str());
|
||||
|
||||
qDebug() << "checkMountedImage result:" << result.success
|
||||
<< result.message.c_str() << QString::fromStdString(result.output);
|
||||
<< result.message.c_str() << QString::fromStdString(result.sig);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
m_mounted_sig = strdup(result.output.c_str());
|
||||
m_mounted_sig_len = result.output.size();
|
||||
if (result.success && !result.sig.empty()) {
|
||||
m_mounted_sig = result.sig;
|
||||
m_mounted_sig_len = result.sig.size();
|
||||
displayImages(); // Refresh to show mounted status
|
||||
QMessageBox::information(
|
||||
this, "Check Mounted Image",
|
||||
"There is already a developer disk image mounted on the device.");
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox::information(this, "Something went wrong",
|
||||
result.message.c_str());
|
||||
// get_mounted_image(m_currentDevice->udid.c_str());
|
||||
QString errorMsg = QString::fromStdString(result.message);
|
||||
if (errorMsg.isEmpty()) {
|
||||
errorMsg = "Unknown error occurred while checking mounted image";
|
||||
}
|
||||
|
||||
// plist_t sig_array_node =
|
||||
// plist_dict_get_item(result.output, "ImageSignature");
|
||||
|
||||
// if (result.success == false || sig_array_node == NULL) {
|
||||
// QMessageBox::information(
|
||||
// this, "Locked",
|
||||
// "The device is locked. Please unlock it and try again.");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// char *mounted_sig = nullptr;
|
||||
// uint64_t mounted_sig_len = 0;
|
||||
|
||||
// if (sig_array_node && plist_get_node_type(sig_array_node) == PLIST_ARRAY
|
||||
// &&
|
||||
// plist_array_get_size(sig_array_node) > 0) {
|
||||
// plist_t sig_data_node = plist_array_get_item(sig_array_node, 0);
|
||||
// if (sig_data_node && plist_get_node_type(sig_data_node) ==
|
||||
// PLIST_DATA) {
|
||||
// plist_get_data_val(sig_data_node, &mounted_sig,
|
||||
// &mounted_sig_len);
|
||||
// }
|
||||
// }
|
||||
|
||||
// auto compatibleImages =
|
||||
// DevDiskManager::sharedInstance()->getCompatibleImages();
|
||||
// for (const auto &info : compatibleImages) {
|
||||
// if (info.isMounted) {
|
||||
// displayImages(); // Refresh to show mounted status
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// QMessageBox::information(this, "Not Mounted",
|
||||
// "The device has no mounted images.");
|
||||
QMessageBox::warning(this, "Check Mounted Image Failed", errorMsg);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define DEVDISKIMAGESWIDGET_H
|
||||
|
||||
#include "iDescriptor.h"
|
||||
#include "qprocessindicator.h"
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <QStackedWidget>
|
||||
#include <QStringList>
|
||||
#include <QWidget>
|
||||
#include <string>
|
||||
|
||||
class DevDiskImagesWidget : public QWidget
|
||||
{
|
||||
@@ -52,7 +54,7 @@ private:
|
||||
qint64 sigReceived = 0;
|
||||
};
|
||||
|
||||
char *m_mounted_sig = NULL;
|
||||
std::string m_mounted_sig = "";
|
||||
uint64_t m_mounted_sig_len = 0;
|
||||
|
||||
QStackedWidget *m_stackedWidget;
|
||||
@@ -63,6 +65,7 @@ private:
|
||||
QComboBox *m_deviceComboBox;
|
||||
QPushButton *m_mountButton;
|
||||
QPushButton *m_check_mountedButton;
|
||||
QProcessIndicator *m_processIndicator;
|
||||
|
||||
iDescriptorDevice *m_currentDevice;
|
||||
QStringList m_compatibleVersions;
|
||||
|
||||
+44
-40
@@ -12,6 +12,7 @@
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <libimobiledevice/mobile_image_mounter.h>
|
||||
|
||||
DevDiskManager *DevDiskManager::sharedInstance()
|
||||
{
|
||||
@@ -199,6 +200,7 @@ GetImagesSortedResult DevDiskManager::getImagesSorted(
|
||||
int imageMinorVersion = (versionParts.size() >= 2)
|
||||
? versionParts[1].toInt(&mi_ok)
|
||||
: 0;
|
||||
// todo: add "maybe compatible"
|
||||
if (ma_ok && mi_ok) {
|
||||
if (deviceMajorVersion >= 16) {
|
||||
info.isCompatible = (imageMajorVersion == 16);
|
||||
@@ -217,7 +219,14 @@ GetImagesSortedResult DevDiskManager::getImagesSorted(
|
||||
}
|
||||
|
||||
// Check if mounted
|
||||
if (info.isCompatible && info.isDownloaded && mounted_sig) {
|
||||
/*
|
||||
in my testing some ios versions do accept older minor versions
|
||||
as well for example an iPhone 5s with iOS 12.5 accepts 12.4 but
|
||||
newer iPhones are more strict, so lets just check where it's
|
||||
compatible or not
|
||||
*/
|
||||
// if (info.isCompatible && info.isDownloaded && mounted_sig)
|
||||
if (info.isDownloaded && mounted_sig) {
|
||||
QString sigLocalPath =
|
||||
QDir(
|
||||
QDir(
|
||||
@@ -375,21 +384,12 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
|
||||
// TODO: boolean to download
|
||||
bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
|
||||
{
|
||||
GetMountedImageResult res = getMountedImage(device->udid.c_str());
|
||||
if (res.success) {
|
||||
qDebug() << "An image is already mounted on device:"
|
||||
<< device->udid.c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned int device_version = idevice_get_device_version(device->device);
|
||||
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
|
||||
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
|
||||
|
||||
// TODO: use actual device version
|
||||
GetImagesSortedFinalResult images =
|
||||
parseImageList(deviceMajorVersion, deviceMinorVersion,
|
||||
res.output.c_str(), res.output.length());
|
||||
parseImageList(deviceMajorVersion, deviceMinorVersion, "", 0);
|
||||
|
||||
// 1. Try to mount an already downloaded compatible image
|
||||
for (const ImageInfo &info : images.compatibleImages) {
|
||||
@@ -398,7 +398,8 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
|
||||
<< info.version;
|
||||
qDebug() << "Attempting to mount image version" << info.version
|
||||
<< "on device:" << device->udid.c_str();
|
||||
if (mountImage(info.version, device->udid.c_str())) {
|
||||
if (MOBILE_IMAGE_MOUNTER_E_SUCCESS ==
|
||||
mountImage(info.version, device->udid.c_str())) {
|
||||
qDebug() << "Mounted existing image version" << info.version
|
||||
<< "on device:" << device->udid.c_str();
|
||||
return true;
|
||||
@@ -440,6 +441,7 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
// Start the download
|
||||
// todo leak?
|
||||
QPair<QNetworkReply *, QNetworkReply *> replies =
|
||||
downloadImage(versionToDownload);
|
||||
auto *downloadItem = new DownloadItem();
|
||||
@@ -467,7 +469,7 @@ bool DevDiskManager::mountCompatibleImageInternal(iDescriptorDevice *device)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
|
||||
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
{
|
||||
if (m_isImageListReady) {
|
||||
@@ -488,18 +490,17 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
|
||||
// The operation is now asynchronous, the immediate return value
|
||||
// indicates that the process has started.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DevDiskManager::mountImage(const QString &version, const QString &udid)
|
||||
mobile_image_mounter_error_t DevDiskManager::mountImage(const QString &version,
|
||||
const QString &udid)
|
||||
{
|
||||
const QString downloadPath =
|
||||
SettingsManager::sharedInstance()->devdiskimgpath();
|
||||
if (!isImageDownloaded(version, downloadPath)) {
|
||||
return false;
|
||||
return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG;
|
||||
}
|
||||
|
||||
QString versionPath = QDir(downloadPath).filePath(version);
|
||||
@@ -631,38 +632,41 @@ bool DevDiskManager::compareSignatures(const char *signature_file_path,
|
||||
return matches;
|
||||
}
|
||||
|
||||
// mobile_image_mounter_error_t
|
||||
|
||||
GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
|
||||
{
|
||||
QPair<bool, plist_t> result = _get_mounted_image(udid);
|
||||
/*
|
||||
FIXME: _get_mounted_image can return MOBILE_IMAGE_MOUNTER_E_SUCCESS even
|
||||
if the device is locked so we are going to go off of the result
|
||||
dictionary
|
||||
*/
|
||||
plist_t result = _get_mounted_image(udid);
|
||||
const char *lockedErr = "DeviceLocked";
|
||||
|
||||
if (result.first == false) {
|
||||
plist_t sig_err = plist_dict_get_item(result.second, "Error");
|
||||
// TODO: should print ?
|
||||
plist_print(result.second);
|
||||
if (sig_err) {
|
||||
char *error = NULL;
|
||||
plist_get_string_val(sig_err, &error);
|
||||
if (error == "DeviceLocked") {
|
||||
qDebug() << "Error:" << error;
|
||||
free(error);
|
||||
plist_free(result.second);
|
||||
return GetMountedImageResult{false, "", "Device is locked"};
|
||||
}
|
||||
} else {
|
||||
PlistNavigator r = PlistNavigator(result);
|
||||
|
||||
std::string error = r["Error"].getString();
|
||||
|
||||
if (!error.empty()) {
|
||||
plist_free(result);
|
||||
if (error == lockedErr) {
|
||||
return GetMountedImageResult{
|
||||
false, "", "Device is locked, please unlock it and try again."};
|
||||
} else
|
||||
return GetMountedImageResult{false, "", "Unknown error"};
|
||||
}
|
||||
}
|
||||
|
||||
plist_t sig_array_node =
|
||||
plist_dict_get_item(result.second, "ImageSignature");
|
||||
plist_t sig_array_node = r["ImageSignature"].getNode();
|
||||
|
||||
if (sig_array_node == NULL) {
|
||||
plist_free(result.second);
|
||||
return GetMountedImageResult{false, "", "No disk image mounted"};
|
||||
plist_free(result);
|
||||
return GetMountedImageResult{true, "", "No disk image mounted"};
|
||||
}
|
||||
|
||||
char *mounted_sig = nullptr;
|
||||
uint64_t mounted_sig_len = 0;
|
||||
|
||||
// get the signature
|
||||
if (sig_array_node && plist_get_node_type(sig_array_node) == PLIST_ARRAY &&
|
||||
plist_array_get_size(sig_array_node) > 0) {
|
||||
plist_t sig_data_node = plist_array_get_item(sig_array_node, 0);
|
||||
@@ -672,10 +676,10 @@ GetMountedImageResult DevDiskManager::getMountedImage(const char *udid)
|
||||
}
|
||||
std::string mounted_sig_str(mounted_sig ? mounted_sig : "");
|
||||
free(mounted_sig);
|
||||
plist_free(result.second);
|
||||
plist_free(result);
|
||||
if (mounted_sig_str.empty()) {
|
||||
return GetMountedImageResult{
|
||||
false, "", "No disk image mounted (No signature found)"};
|
||||
true, "", "No disk image mounted (No signature found)"};
|
||||
}
|
||||
return GetMountedImageResult{true, mounted_sig_str, "Success"};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <libimobiledevice/mobile_image_mounter.h>
|
||||
|
||||
class DevDiskManager : public QObject
|
||||
{
|
||||
@@ -33,7 +34,8 @@ public:
|
||||
|
||||
// Mount operations
|
||||
|
||||
bool mountImage(const QString &version, const QString &udid);
|
||||
mobile_image_mounter_error_t mountImage(const QString &version,
|
||||
const QString &udid);
|
||||
bool unmountImage();
|
||||
|
||||
// Signature comparison
|
||||
|
||||
@@ -30,7 +30,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
{
|
||||
// Main layout with horizontal orientation
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setContentsMargins(0, 0, 10, 0);
|
||||
mainLayout->setSpacing(1);
|
||||
m_graphicsScene = new QGraphicsScene(this); // no parent
|
||||
QGraphicsPixmapItem *pixmapItem =
|
||||
@@ -60,6 +60,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
// Header
|
||||
QGroupBox *headerWidget = new QGroupBox();
|
||||
QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget);
|
||||
headerLayout->setContentsMargins(5, 5, 5, 5);
|
||||
headerLayout->setSpacing(15);
|
||||
|
||||
QLabel *devProductType =
|
||||
@@ -75,7 +76,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
|
||||
QSizePolicy::Preferred);
|
||||
diskCapacityLabel->setAttribute(Qt::WA_StyledBackground, true);
|
||||
diskCapacityLabel->setStyleSheet("background-color: rgba(0, 255, 30, 0.5);"
|
||||
"padding: 4px;"
|
||||
"padding: 2px;"
|
||||
"border-radius: 13px;");
|
||||
|
||||
m_chargingStatusLabel =
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "appcontext.h"
|
||||
#include "devicemenuwidget.h"
|
||||
#include "devicependingwidget.h"
|
||||
#include "recoverydeviceinfowidget.h"
|
||||
#include <QDebug>
|
||||
|
||||
DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
|
||||
@@ -40,6 +41,40 @@ DeviceManagerWidget::DeviceManagerWidget(QWidget *parent)
|
||||
emit updateNoDevicesConnected();
|
||||
});
|
||||
|
||||
connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceAdded,
|
||||
this, [this](QObject *recoveryDeviceInfoObj) {
|
||||
if (!recoveryDeviceInfoObj)
|
||||
return;
|
||||
try {
|
||||
RecoveryDeviceInfo *device =
|
||||
qobject_cast<RecoveryDeviceInfo *>(
|
||||
recoveryDeviceInfoObj);
|
||||
if (!device) {
|
||||
qDebug() << "Invalid recovery device info object";
|
||||
return;
|
||||
}
|
||||
// IDescriptorInitDeviceResultRecovery initResult=
|
||||
// init_idescriptor_recovery_device(deviceInfo);
|
||||
|
||||
// IDescriptorInitDeviceResult initResult =
|
||||
// init_idescriptor_device(udid.toStdString().c_str());
|
||||
|
||||
qDebug() << "Recovery device initialized: " << device->ecid;
|
||||
|
||||
// std::string added_ecid =
|
||||
// AppContext::sharedInstance()->addRecoveryDevice(device);
|
||||
|
||||
// Create device info widget
|
||||
RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
|
||||
new RecoveryDeviceInfoWidget(device);
|
||||
m_stackedWidget->addWidget(recoveryDeviceInfoWidget);
|
||||
|
||||
} catch (...) {
|
||||
qDebug() << "Error initializing recovery device";
|
||||
}
|
||||
emit updateNoDevicesConnected();
|
||||
});
|
||||
|
||||
// connect(AppContext::sharedInstance(), &AppContext::recoveryDeviceRemoved,
|
||||
// this, [this](const QString &ecid) {
|
||||
// qDebug() << "Removing:" << ecid;
|
||||
@@ -98,10 +133,31 @@ void DeviceManagerWidget::addDevice(iDescriptorDevice *device)
|
||||
m_deviceWidgets[device->udid] = std::pair{
|
||||
deviceWidget, m_sidebar->addToSidebar(tabTitle, device->udid)};
|
||||
|
||||
// If this is the first device, make it current
|
||||
// if (m_currentDeviceIndex == -1) {
|
||||
// setCurrentDevice(deviceIndex);
|
||||
// todo
|
||||
// If this is the first device, make it current
|
||||
// if (m_currentDeviceIndex == -1) {
|
||||
// setCurrentDevice(deviceIndex);
|
||||
// }
|
||||
}
|
||||
|
||||
void DeviceManagerWidget::addRecoveryDevice(RecoveryDeviceInfo *device)
|
||||
{
|
||||
// if (m_deviceWidgets.contains(device->ecid)) {
|
||||
// qWarning() << "Recovery device already exists:"
|
||||
// << QString::fromStdString(device->ecid);
|
||||
// return;
|
||||
// }
|
||||
// qDebug() << "Connect ::recoveryDeviceAdded Adding:"
|
||||
// << QString::fromStdString(device->ecid);
|
||||
|
||||
RecoveryDeviceInfoWidget *deviceWidget =
|
||||
new RecoveryDeviceInfoWidget(device, this);
|
||||
|
||||
// QString tabTitle = QString::fromStdString(device->product);
|
||||
|
||||
m_stackedWidget->addWidget(deviceWidget);
|
||||
// m_deviceWidgets[device->ecid] = std::pair{
|
||||
// deviceWidget, m_sidebar->addToSidebar(tabTitle, device->ecid)};
|
||||
}
|
||||
|
||||
void DeviceManagerWidget::addPendingDevice(const QString &udid, bool locked)
|
||||
@@ -214,11 +270,6 @@ std::string DeviceManagerWidget::getCurrentDevice() const
|
||||
return m_currentDeviceUuid;
|
||||
}
|
||||
|
||||
QWidget *DeviceManagerWidget::getDeviceWidget(int deviceIndex) const
|
||||
{
|
||||
// return m_deviceWidgets.value(deviceIndex, nullptr);
|
||||
}
|
||||
|
||||
void DeviceManagerWidget::setDeviceNavigation(int deviceIndex,
|
||||
const QString §ion)
|
||||
{
|
||||
|
||||
@@ -17,18 +17,9 @@ class DeviceManagerWidget : public QWidget
|
||||
public:
|
||||
explicit DeviceManagerWidget(QWidget *parent = nullptr);
|
||||
|
||||
void addDevice(iDescriptorDevice *device);
|
||||
// TODO:udid or uuid ?
|
||||
void addPendingDevice(const QString &udid, bool locked);
|
||||
void addPairedDevice(iDescriptorDevice *device);
|
||||
|
||||
void removeDevice(const std::string &uuid);
|
||||
void setCurrentDevice(const std::string &uuid);
|
||||
std::string getCurrentDevice() const;
|
||||
|
||||
// Get the device widget at a specific index
|
||||
QWidget *getDeviceWidget(int deviceIndex) const;
|
||||
|
||||
// Navigation methods
|
||||
void setDeviceNavigation(int deviceIndex, const QString §ion);
|
||||
|
||||
@@ -45,6 +36,12 @@ private slots:
|
||||
private:
|
||||
void setupUI();
|
||||
|
||||
void addDevice(iDescriptorDevice *device);
|
||||
void removeDevice(const std::string &uuid);
|
||||
void addRecoveryDevice(RecoveryDeviceInfo *device);
|
||||
// TODO:udid or uuid ?
|
||||
void addPendingDevice(const QString &udid, bool locked);
|
||||
void addPairedDevice(iDescriptorDevice *device);
|
||||
QHBoxLayout *m_mainLayout;
|
||||
DeviceSidebarWidget *m_sidebar;
|
||||
QStackedWidget *m_stackedWidget;
|
||||
|
||||
@@ -2,24 +2,17 @@
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "loadingspinnerwidget.h"
|
||||
#include <QDebug>
|
||||
#include <QEasingCurve>
|
||||
|
||||
// DeviceSidebarItem Implementation
|
||||
DeviceSidebarItem::DeviceSidebarItem(const QString &deviceName,
|
||||
const std::string &uuid, QWidget *parent)
|
||||
: QFrame(parent), m_deviceName(deviceName), m_uuid(uuid), m_selected(false),
|
||||
m_collapsed(true)
|
||||
m_collapsed(false)
|
||||
{
|
||||
setupUI();
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
setLineWidth(1);
|
||||
updateToggleButton();
|
||||
|
||||
// Initialize animation
|
||||
m_collapseAnimation =
|
||||
new QPropertyAnimation(m_optionsWidget, "maximumHeight", this);
|
||||
m_collapseAnimation->setDuration(200);
|
||||
m_collapseAnimation->setEasingCurve(QEasingCurve::InOutQuad);
|
||||
}
|
||||
|
||||
void DeviceSidebarItem::setupUI()
|
||||
@@ -122,9 +115,10 @@ void DeviceSidebarItem::setupUI()
|
||||
|
||||
m_mainLayout->addWidget(m_optionsWidget);
|
||||
|
||||
// Initially hide options
|
||||
m_optionsWidget->setMaximumHeight(0);
|
||||
m_optionsWidget->hide();
|
||||
// Initialize UI state to match m_collapsed value
|
||||
// This ensures consistency regardless of initial m_collapsed value
|
||||
updateToggleButton();
|
||||
toggleCollapse();
|
||||
|
||||
setStyleSheet("DeviceSidebarItem { border: "
|
||||
"1px solid #e0e0e0; border-radius: 5px; }");
|
||||
@@ -153,7 +147,7 @@ void DeviceSidebarItem::setCollapsed(bool collapsed)
|
||||
|
||||
m_collapsed = collapsed;
|
||||
updateToggleButton();
|
||||
animateCollapse();
|
||||
toggleCollapse();
|
||||
}
|
||||
|
||||
void DeviceSidebarItem::updateToggleButton()
|
||||
@@ -165,40 +159,15 @@ void DeviceSidebarItem::updateToggleButton()
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceSidebarItem::animateCollapse()
|
||||
void DeviceSidebarItem::toggleCollapse()
|
||||
{
|
||||
m_collapseAnimation->stop();
|
||||
|
||||
if (m_collapsed) {
|
||||
// Collapsing
|
||||
m_collapseAnimation->setStartValue(m_optionsWidget->height());
|
||||
m_collapseAnimation->setEndValue(0);
|
||||
|
||||
connect(m_collapseAnimation, &QPropertyAnimation::finished, this,
|
||||
[this]() {
|
||||
m_optionsWidget->hide();
|
||||
disconnect(m_collapseAnimation,
|
||||
&QPropertyAnimation::finished, this, nullptr);
|
||||
});
|
||||
m_optionsWidget->hide();
|
||||
m_optionsWidget->setMaximumHeight(0);
|
||||
} else {
|
||||
// Expanding
|
||||
m_optionsWidget->show();
|
||||
m_optionsWidget->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
int targetHeight = m_optionsWidget->sizeHint().height();
|
||||
m_optionsWidget->setMaximumHeight(0);
|
||||
|
||||
m_collapseAnimation->setStartValue(0);
|
||||
m_collapseAnimation->setEndValue(targetHeight);
|
||||
|
||||
connect(m_collapseAnimation, &QPropertyAnimation::finished, this,
|
||||
[this]() {
|
||||
m_optionsWidget->setMaximumHeight(QWIDGETSIZE_MAX);
|
||||
disconnect(m_collapseAnimation,
|
||||
&QPropertyAnimation::finished, this, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
m_collapseAnimation->start();
|
||||
}
|
||||
|
||||
void DeviceSidebarItem::onToggleCollapse() { setCollapsed(!m_collapsed); }
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QParallelAnimationGroup>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
@@ -39,7 +37,7 @@ private slots:
|
||||
private:
|
||||
void setupUI();
|
||||
void updateToggleButton();
|
||||
void animateCollapse();
|
||||
void toggleCollapse();
|
||||
|
||||
std::string m_uuid;
|
||||
QString m_deviceName;
|
||||
@@ -57,8 +55,6 @@ private:
|
||||
QPushButton *m_galleryButton;
|
||||
QPushButton *m_filesButton;
|
||||
QButtonGroup *m_navigationGroup;
|
||||
|
||||
QPropertyAnimation *m_collapseAnimation;
|
||||
};
|
||||
|
||||
#ifndef DEVICEPENDINGSIDEBARITEM_H
|
||||
|
||||
+75
-46
@@ -36,11 +36,36 @@ void DiskUsageWidget::setupUI()
|
||||
m_titleLabel->setAlignment(Qt::AlignCenter);
|
||||
m_mainLayout->addWidget(m_titleLabel);
|
||||
|
||||
// Status label (for loading/error states)
|
||||
m_statusLabel = new QLabel(this);
|
||||
// Stacked widget for different states
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
m_mainLayout->addWidget(m_stackedWidget);
|
||||
|
||||
// Loading/Error page
|
||||
m_loadingErrorPage = new QWidget();
|
||||
m_loadingErrorLayout = new QVBoxLayout(m_loadingErrorPage);
|
||||
m_loadingErrorLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_loadingErrorLayout->setSpacing(5);
|
||||
|
||||
m_processIndicator = new QProcessIndicator(m_loadingErrorPage);
|
||||
m_processIndicator->setFixedSize(24, 24);
|
||||
m_processIndicator->start();
|
||||
|
||||
m_statusLabel = new QLabel(m_loadingErrorPage);
|
||||
m_statusLabel->setAlignment(Qt::AlignCenter);
|
||||
m_statusLabel->setText("Loading disk usage...");
|
||||
m_mainLayout->addWidget(m_statusLabel);
|
||||
|
||||
m_loadingErrorLayout->addStretch();
|
||||
m_loadingErrorLayout->addWidget(m_processIndicator, 0, Qt::AlignCenter);
|
||||
m_loadingErrorLayout->addWidget(m_statusLabel);
|
||||
m_loadingErrorLayout->addStretch();
|
||||
|
||||
m_stackedWidget->addWidget(m_loadingErrorPage);
|
||||
|
||||
// Data page
|
||||
m_dataPage = new QWidget();
|
||||
m_dataLayout = new QVBoxLayout(m_dataPage);
|
||||
m_dataLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_dataLayout->setSpacing(0);
|
||||
|
||||
// Disk usage bar container
|
||||
m_diskBarContainer = new QWidget(this);
|
||||
@@ -89,8 +114,7 @@ void DiskUsageWidget::setupUI()
|
||||
m_diskBarLayout->addWidget(m_othersBar);
|
||||
m_diskBarLayout->addWidget(m_freeBar);
|
||||
|
||||
m_diskBarContainer->hide(); // Initially hidden
|
||||
m_mainLayout->addWidget(m_diskBarContainer);
|
||||
m_dataLayout->addWidget(m_diskBarContainer);
|
||||
|
||||
// Legend layout
|
||||
m_legendLayout = new QHBoxLayout();
|
||||
@@ -98,11 +122,11 @@ void DiskUsageWidget::setupUI()
|
||||
m_legendLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Legend labels
|
||||
m_systemLabel = new QLabel("System", this);
|
||||
m_appsLabel = new QLabel("Apps", this);
|
||||
m_mediaLabel = new QLabel("Media", this);
|
||||
m_othersLabel = new QLabel("Others", this);
|
||||
m_freeLabel = new QLabel("Free", this);
|
||||
m_systemLabel = new QLabel("System", m_dataPage);
|
||||
m_appsLabel = new QLabel("Apps", m_dataPage);
|
||||
m_mediaLabel = new QLabel("Media", m_dataPage);
|
||||
m_othersLabel = new QLabel("Others", m_dataPage);
|
||||
m_freeLabel = new QLabel("Free", m_dataPage);
|
||||
|
||||
// Style legend labels with colored backgrounds
|
||||
QString labelStyle =
|
||||
@@ -120,14 +144,12 @@ void DiskUsageWidget::setupUI()
|
||||
m_legendLayout->addWidget(m_freeLabel);
|
||||
m_legendLayout->addStretch();
|
||||
|
||||
// Hide legend initially
|
||||
m_systemLabel->hide();
|
||||
m_appsLabel->hide();
|
||||
m_mediaLabel->hide();
|
||||
m_othersLabel->hide();
|
||||
m_freeLabel->hide();
|
||||
m_dataLayout->addLayout(m_legendLayout);
|
||||
|
||||
m_mainLayout->addLayout(m_legendLayout);
|
||||
m_stackedWidget->addWidget(m_dataPage);
|
||||
|
||||
// Initially show loading page
|
||||
m_stackedWidget->setCurrentWidget(m_loadingErrorPage);
|
||||
}
|
||||
|
||||
QSize DiskUsageWidget::sizeHint() const { return QSize(400, 80); }
|
||||
@@ -135,49 +157,28 @@ QSize DiskUsageWidget::sizeHint() const { return QSize(400, 80); }
|
||||
void DiskUsageWidget::updateUI()
|
||||
{
|
||||
if (m_state == Loading) {
|
||||
m_processIndicator->start();
|
||||
m_statusLabel->setText("Loading disk usage...");
|
||||
m_statusLabel->show();
|
||||
m_diskBarContainer->hide();
|
||||
m_systemLabel->hide();
|
||||
m_appsLabel->hide();
|
||||
m_mediaLabel->hide();
|
||||
m_othersLabel->hide();
|
||||
m_freeLabel->hide();
|
||||
m_stackedWidget->setCurrentWidget(m_loadingErrorPage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state == Error) {
|
||||
m_processIndicator->stop();
|
||||
m_statusLabel->setText("Error: " + m_errorMessage);
|
||||
m_statusLabel->show();
|
||||
m_diskBarContainer->hide();
|
||||
m_systemLabel->hide();
|
||||
m_appsLabel->hide();
|
||||
m_mediaLabel->hide();
|
||||
m_othersLabel->hide();
|
||||
m_freeLabel->hide();
|
||||
m_stackedWidget->setCurrentWidget(m_loadingErrorPage);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_totalCapacity == 0) {
|
||||
m_processIndicator->stop();
|
||||
m_statusLabel->setText("No disk information available.");
|
||||
m_statusLabel->show();
|
||||
m_diskBarContainer->hide();
|
||||
m_systemLabel->hide();
|
||||
m_appsLabel->hide();
|
||||
m_mediaLabel->hide();
|
||||
m_othersLabel->hide();
|
||||
m_freeLabel->hide();
|
||||
m_stackedWidget->setCurrentWidget(m_loadingErrorPage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide status label and show disk bar and legend
|
||||
m_statusLabel->hide();
|
||||
m_diskBarContainer->show();
|
||||
m_systemLabel->show();
|
||||
m_appsLabel->show();
|
||||
m_mediaLabel->show();
|
||||
m_othersLabel->show();
|
||||
m_freeLabel->show();
|
||||
// Show data page
|
||||
m_stackedWidget->setCurrentWidget(m_dataPage);
|
||||
|
||||
// Calculate proportions for each segment
|
||||
int totalWidth = m_diskBarContainer->width();
|
||||
@@ -271,7 +272,35 @@ void DiskUsageWidget::updateUI()
|
||||
(double)m_othersUsage / m_totalCapacity);
|
||||
m_freeBar->setUsageInfo("Free", formatSize(m_freeSpace), "#BDC3C7",
|
||||
(double)m_freeSpace / m_totalCapacity);
|
||||
#else
|
||||
m_systemBar->setToolTip(
|
||||
QString("System: %1 (%2%)")
|
||||
.arg(formatSize(m_systemUsage))
|
||||
.arg(QString::number((double)m_systemUsage / m_totalCapacity * 100,
|
||||
'f', 1)));
|
||||
m_appsBar->setToolTip(
|
||||
QString("Apps: %1 (%2%)")
|
||||
.arg(formatSize(m_appsUsage))
|
||||
.arg(QString::number((double)m_appsUsage / m_totalCapacity * 100,
|
||||
'f', 1)));
|
||||
m_mediaBar->setToolTip(
|
||||
QString("Media: %1 (%2%)")
|
||||
.arg(formatSize(m_mediaUsage))
|
||||
.arg(QString::number((double)m_mediaUsage / m_totalCapacity * 100,
|
||||
'f', 1)));
|
||||
m_othersBar->setToolTip(
|
||||
QString("Others: %1 (%2%)")
|
||||
.arg(formatSize(m_othersUsage))
|
||||
.arg(QString::number((double)m_othersUsage / m_totalCapacity * 100,
|
||||
'f', 1)));
|
||||
m_freeBar->setToolTip(
|
||||
QString("Free: %1 (%2%)")
|
||||
.arg(formatSize(m_freeSpace))
|
||||
.arg(QString::number((double)m_freeSpace / m_totalCapacity * 100,
|
||||
'f', 1)));
|
||||
|
||||
#endif
|
||||
|
||||
// Hide segments with zero usage
|
||||
// m_systemBar->setVisible(m_systemUsage > 0);
|
||||
// m_appsBar->setVisible(m_appsUsage > 0);
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
#define DISKUSAGEWIDGET_H
|
||||
#include "diskusagebar.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "qprocessindicator.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QProgressBar>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <cstdint>
|
||||
@@ -35,7 +37,17 @@ private:
|
||||
// UI widgets
|
||||
QVBoxLayout *m_mainLayout;
|
||||
QLabel *m_titleLabel;
|
||||
QStackedWidget *m_stackedWidget;
|
||||
|
||||
// Loading/Error page
|
||||
QWidget *m_loadingErrorPage;
|
||||
QVBoxLayout *m_loadingErrorLayout;
|
||||
QProcessIndicator *m_processIndicator;
|
||||
QLabel *m_statusLabel;
|
||||
|
||||
// Data page
|
||||
QWidget *m_dataPage;
|
||||
QVBoxLayout *m_dataLayout;
|
||||
QWidget *m_diskBarContainer;
|
||||
QHBoxLayout *m_diskBarLayout;
|
||||
#ifdef Q_OS_MAC
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include "fileexplorerwidget.h"
|
||||
#include "afcexplorerwidget.h"
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "mediapreviewdialog.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileDialog>
|
||||
@@ -12,9 +14,12 @@
|
||||
#include <QInputDialog>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSplitter>
|
||||
#include <QSplitterHandle>
|
||||
#include <QTreeWidget>
|
||||
#include <QVariant>
|
||||
#include <libimobiledevice/afc.h>
|
||||
@@ -24,11 +29,12 @@ FileExplorerWidget::FileExplorerWidget(iDescriptorDevice *device,
|
||||
QWidget *parent)
|
||||
: QWidget(parent), device(device), usingAFC2(false)
|
||||
{
|
||||
m_mainSplitter = new ModernSplitter(Qt::Horizontal, this);
|
||||
|
||||
m_mainSplitter = new QSplitter(Qt::Horizontal, this);
|
||||
// Main layout
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->addWidget(m_mainSplitter);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setupSidebar();
|
||||
|
||||
@@ -47,7 +53,7 @@ void FileExplorerWidget::setupSidebar()
|
||||
m_sidebarTree = new QTreeWidget();
|
||||
m_sidebarTree->setHeaderLabel("Files");
|
||||
m_sidebarTree->setMinimumWidth(50);
|
||||
m_sidebarTree->setMaximumWidth(200);
|
||||
m_sidebarTree->setMaximumWidth(250);
|
||||
|
||||
// AFC Default section
|
||||
m_afcDefaultItem = new QTreeWidgetItem(m_sidebarTree);
|
||||
|
||||
+58
-1
@@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
#include <QApplication>
|
||||
#include <QGraphicsView>
|
||||
#include <QMainWindow>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QSplitter>
|
||||
#include <QSplitterHandle>
|
||||
#include <QStyleOption>
|
||||
#include <QWidget>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
@@ -73,4 +78,56 @@ enum class iDescriptorTool {
|
||||
UnmountDevImage,
|
||||
Unknown,
|
||||
iFuse
|
||||
};
|
||||
};
|
||||
|
||||
class ModernSplitterHandle : public QSplitterHandle
|
||||
{
|
||||
public:
|
||||
ModernSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
|
||||
: QSplitterHandle(orientation, parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
QColor buttonColor = QApplication::palette().color(QPalette::Text);
|
||||
buttonColor.setAlpha(60);
|
||||
|
||||
int margin = 10;
|
||||
int availableWidth = width() - (2 * margin);
|
||||
int centerX = margin + availableWidth / 2;
|
||||
int centerY = height() / 2;
|
||||
|
||||
int buttonWidth = 6;
|
||||
int buttonHeight = 50;
|
||||
|
||||
QRect buttonRect(centerX - buttonWidth / 2, centerY - buttonHeight / 2,
|
||||
buttonWidth, buttonHeight);
|
||||
|
||||
painter.setBrush(QBrush(buttonColor));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.drawRoundedRect(buttonRect, buttonWidth / 2, buttonWidth / 2);
|
||||
}
|
||||
};
|
||||
|
||||
class ModernSplitter : public QSplitter
|
||||
{
|
||||
public:
|
||||
ModernSplitter(Qt::Orientation orientation, QWidget *parent = nullptr)
|
||||
: QSplitter(orientation, parent)
|
||||
{
|
||||
setHandleWidth(10);
|
||||
}
|
||||
|
||||
protected:
|
||||
QSplitterHandle *createHandle() override
|
||||
{
|
||||
return new ModernSplitterHandle(orientation(), this);
|
||||
}
|
||||
};
|
||||
|
||||
+6
-3
@@ -4,6 +4,7 @@
|
||||
#include <libimobiledevice/afc.h>
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <libimobiledevice/mobile_image_mounter.h>
|
||||
#include <libimobiledevice/screenshotr.h>
|
||||
#include <libirecovery.h>
|
||||
#include <pugixml.hpp>
|
||||
@@ -319,6 +320,7 @@ public:
|
||||
free(value);
|
||||
return result;
|
||||
}
|
||||
plist_t getNode() const { return current_node; }
|
||||
};
|
||||
|
||||
afc_error_t safe_afc_read_directory(afc_client_t afcClient, idevice_t device,
|
||||
@@ -359,15 +361,16 @@ bool shutdown(idevice_t device);
|
||||
|
||||
TakeScreenshotResult take_screenshot(screenshotr_client_t shotr);
|
||||
|
||||
bool mount_dev_image(const char *udid, const char *image_dir_path);
|
||||
mobile_image_mounter_error_t mount_dev_image(const char *udid,
|
||||
const char *image_dir_path);
|
||||
|
||||
struct GetMountedImageResult {
|
||||
bool success;
|
||||
std::string output;
|
||||
std::string sig;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
QPair<bool, plist_t> _get_mounted_image(const char *udid);
|
||||
plist_t _get_mounted_image(const char *udid);
|
||||
|
||||
bool restart(std::string udid);
|
||||
|
||||
|
||||
+217
-235
@@ -1,5 +1,6 @@
|
||||
#include "installedappswidget.h"
|
||||
#include "afcexplorerwidget.h"
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "qprocessindicator.h"
|
||||
#include <QAction>
|
||||
@@ -19,14 +20,13 @@
|
||||
#include <libimobiledevice/lockdown.h>
|
||||
#include <plist/plist.h>
|
||||
|
||||
// AppTabWidget Implementation
|
||||
AppTabWidget::AppTabWidget(const QString &appName, const QString &bundleId,
|
||||
const QString &version, QWidget *parent)
|
||||
: QGroupBox(parent), m_appName(appName), m_bundleId(bundleId),
|
||||
m_version(version), m_selected(false), m_hovered(false)
|
||||
{
|
||||
setFixedHeight(60);
|
||||
setMinimumWidth(250);
|
||||
setMinimumWidth(100);
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
|
||||
setupUI();
|
||||
@@ -67,22 +67,19 @@ void AppTabWidget::setSelected(bool selected)
|
||||
|
||||
void AppTabWidget::setupUI()
|
||||
{
|
||||
// Create main layout
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(10, 8, 10, 8);
|
||||
mainLayout->setSpacing(10);
|
||||
|
||||
// m_defaultBg = this->palette().color(QPalette::Window);
|
||||
// Icon label
|
||||
m_iconLabel = new QLabel();
|
||||
m_iconLabel->setFixedSize(32, 32);
|
||||
m_iconLabel->setScaledContents(true);
|
||||
|
||||
// Load placeholder icon
|
||||
QPixmap placeholderIcon = QApplication::style()
|
||||
->standardIcon(QStyle::SP_ComputerIcon)
|
||||
.pixmap(32, 32);
|
||||
m_iconLabel->setPixmap(placeholderIcon);
|
||||
|
||||
mainLayout->addWidget(m_iconLabel);
|
||||
|
||||
// Text container
|
||||
@@ -92,24 +89,21 @@ void AppTabWidget::setupUI()
|
||||
|
||||
// App name label
|
||||
m_nameLabel = new QLabel();
|
||||
m_nameLabel->setFont(QFont(m_nameLabel->font().family(),
|
||||
m_nameLabel->font().pointSize(), QFont::Medium));
|
||||
QFont nameFont = m_nameLabel->font();
|
||||
nameFont.setWeight(QFont::Medium);
|
||||
m_nameLabel->setFont(nameFont);
|
||||
|
||||
QString displayText = m_appName;
|
||||
if (displayText.length() > 20) {
|
||||
displayText = displayText.left(17) + "...";
|
||||
}
|
||||
m_nameLabel->setText(displayText);
|
||||
|
||||
textLayout->addWidget(m_nameLabel);
|
||||
|
||||
// Version label
|
||||
if (!m_version.isEmpty()) {
|
||||
m_versionLabel = new QLabel(m_version);
|
||||
QFont versionFont = m_versionLabel->font();
|
||||
versionFont.setPointSize(versionFont.pointSize() - 1);
|
||||
m_versionLabel->setFont(versionFont);
|
||||
// m_versionLabel->setStyleSheet("color: #666666;");
|
||||
m_versionLabel->setStyleSheet("font-size: 11px;");
|
||||
textLayout->addWidget(m_versionLabel);
|
||||
} else {
|
||||
m_versionLabel = nullptr;
|
||||
@@ -118,8 +112,7 @@ void AppTabWidget::setupUI()
|
||||
mainLayout->addLayout(textLayout);
|
||||
mainLayout->addStretch();
|
||||
|
||||
// Set initial styles
|
||||
// updateStyles();
|
||||
updateStyles();
|
||||
}
|
||||
|
||||
void AppTabWidget::mousePressEvent(QMouseEvent *event)
|
||||
@@ -132,48 +125,31 @@ void AppTabWidget::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
m_hovered = true;
|
||||
// updateStyles();
|
||||
updateStyles();
|
||||
}
|
||||
|
||||
void AppTabWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
m_hovered = false;
|
||||
// updateStyles();
|
||||
updateStyles();
|
||||
}
|
||||
|
||||
void AppTabWidget::updateStyles()
|
||||
{
|
||||
QString backgroundColor;
|
||||
QString nameColor = "#000000";
|
||||
QString versionColor = "#666666";
|
||||
QString borderStyle;
|
||||
|
||||
if (m_selected) {
|
||||
backgroundColor = "#007AFF";
|
||||
nameColor = "#ffffff";
|
||||
versionColor = "#ffffff";
|
||||
borderStyle = "border: 2px solid #007AFF; border-radius: 6px;";
|
||||
} else if (m_hovered) {
|
||||
backgroundColor = "#f0f0f0";
|
||||
borderStyle = "border: 1px solid #e0e0e0; border-radius: 6px;";
|
||||
} else {
|
||||
backgroundColor = "transparent";
|
||||
borderStyle = "border: 1px solid transparent; border-radius: 6px;";
|
||||
}
|
||||
|
||||
// Update widget background
|
||||
// setStyleSheet(QString("AppTabWidget { background-color: %1; %2 }")
|
||||
// .arg(backgroundColor, borderStyle));
|
||||
|
||||
// Update name label color
|
||||
// m_nameLabel->setStyleSheet(QString("color: %1;").arg(nameColor));
|
||||
|
||||
// Update version label color if it exists
|
||||
// if (m_versionLabel) {
|
||||
// m_versionLabel->setStyleSheet(QString("color:
|
||||
// %1;").arg(versionColor));
|
||||
// }
|
||||
// TODO: for some reason setting a style overrides every other style instead
|
||||
// of adding or overriding
|
||||
// if (m_selected) {
|
||||
// setStyleSheet("border: 2px solid #007AFF;");
|
||||
// }
|
||||
// borderStyle = "border: 2px solid #007AFF;";
|
||||
// } else if (m_hovered) {
|
||||
// borderStyle = "border: 1px solid" + highlightColor.name() + ";";
|
||||
// } else {
|
||||
// borderStyle = "";
|
||||
// }
|
||||
// setStyleSheet(borderStyle);
|
||||
}
|
||||
|
||||
InstalledAppsWidget::InstalledAppsWidget(iDescriptorDevice *device,
|
||||
@@ -198,158 +174,107 @@ void InstalledAppsWidget::setupUI()
|
||||
m_mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_mainLayout->setSpacing(0);
|
||||
|
||||
// Create main splitter
|
||||
m_splitter = new QSplitter(Qt::Horizontal, this);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
m_mainLayout->addWidget(m_splitter);
|
||||
// Create stacked widget for different states
|
||||
m_stackedWidget = new QStackedWidget(this);
|
||||
m_mainLayout->addWidget(m_stackedWidget);
|
||||
|
||||
// Left side - Custom tab area with scroll
|
||||
QWidget *tabWidget = new QWidget();
|
||||
tabWidget->setMinimumWidth(300);
|
||||
tabWidget->setMaximumWidth(500);
|
||||
// Create loading widget
|
||||
createLoadingWidget();
|
||||
|
||||
QVBoxLayout *tabWidgetLayout = new QVBoxLayout(tabWidget);
|
||||
tabWidgetLayout->setContentsMargins(0, 0, 0, 0);
|
||||
tabWidgetLayout->setSpacing(0);
|
||||
// Create error widget
|
||||
createErrorWidget();
|
||||
|
||||
// Search box at the top
|
||||
QWidget *searchContainer = new QWidget();
|
||||
searchContainer->setFixedHeight(50);
|
||||
QHBoxLayout *searchLayout = new QHBoxLayout(searchContainer);
|
||||
searchLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
m_searchEdit = new QLineEdit();
|
||||
m_searchEdit->setPlaceholderText("Search apps...");
|
||||
m_searchEdit->setStyleSheet("QLineEdit { "
|
||||
" border: 2px solid #e0e0e0; "
|
||||
" border-radius: 6px; "
|
||||
" padding: 4px 8px; "
|
||||
" font-size: 14px; "
|
||||
"} "
|
||||
"QLineEdit:focus { "
|
||||
" border: 2px solid #007AFF; "
|
||||
" outline: none; "
|
||||
"} ");
|
||||
|
||||
// Add search icon
|
||||
// QAction *searchAction = m_searchEdit->addAction(
|
||||
// this->style()->standardIcon(QStyle::SP_FileDialogContentsView),
|
||||
// QLineEdit::LeadingPosition);
|
||||
m_searchEdit->setToolTip("Search");
|
||||
|
||||
searchLayout->addWidget(m_searchEdit);
|
||||
|
||||
// Add checkbox for file sharing filter
|
||||
m_fileSharingCheckBox = new QCheckBox("Show Only File Sharing Enabled");
|
||||
m_fileSharingCheckBox->setChecked(true); // Default enabled
|
||||
m_fileSharingCheckBox->setStyleSheet("QCheckBox { "
|
||||
" font-size: 10px; "
|
||||
" margin-left: 5px; "
|
||||
"}");
|
||||
searchLayout->addWidget(m_fileSharingCheckBox);
|
||||
|
||||
tabWidgetLayout->addWidget(searchContainer);
|
||||
|
||||
// Add a separator line
|
||||
// QFrame *separator = new QFrame();
|
||||
// separator->setFrameShape(QFrame::HLine);
|
||||
// separator->setFrameShadow(QFrame::Sunken);
|
||||
// separator->setStyleSheet("QFrame { color: #e0e0e0; }");
|
||||
// tabFrameLayout->addWidget(separator);
|
||||
|
||||
m_tabScrollArea = new QScrollArea();
|
||||
m_tabScrollArea->setWidgetResizable(true);
|
||||
m_tabScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_tabScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_tabScrollArea->setStyleSheet("QScrollArea { border: none; }");
|
||||
|
||||
m_tabContainer = new QWidget();
|
||||
// m_tabContainer->setStyleSheet("");
|
||||
m_tabLayout = new QVBoxLayout(m_tabContainer);
|
||||
m_tabLayout->setContentsMargins(0, 0, 10, 0);
|
||||
m_tabLayout->setSpacing(10);
|
||||
m_tabLayout->addStretch(); // Push tabs to top
|
||||
|
||||
m_tabScrollArea->setWidget(m_tabContainer);
|
||||
tabWidgetLayout->addWidget(m_tabScrollArea);
|
||||
|
||||
// Add tab widget to splitter
|
||||
m_splitter->addWidget(tabWidget);
|
||||
|
||||
// Right side - Content area
|
||||
m_contentWidget = new QWidget();
|
||||
// m_contentWidget->setStyleSheet("border: 1px solid #ccc;");
|
||||
|
||||
QVBoxLayout *contentLayout = new QVBoxLayout(m_contentWidget);
|
||||
contentLayout->setContentsMargins(0, 0, 0, 0);
|
||||
contentLayout->setSpacing(0);
|
||||
|
||||
m_contentLabel = new QLabel("Select an app to view details");
|
||||
m_contentLabel->setAlignment(Qt::AlignCenter);
|
||||
// m_contentLabel->setStyleSheet("font-size: 16px; color: #666;");
|
||||
contentLayout->addWidget(m_contentLabel);
|
||||
|
||||
// Container explorer area
|
||||
// QLabel *containerTitle = new QLabel("App Container:");
|
||||
// containerTitle->setStyleSheet(
|
||||
// "font-size: 14px; font-weight: bold; color: #333;");
|
||||
// containerTitle->setVisible(false);
|
||||
// contentLayout->addWidget(containerTitle);
|
||||
|
||||
m_containerScrollArea = new QScrollArea();
|
||||
m_containerScrollArea->setWidgetResizable(true);
|
||||
m_containerScrollArea->setMinimumHeight(200);
|
||||
// m_containerScrollArea->setStyleSheet(
|
||||
// "QScrollArea { border: 1px solid #ddd; border-radius: 4px; }");
|
||||
m_containerScrollArea->setVisible(false);
|
||||
|
||||
m_containerWidget = new QWidget();
|
||||
m_containerLayout = new QVBoxLayout(m_containerWidget);
|
||||
m_containerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_containerLayout->setSpacing(0);
|
||||
|
||||
m_containerScrollArea->setWidget(m_containerWidget);
|
||||
contentLayout->addWidget(m_containerScrollArea);
|
||||
|
||||
// Add content widget to splitter
|
||||
m_splitter->addWidget(m_contentWidget);
|
||||
|
||||
// Set initial splitter sizes (30% for tabs, 70% for content)
|
||||
m_splitter->setSizes({300, 700});
|
||||
|
||||
// // Progress bar for loading
|
||||
// m_progressBar = new QProgressBar();
|
||||
// m_progressBar->setRange(0, 0); // Indeterminate progress
|
||||
// m_progressBar->setVisible(false);
|
||||
// contentLayout->addWidget(m_progressBar);
|
||||
|
||||
// Connect search functionality
|
||||
connect(m_searchEdit, &QLineEdit::textChanged, this,
|
||||
&InstalledAppsWidget::filterApps);
|
||||
|
||||
// Connect file sharing filter
|
||||
connect(m_fileSharingCheckBox, &QCheckBox::toggled, this,
|
||||
&InstalledAppsWidget::onFileSharingFilterChanged);
|
||||
// Create content widget
|
||||
createContentWidget();
|
||||
|
||||
// Start in loading state
|
||||
showLoadingState();
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::showLoadingState()
|
||||
{
|
||||
m_contentLabel->setText("Loading installed apps...");
|
||||
// m_progressBar->setVisible(true);
|
||||
|
||||
// Clear existing tabs
|
||||
qDeleteAll(m_appTabs);
|
||||
m_appTabs.clear();
|
||||
m_selectedTab = nullptr;
|
||||
m_stackedWidget->setCurrentWidget(m_loadingWidget);
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::showErrorState(const QString &error)
|
||||
{
|
||||
m_contentLabel->setText(QString("Error loading apps: %1").arg(error));
|
||||
// m_progressBar->setVisible(false);
|
||||
m_errorLabel->setText(QString("Error loading apps: %1").arg(error));
|
||||
m_stackedWidget->setCurrentWidget(m_errorWidget);
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::createLoadingWidget()
|
||||
{
|
||||
m_loadingWidget = new QWidget();
|
||||
QVBoxLayout *loadingLayout = new QVBoxLayout(m_loadingWidget);
|
||||
loadingLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QProcessIndicator *spinner = new QProcessIndicator();
|
||||
spinner->setType(QProcessIndicator::line_rotate);
|
||||
spinner->setFixedSize(48, 48);
|
||||
spinner->start();
|
||||
loadingLayout->addWidget(spinner, 0, Qt::AlignCenter);
|
||||
|
||||
QLabel *loadingLabel = new QLabel("Loading installed apps...");
|
||||
loadingLabel->setAlignment(Qt::AlignCenter);
|
||||
loadingLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #666; margin-top: 10px;");
|
||||
loadingLayout->addWidget(loadingLabel);
|
||||
|
||||
m_stackedWidget->addWidget(m_loadingWidget);
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::createErrorWidget()
|
||||
{
|
||||
m_errorWidget = new QWidget();
|
||||
QVBoxLayout *errorLayout = new QVBoxLayout(m_errorWidget);
|
||||
errorLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
m_errorLabel = new QLabel();
|
||||
m_errorLabel->setAlignment(Qt::AlignCenter);
|
||||
m_errorLabel->setStyleSheet(
|
||||
"font-size: 14px; color: #d32f2f; margin: 20px;");
|
||||
m_errorLabel->setWordWrap(true);
|
||||
errorLayout->addWidget(m_errorLabel);
|
||||
|
||||
QPushButton *retryButton = new QPushButton("Retry");
|
||||
retryButton->setFixedSize(100, 30);
|
||||
connect(retryButton, &QPushButton::clicked, this,
|
||||
&InstalledAppsWidget::fetchInstalledApps);
|
||||
errorLayout->addWidget(retryButton, 0, Qt::AlignCenter);
|
||||
|
||||
m_stackedWidget->addWidget(m_errorWidget);
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::createContentWidget()
|
||||
{
|
||||
m_contentWidget = new QWidget();
|
||||
QHBoxLayout *contentLayout = new QHBoxLayout(m_contentWidget);
|
||||
contentLayout->setContentsMargins(0, 0, 0, 0);
|
||||
contentLayout->setSpacing(0);
|
||||
|
||||
// Create main splitter
|
||||
m_splitter = new ModernSplitter(Qt::Horizontal, m_contentWidget);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
contentLayout->addWidget(m_splitter);
|
||||
|
||||
// Left side - App list
|
||||
createLeftPanel();
|
||||
|
||||
// Right side - Content area
|
||||
createRightPanel();
|
||||
|
||||
// Set initial splitter sizes (400px for tabs, rest for content)
|
||||
m_splitter->setSizes({400, 600});
|
||||
|
||||
// Connect signals
|
||||
connect(m_searchEdit, &QLineEdit::textChanged, this,
|
||||
&InstalledAppsWidget::filterApps);
|
||||
connect(m_fileSharingCheckBox, &QCheckBox::toggled, this,
|
||||
&InstalledAppsWidget::onFileSharingFilterChanged);
|
||||
|
||||
m_stackedWidget->addWidget(m_contentWidget);
|
||||
}
|
||||
|
||||
// todo: move to services
|
||||
void InstalledAppsWidget::fetchInstalledApps()
|
||||
{
|
||||
@@ -362,6 +287,10 @@ void InstalledAppsWidget::fetchInstalledApps()
|
||||
QVariantMap result;
|
||||
QVariantList apps;
|
||||
|
||||
// result["success"] = true;
|
||||
// result["apps"] = apps;
|
||||
// return result;
|
||||
|
||||
instproxy_client_t instproxy = nullptr;
|
||||
lockdownd_client_t lockdownClient = nullptr;
|
||||
lockdownd_service_descriptor_t lockdowndService = nullptr;
|
||||
@@ -528,7 +457,6 @@ void InstalledAppsWidget::fetchInstalledApps()
|
||||
void InstalledAppsWidget::onAppsDataReady()
|
||||
{
|
||||
QVariantMap result = m_watcher->result();
|
||||
// m_progressBar->setVisible(false);
|
||||
|
||||
if (!result.value("success", false).toBool()) {
|
||||
showErrorState(result.value("error", "Unknown error").toString());
|
||||
@@ -537,10 +465,13 @@ void InstalledAppsWidget::onAppsDataReady()
|
||||
|
||||
QVariantList apps = result.value("apps").toList();
|
||||
if (apps.isEmpty()) {
|
||||
m_contentLabel->setText("No apps found");
|
||||
showErrorState("No apps found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch to content view once data is loaded
|
||||
m_stackedWidget->setCurrentWidget(m_contentWidget);
|
||||
|
||||
// Sort apps by display name
|
||||
std::sort(apps.begin(), apps.end(),
|
||||
[](const QVariant &a, const QVariant &b) {
|
||||
@@ -586,8 +517,8 @@ void InstalledAppsWidget::onAppsDataReady()
|
||||
createAppTab(tabName, bundleId, version);
|
||||
}
|
||||
|
||||
m_contentLabel->setText(
|
||||
QString("Found %1 installed apps").arg(apps.count()));
|
||||
// m_contentLabel->setText(
|
||||
// QString("Found %1 installed apps").arg(apps.count()));
|
||||
|
||||
// Select first tab if available
|
||||
if (!m_appTabs.isEmpty()) {
|
||||
@@ -604,7 +535,6 @@ void InstalledAppsWidget::createAppTab(const QString &appName,
|
||||
connect(tabWidget, &AppTabWidget::clicked, this,
|
||||
&InstalledAppsWidget::onAppTabClicked);
|
||||
|
||||
// TODO: is this needed ?
|
||||
// Remove the stretch before adding the new tab
|
||||
m_tabLayout->removeItem(m_tabLayout->itemAt(m_tabLayout->count() - 1));
|
||||
|
||||
@@ -633,26 +563,7 @@ void InstalledAppsWidget::selectAppTab(AppTabWidget *tab)
|
||||
m_selectedTab = tab;
|
||||
tab->setSelected(true);
|
||||
|
||||
// Update content
|
||||
QString bundleId = tab->getBundleId();
|
||||
QString version = tab->getVersion();
|
||||
QString appName = tab->getAppName();
|
||||
|
||||
// Remove the (System) suffix for display
|
||||
QString displayName = appName;
|
||||
displayName.remove(" (System)");
|
||||
|
||||
QString content = QString("<h2>%1</h2>"
|
||||
"<p><b>Bundle ID:</b> %2</p>"
|
||||
"<p><b>Version:</b> %3</p>"
|
||||
"<hr>")
|
||||
.arg(displayName, bundleId,
|
||||
version.isEmpty() ? "Unknown" : version);
|
||||
|
||||
m_contentLabel->setText(content);
|
||||
m_contentLabel->setTextFormat(Qt::RichText);
|
||||
m_contentLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
m_contentLabel->setWordWrap(true);
|
||||
|
||||
// Load app container data
|
||||
loadAppContainer(bundleId);
|
||||
@@ -695,21 +606,19 @@ void InstalledAppsWidget::loadAppContainer(const QString &bundleId)
|
||||
delete item;
|
||||
}
|
||||
|
||||
// Create a centered loading widget
|
||||
QWidget *loadingWidget = new QWidget();
|
||||
QVBoxLayout *loadingLayout = new QVBoxLayout(loadingWidget);
|
||||
loadingLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
QProcessIndicator *l = new QProcessIndicator();
|
||||
l->setType(QProcessIndicator::line_rotate);
|
||||
l->setFixedSize(32, 32);
|
||||
l->start();
|
||||
m_containerLayout->addWidget(l);
|
||||
m_containerScrollArea->setVisible(true);
|
||||
loadingLayout->addWidget(l, 0, Qt::AlignCenter);
|
||||
|
||||
// // Find container title and make it visible
|
||||
// QVBoxLayout *contentLayout =
|
||||
// qobject_cast<QVBoxLayout *>(m_contentWidget->layout());
|
||||
// if (contentLayout && contentLayout->count() > 1) {
|
||||
// QLayoutItem *titleItem = contentLayout->itemAt(1);
|
||||
// if (titleItem && titleItem->widget()) {
|
||||
// titleItem->widget()->setVisible(true);
|
||||
// }
|
||||
// }
|
||||
m_containerLayout->addWidget(loadingWidget);
|
||||
m_containerScrollArea->setVisible(true);
|
||||
|
||||
QFuture<QVariantMap> future = QtConcurrent::run([this, bundleId]()
|
||||
-> QVariantMap {
|
||||
@@ -865,9 +774,9 @@ void InstalledAppsWidget::onContainerDataReady()
|
||||
}
|
||||
|
||||
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->setStyleSheet(
|
||||
// "color: #999; font-style: italic; text-align: center;");
|
||||
errorLabel->setAlignment(Qt::AlignCenter);
|
||||
m_containerLayout->addWidget(errorLabel);
|
||||
return;
|
||||
@@ -876,29 +785,19 @@ void InstalledAppsWidget::onContainerDataReady()
|
||||
// Get the AFC clients from the result
|
||||
afc_client_t afcClient = reinterpret_cast<afc_client_t>(
|
||||
result.value("afcClient").value<void *>());
|
||||
house_arrest_client_t houseArrestClient =
|
||||
reinterpret_cast<house_arrest_client_t>(
|
||||
result.value("houseArrestClient").value<void *>());
|
||||
|
||||
if (!afcClient) {
|
||||
QLabel *errorLabel =
|
||||
new QLabel("Failed to get AFC client for app container");
|
||||
// errorLabel->setStyleSheet("color: #999; font-style: italic;");
|
||||
m_containerLayout->addWidget(errorLabel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create AfcExplorerWidget with the house arrest AFC client
|
||||
AfcExplorerWidget *explorer = new AfcExplorerWidget(
|
||||
afcClient,
|
||||
[houseArrestClient]() {
|
||||
// Cleanup callback when client becomes invalid
|
||||
if (houseArrestClient) {
|
||||
house_arrest_client_free(houseArrestClient);
|
||||
}
|
||||
},
|
||||
m_device, this);
|
||||
|
||||
// todo:afcClient never gets freed
|
||||
AfcExplorerWidget *explorer =
|
||||
new AfcExplorerWidget(afcClient, []() {}, m_device, this);
|
||||
explorer->setStyleSheet("border :none;");
|
||||
m_containerLayout->addWidget(explorer);
|
||||
}
|
||||
|
||||
@@ -908,3 +807,86 @@ void InstalledAppsWidget::onFileSharingFilterChanged(bool enabled)
|
||||
// Refresh the apps list when filter changes
|
||||
fetchInstalledApps();
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::createLeftPanel()
|
||||
{
|
||||
QWidget *tabWidget = new QWidget();
|
||||
tabWidget->setMinimumWidth(100);
|
||||
tabWidget->setMaximumWidth(500);
|
||||
|
||||
QVBoxLayout *tabWidgetLayout = new QVBoxLayout(tabWidget);
|
||||
tabWidgetLayout->setContentsMargins(0, 0, 0, 0);
|
||||
tabWidgetLayout->setSpacing(0);
|
||||
|
||||
// Search container
|
||||
QWidget *searchContainer = new QWidget();
|
||||
searchContainer->setFixedHeight(60);
|
||||
QHBoxLayout *searchLayout = new QHBoxLayout(searchContainer);
|
||||
searchLayout->setContentsMargins(5, 0, 5, 5);
|
||||
|
||||
// Search box
|
||||
m_searchEdit = new QLineEdit();
|
||||
m_searchEdit->setPlaceholderText("Search apps...");
|
||||
m_searchEdit->setStyleSheet("QLineEdit { "
|
||||
" border: 2px solid #e0e0e0; "
|
||||
" border-radius: 6px; "
|
||||
" padding: 8px 12px; "
|
||||
" font-size: 14px; "
|
||||
"} "
|
||||
"QLineEdit:focus { "
|
||||
" border: 2px solid #007AFF; "
|
||||
" outline: none; "
|
||||
"}");
|
||||
searchLayout->addWidget(m_searchEdit);
|
||||
|
||||
// File sharing filter checkbox
|
||||
m_fileSharingCheckBox = new QCheckBox("Show Only File Sharing Enabled");
|
||||
m_fileSharingCheckBox->setChecked(true);
|
||||
m_fileSharingCheckBox->setStyleSheet("QCheckBox { font-size: 10px; }");
|
||||
searchLayout->addWidget(m_fileSharingCheckBox);
|
||||
|
||||
tabWidgetLayout->addWidget(searchContainer);
|
||||
|
||||
// App list scroll area
|
||||
m_tabScrollArea = new QScrollArea();
|
||||
m_tabScrollArea->setWidgetResizable(true);
|
||||
m_tabScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_tabScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_tabScrollArea->setStyleSheet("QScrollArea { border: none; }");
|
||||
|
||||
m_tabContainer = new QWidget();
|
||||
m_tabLayout = new QVBoxLayout(m_tabContainer);
|
||||
m_tabLayout->setContentsMargins(0, 0, 10, 0);
|
||||
m_tabLayout->setSpacing(10);
|
||||
m_tabLayout->addStretch();
|
||||
|
||||
m_tabScrollArea->setWidget(m_tabContainer);
|
||||
tabWidgetLayout->addWidget(m_tabScrollArea);
|
||||
|
||||
m_splitter->addWidget(tabWidget);
|
||||
}
|
||||
|
||||
void InstalledAppsWidget::createRightPanel()
|
||||
{
|
||||
QWidget *rightContentWidget = new QWidget();
|
||||
|
||||
QVBoxLayout *contentLayout = new QVBoxLayout(rightContentWidget);
|
||||
contentLayout->setContentsMargins(0, 0, 0, 5);
|
||||
contentLayout->setSpacing(0);
|
||||
|
||||
// Container explorer area
|
||||
m_containerScrollArea = new QScrollArea();
|
||||
m_containerScrollArea->setWidgetResizable(true);
|
||||
m_containerScrollArea->setMinimumHeight(200);
|
||||
m_containerScrollArea->setVisible(false);
|
||||
|
||||
m_containerWidget = new QWidget();
|
||||
m_containerLayout = new QVBoxLayout(m_containerWidget);
|
||||
m_containerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
m_containerLayout->setSpacing(0);
|
||||
|
||||
m_containerScrollArea->setWidget(m_containerWidget);
|
||||
contentLayout->addWidget(m_containerScrollArea);
|
||||
|
||||
m_splitter->addWidget(rightContentWidget);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QSplitter>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -78,6 +79,11 @@ private slots:
|
||||
|
||||
private:
|
||||
void setupUI();
|
||||
void createLoadingWidget();
|
||||
void createErrorWidget();
|
||||
void createContentWidget();
|
||||
void createLeftPanel();
|
||||
void createRightPanel();
|
||||
void fetchInstalledApps();
|
||||
void createAppTab(const QString &appName, const QString &bundleId,
|
||||
const QString &version);
|
||||
@@ -86,16 +92,20 @@ private:
|
||||
void selectAppTab(AppTabWidget *tab);
|
||||
void filterApps(const QString &searchText);
|
||||
void loadAppContainer(const QString &bundleId);
|
||||
void createHouseArrestAfcClient();
|
||||
|
||||
iDescriptorDevice *m_device;
|
||||
QHBoxLayout *m_mainLayout;
|
||||
QStackedWidget *m_stackedWidget;
|
||||
QWidget *m_loadingWidget;
|
||||
QWidget *m_errorWidget;
|
||||
QWidget *m_contentWidget;
|
||||
QLabel *m_errorLabel;
|
||||
QLineEdit *m_searchEdit;
|
||||
QCheckBox *m_fileSharingCheckBox;
|
||||
QScrollArea *m_tabScrollArea;
|
||||
QWidget *m_tabContainer;
|
||||
QVBoxLayout *m_tabLayout;
|
||||
QWidget *m_contentWidget;
|
||||
QLabel *m_contentLabel;
|
||||
QProgressBar *m_progressBar;
|
||||
QScrollArea *m_containerScrollArea;
|
||||
QWidget *m_containerWidget;
|
||||
|
||||
+5
-6
@@ -13,9 +13,8 @@
|
||||
|
||||
LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Login to App Store");
|
||||
setWindowTitle("Login to App Store - iDescriptor");
|
||||
setModal(true);
|
||||
// setFixedSize(400, 250);
|
||||
setFixedWidth(400);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
@@ -24,7 +23,7 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent)
|
||||
|
||||
// Email
|
||||
QLabel *emailLabel = new QLabel("Email:");
|
||||
emailLabel->setStyleSheet("font-size: 14px; color: #555;");
|
||||
emailLabel->setStyleSheet("font-size: 14px");
|
||||
layout->addWidget(emailLabel);
|
||||
|
||||
m_emailEdit = new QLineEdit();
|
||||
@@ -35,7 +34,7 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent)
|
||||
|
||||
// Password
|
||||
QLabel *passwordLabel = new QLabel("Password:");
|
||||
passwordLabel->setStyleSheet("font-size: 14px; color: #555;");
|
||||
passwordLabel->setStyleSheet("font-size: 14px");
|
||||
layout->addWidget(passwordLabel);
|
||||
|
||||
m_passwordEdit = new QLineEdit();
|
||||
@@ -48,8 +47,8 @@ LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent)
|
||||
// Description
|
||||
QLabel *descriptionLabel =
|
||||
new QLabel("Don't worry, your credentials won't be "
|
||||
"stored, shared anywhere. This App is open-source.");
|
||||
descriptionLabel->setStyleSheet("font-size: 10px; font-weight: bold;");
|
||||
"stored or shared anywhere. This App is open-source.");
|
||||
descriptionLabel->setStyleSheet("font-size: 10px; font-weight: thin;");
|
||||
descriptionLabel->setAlignment(Qt::AlignLeft);
|
||||
descriptionLabel->setWordWrap(true); // Add this line
|
||||
layout->addWidget(descriptionLabel);
|
||||
|
||||
+14
-172
@@ -5,48 +5,28 @@
|
||||
#include "ifusediskunmountbutton.h"
|
||||
#include "ifusemanager.h"
|
||||
#include "settingswidget.h"
|
||||
#include <QDialog>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSvgItem>
|
||||
#include <QMessageBox>
|
||||
#include <QSvgRenderer>
|
||||
#include <QTimer>
|
||||
#include <QtSvg>
|
||||
|
||||
#include <libimobiledevice/libimobiledevice.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "appswidget.h"
|
||||
#include "devicemanagerwidget.h"
|
||||
#include "iDescriptor-ui.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "jailbrokenwidget.h"
|
||||
#include "libirecovery.h"
|
||||
#include "toolboxwidget.h"
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QStack>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "appcontext.h"
|
||||
#include "deviceinfowidget.h"
|
||||
#include "devicemenuwidget.h"
|
||||
#include "fileexplorerwidget.h"
|
||||
#include "jailbrokenwidget.h"
|
||||
#include "recoverydeviceinfowidget.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QApplication>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void handleCallback(const idevice_event_t *event, void *userData)
|
||||
{
|
||||
@@ -105,15 +85,15 @@ void handleCallbackRecovery(const irecv_device_event_t *event, void *userData)
|
||||
case IRECV_DEVICE_ADD:
|
||||
qDebug() << "Recovery device added: ";
|
||||
// TODO: handle recovery device addition
|
||||
// QMetaObject::invokeMethod(ctx->mainWindow, "onRecoveryDeviceAdded",
|
||||
// Qt::QueuedConnection,
|
||||
// Q_ARG(QObject *, new
|
||||
// RecoveryDeviceInfo(event)));
|
||||
QMetaObject::invokeMethod(
|
||||
AppContext::sharedInstance(), "addRecoveryDevice",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(RecoveryDeviceInfo *, new RecoveryDeviceInfo(event)));
|
||||
break;
|
||||
case IRECV_DEVICE_REMOVE:
|
||||
qDebug() << "Recovery device removed: ";
|
||||
QMetaObject::invokeMethod(
|
||||
AppContext::sharedInstance(), "onRecoveryDeviceRemoved",
|
||||
AppContext::sharedInstance(), "removeRecoveryDevice",
|
||||
Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::number(event->device_info->ecid)));
|
||||
break;
|
||||
@@ -133,7 +113,9 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent), ui(new Ui::MainWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
// setStyleSheet("background-color: white; color: black;");
|
||||
|
||||
setWindowIcon(QIcon(":/icons/icon.png"));
|
||||
|
||||
// Create custom tab widget
|
||||
m_customTabWidget = new CustomTabWidget(this);
|
||||
m_customTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea,
|
||||
@@ -250,10 +232,9 @@ void MainWindow::createMenus()
|
||||
QMenu *actionsMenu = menuBar()->addMenu("&Actions");
|
||||
|
||||
QAction *aboutAct = new QAction("&About iDescriptor", this);
|
||||
connect(aboutAct, &QAction::triggered, this, [=]() {
|
||||
QMessageBox::about(this, "About iDescriptor",
|
||||
"<b>iDescriptor</b><br>"
|
||||
"A modern device management tool.");
|
||||
connect(aboutAct, &QAction::triggered, this, [this]() {
|
||||
QMessageBox::about(this, "iDescriptor",
|
||||
"A free and open-source idevice management tool.");
|
||||
});
|
||||
actionsMenu->addAction(aboutAct);
|
||||
#endif
|
||||
@@ -276,75 +257,6 @@ void MainWindow::updateNoDevicesConnected()
|
||||
m_mainStackedWidget->setCurrentIndex(1); // Show device list page
|
||||
}
|
||||
|
||||
void MainWindow::onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj)
|
||||
{
|
||||
if (!recoveryDeviceInfoObj)
|
||||
// TODO: handle
|
||||
return;
|
||||
try {
|
||||
m_mainStackedWidget->setCurrentIndex(1);
|
||||
RecoveryDeviceInfo *device =
|
||||
qobject_cast<RecoveryDeviceInfo *>(recoveryDeviceInfoObj);
|
||||
if (!device) {
|
||||
qDebug() << "Invalid recovery device info object";
|
||||
return;
|
||||
}
|
||||
// IDescriptorInitDeviceResultRecovery initResult=
|
||||
// init_idescriptor_recovery_device(deviceInfo);
|
||||
|
||||
// IDescriptorInitDeviceResult initResult =
|
||||
// init_idescriptor_device(udid.toStdString().c_str());
|
||||
|
||||
qDebug() << "Recovery device initialized: " << device->ecid;
|
||||
|
||||
std::string added_ecid =
|
||||
AppContext::sharedInstance()->addRecoveryDevice(device);
|
||||
|
||||
// Create device info widget
|
||||
RecoveryDeviceInfoWidget *recoveryDeviceInfoWidget =
|
||||
new RecoveryDeviceInfoWidget(device);
|
||||
QPixmap recoveryIcon(16, 16);
|
||||
recoveryIcon.fill(Qt::transparent);
|
||||
QPainter painter(&recoveryIcon);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setBrush(QColor(255, 59, 48)); // Red for recovery mode
|
||||
painter.drawRoundedRect(2, 2, 12, 12, 2, 2);
|
||||
|
||||
// int mostRecentDevice =
|
||||
// customTabWidget->addTabWithIcon(recoveryDeviceInfoWidget,
|
||||
// recoveryIcon, "Recovery Mode");
|
||||
|
||||
// m_device_menu_widgets[added_ecid] = recoveryDeviceInfoWidget;
|
||||
// Get device icon and product type for tab
|
||||
// QString tabTitle =
|
||||
// QString::fromStdString(device->product.toStdString());
|
||||
QString tabTitle = QString::fromStdString("recovery mode device");
|
||||
|
||||
// Add tab with custom icon
|
||||
// int mostRecentDevice =
|
||||
// m_deviceManager->addDevice(recoveryDeviceInfoWidget, tabTitle);
|
||||
// m_deviceManager->setCurrentDevice(mostRecentDevice);
|
||||
} catch (const std::exception &e) {
|
||||
qDebug() << "Exception in onDeviceAdded: " << e.what();
|
||||
QMessageBox::critical(
|
||||
this, "Error",
|
||||
"An error occurred while processing device information");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onRecoveryDeviceRemoved(QObject *deviceInfoObj)
|
||||
{
|
||||
auto *info = qobject_cast<RecoveryDeviceInfo *>(deviceInfoObj);
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
qDebug() << "Recovery device removed: " << info->ecid;
|
||||
|
||||
// TODO: Implement proper device removal in DeviceManagerWidget
|
||||
// For now, we'll just log the removal
|
||||
qDebug() << "Recovery device cleanup not yet implemented";
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
idevice_event_unsubscribe();
|
||||
@@ -356,75 +268,5 @@ MainWindow::~MainWindow()
|
||||
// }
|
||||
// idescriptor_devices.clear();
|
||||
delete ui;
|
||||
sleep(1); // Give some time for cleanup to finish
|
||||
}
|
||||
|
||||
void MainWindow::onDeviceInitFailed(QString udid, lockdownd_error_t err)
|
||||
{
|
||||
QString errorTitle = "Device Connection Error";
|
||||
QString errorMessage;
|
||||
|
||||
switch (err) {
|
||||
case LOCKDOWN_E_PASSWORD_PROTECTED:
|
||||
errorMessage =
|
||||
QString(
|
||||
"Could not validate device %1 because a passcode is set.\n\n"
|
||||
"Please enter the passcode on your device and try again.")
|
||||
.arg(udid);
|
||||
qDebug() << "ERROR: Could not validate with device" << udid
|
||||
<< "because a passcode is set. Please enter the passcode on "
|
||||
"the device and retry.";
|
||||
break;
|
||||
case LOCKDOWN_E_INVALID_CONF:
|
||||
case LOCKDOWN_E_INVALID_HOST_ID:
|
||||
errorMessage = QString("Device %1 is not paired with this computer.\n\n"
|
||||
"Please check your device settings.")
|
||||
.arg(udid);
|
||||
qDebug() << "ERROR: Device" << udid << "is not paired with this host";
|
||||
break;
|
||||
case LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING:
|
||||
errorMessage =
|
||||
QString(
|
||||
"Trust dialog is waiting for your response.\n\n"
|
||||
"Please accept the trust dialog on the screen of device %1,\n"
|
||||
"then attempt to pair again.")
|
||||
.arg(udid);
|
||||
qDebug()
|
||||
<< "ERROR: Please accept the trust dialog on the screen of device"
|
||||
<< udid << ", then attempt to pair again.";
|
||||
break;
|
||||
case LOCKDOWN_E_USER_DENIED_PAIRING:
|
||||
errorMessage = QString("Pairing rejected.\n\n"
|
||||
"You denied the trust dialog on device %1.")
|
||||
.arg(udid);
|
||||
qDebug() << "ERROR: Device" << udid
|
||||
<< "said that the user denied the trust dialog.";
|
||||
break;
|
||||
case LOCKDOWN_E_PAIRING_FAILED:
|
||||
errorMessage = QString("Pairing with device %1 failed.\n\n"
|
||||
"Please try again or restart your device.")
|
||||
.arg(udid);
|
||||
qDebug() << "ERROR: Pairing with device" << udid << "failed.";
|
||||
break;
|
||||
case LOCKDOWN_E_GET_PROHIBITED:
|
||||
case LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION:
|
||||
errorMessage = "Pairing is not possible over this connection.\n\n"
|
||||
"Please try using a USB connection.";
|
||||
qDebug() << "ERROR: Pairing is not possible over this connection.";
|
||||
break;
|
||||
default:
|
||||
errorMessage = QString("Unknown error occurred with device %1.\n\n"
|
||||
"Error code: %2")
|
||||
.arg(udid)
|
||||
.arg(err);
|
||||
qDebug() << "ERROR: Device" << udid << "returned unhandled error code"
|
||||
<< err;
|
||||
break;
|
||||
}
|
||||
QMessageBox errorDialog(this);
|
||||
errorDialog.setWindowTitle(errorTitle);
|
||||
errorDialog.setText(errorMessage);
|
||||
errorDialog.setIcon(QMessageBox::Warning);
|
||||
errorDialog.setStandardButtons(QMessageBox::Ok);
|
||||
errorDialog.exec();
|
||||
sleep(2); // Give some time for cleanup to finish
|
||||
}
|
||||
+1
-5
@@ -2,11 +2,11 @@
|
||||
#define MAINWINDOW_H
|
||||
#include "customtabwidget.h"
|
||||
#include "devicemanagerwidget.h"
|
||||
#include "devicemenuwidget.h"
|
||||
#include "iDescriptor.h"
|
||||
#include "libirecovery.h"
|
||||
#include <QLabel>
|
||||
#include <QMainWindow>
|
||||
#include <QStackedWidget>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui
|
||||
@@ -23,11 +23,7 @@ public:
|
||||
static MainWindow *sharedInstance();
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
void onRecoveryDeviceAdded(QObject *recoveryDeviceInfoObj);
|
||||
void onRecoveryDeviceRemoved(QObject *deviceInfoObj);
|
||||
|
||||
public slots:
|
||||
void onDeviceInitFailed(QString udid, lockdownd_error_t err);
|
||||
void updateNoDevicesConnected();
|
||||
|
||||
private:
|
||||
|
||||
@@ -15,26 +15,24 @@ QueryMobileGestaltWidget::QueryMobileGestaltWidget(iDescriptorDevice *device,
|
||||
|
||||
void QueryMobileGestaltWidget::setupUI()
|
||||
{
|
||||
setWindowTitle("MobileGestalt Query Tool");
|
||||
setWindowTitle("Query MobileGestalt - iDescriptor");
|
||||
setMinimumSize(800, 600);
|
||||
|
||||
// Main layout
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
|
||||
// Title
|
||||
QLabel *titleLabel = new QLabel("MobileGestalt Query Interface");
|
||||
titleLabel->setStyleSheet(
|
||||
"font-size: 18px; font-weight: bold; margin: 10px;");
|
||||
titleLabel->setAlignment(Qt::AlignCenter);
|
||||
mainLayout->addWidget(titleLabel);
|
||||
QLabel *desc = new QLabel("This tool lets you query MobileGestalt keys , "
|
||||
"which provide various device information.");
|
||||
desc->setStyleSheet("margin:5px;");
|
||||
mainLayout->addWidget(desc);
|
||||
|
||||
// Selection group
|
||||
selectionGroup = new QGroupBox("Select MobileGestalt Keys");
|
||||
selectionGroup->setStyleSheet(
|
||||
"QGroupBox { font-weight: bold; margin-top: 10px; }");
|
||||
mainLayout->addWidget(selectionGroup);
|
||||
|
||||
QVBoxLayout *groupLayout = new QVBoxLayout(selectionGroup);
|
||||
groupLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Select/Clear buttons
|
||||
buttonLayout = new QHBoxLayout();
|
||||
@@ -45,6 +43,7 @@ void QueryMobileGestaltWidget::setupUI()
|
||||
buttonLayout->addWidget(selectAllButton);
|
||||
buttonLayout->addWidget(clearAllButton);
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->setContentsMargins(5, 5, 5, 5);
|
||||
groupLayout->addLayout(buttonLayout);
|
||||
|
||||
// Scroll area for checkboxes
|
||||
@@ -63,23 +62,8 @@ void QueryMobileGestaltWidget::setupUI()
|
||||
|
||||
// Query button
|
||||
queryButton = new QPushButton("Query MobileGestalt");
|
||||
queryButton->setStyleSheet("QPushButton {"
|
||||
" background-color: #4CAF50;"
|
||||
" color: white;"
|
||||
" border: none;"
|
||||
" padding: 12px 24px;"
|
||||
" font-size: 16px;"
|
||||
" font-weight: bold;"
|
||||
" border-radius: 6px;"
|
||||
" margin: 10px;"
|
||||
"}"
|
||||
"QPushButton:hover {"
|
||||
" background-color: #45a049;"
|
||||
"}"
|
||||
"QPushButton:pressed {"
|
||||
" background-color: #3d8b40;"
|
||||
"}");
|
||||
queryButton->setMaximumWidth(200);
|
||||
queryButton->setProperty("primary", true);
|
||||
queryButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
mainLayout->addWidget(queryButton, 0, Qt::AlignCenter);
|
||||
|
||||
// Status label
|
||||
@@ -87,23 +71,17 @@ void QueryMobileGestaltWidget::setupUI()
|
||||
statusLabel->setStyleSheet("color: #666; font-style: italic; margin: 5px;");
|
||||
mainLayout->addWidget(statusLabel);
|
||||
|
||||
// Output text area
|
||||
QLabel *outputLabel = new QLabel("Query Results:");
|
||||
outputLabel->setStyleSheet("font-weight: bold; margin-top: 10px;");
|
||||
mainLayout->addWidget(outputLabel);
|
||||
|
||||
QGroupBox *outputGroup = new QGroupBox("Query Results");
|
||||
outputTextEdit = new QTextEdit();
|
||||
outputTextEdit->setReadOnly(true);
|
||||
outputTextEdit->setPlaceholderText("Query results will appear here...");
|
||||
outputTextEdit->setStyleSheet(
|
||||
"QTextEdit {"
|
||||
" border: 2px solid #ddd;"
|
||||
" border-radius: 5px;"
|
||||
" padding: 10px;"
|
||||
" font-family: 'Consolas', 'Monaco', monospace;"
|
||||
" background-color: #f9f9f9;"
|
||||
"}");
|
||||
mainLayout->addWidget(outputTextEdit);
|
||||
outputTextEdit->setPlaceholderText("results will appear here...");
|
||||
outputTextEdit->setStyleSheet("QTextEdit {"
|
||||
"border : none;"
|
||||
"}");
|
||||
outputGroup->setLayout(new QVBoxLayout());
|
||||
outputGroup->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
outputGroup->layout()->addWidget(outputTextEdit);
|
||||
mainLayout->addWidget(outputGroup);
|
||||
|
||||
// Connect signals
|
||||
connect(queryButton, &QPushButton::clicked, this,
|
||||
@@ -1091,8 +1069,6 @@ void QueryMobileGestaltWidget::onQueryButtonClicked()
|
||||
QString("Querying %1 key(s)...").arg(selectedKeys.size()));
|
||||
statusLabel->setStyleSheet("color: #4CAF50; font-style: italic;");
|
||||
|
||||
// Call your actual query function here
|
||||
// For demonstration, using a mock function
|
||||
QMap<QString, QVariant> results = queryMobileGestalt(selectedKeys);
|
||||
|
||||
displayResults(results);
|
||||
@@ -1135,7 +1111,6 @@ void QueryMobileGestaltWidget::displayResults(
|
||||
outputTextEdit->setPlainText(output);
|
||||
}
|
||||
|
||||
// Mock query function - replace this with your actual implementation
|
||||
QMap<QString, QVariant>
|
||||
QueryMobileGestaltWidget::queryMobileGestalt(const QStringList &keys)
|
||||
{
|
||||
@@ -1146,8 +1121,6 @@ QueryMobileGestaltWidget::queryMobileGestalt(const QStringList &keys)
|
||||
qDebug() << "MobileGestalt query failed.";
|
||||
return {};
|
||||
}
|
||||
// This is a mock implementation
|
||||
// Replace this with your actual query function that takes a plist dict
|
||||
pugi::xml_document infoXml;
|
||||
pugi::xml_parse_result result = infoXml.load_string(xml);
|
||||
if (xml)
|
||||
|
||||
@@ -23,58 +23,6 @@
|
||||
#ifndef _WIN32
|
||||
#include <signal.h>
|
||||
#endif
|
||||
static void get_image_filename(char *imgdata, char **filename)
|
||||
{
|
||||
// If the provided filename already has an extension, use it as is.
|
||||
if (*filename) {
|
||||
char *last_dot = strrchr(*filename, '.');
|
||||
if (last_dot && !strchr(last_dot, '/')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the appropriate file extension for the filename.
|
||||
const char *fileext = NULL;
|
||||
if (memcmp(imgdata, "\x89PNG", 4) == 0) {
|
||||
fileext = ".png";
|
||||
} else if (memcmp(imgdata, "MM\x00*", 4) == 0) {
|
||||
fileext = ".tiff";
|
||||
} else {
|
||||
printf("WARNING: screenshot data has unexpected image format.\n");
|
||||
fileext = ".dat";
|
||||
}
|
||||
|
||||
// If a filename without an extension is provided, append the extension.
|
||||
// Otherwise, generate a filename based on the current time.
|
||||
char *basename = NULL;
|
||||
if (*filename) {
|
||||
basename = (char *)malloc(strlen(*filename) + 1);
|
||||
strcpy(basename, *filename);
|
||||
free(*filename);
|
||||
*filename = NULL;
|
||||
} else {
|
||||
time_t now = time(NULL);
|
||||
basename = (char *)malloc(32);
|
||||
strftime(basename, 31, "screenshot-%Y-%m-%d-%H-%M-%S", gmtime(&now));
|
||||
}
|
||||
|
||||
// Ensure the filename is unique on disk.
|
||||
char *unique_filename =
|
||||
(char *)malloc(strlen(basename) + strlen(fileext) + 7);
|
||||
sprintf(unique_filename, "%s%s", basename, fileext);
|
||||
int i;
|
||||
for (i = 2; i < (1 << 16); i++) {
|
||||
if (access(unique_filename, F_OK) == -1) {
|
||||
*filename = unique_filename;
|
||||
break;
|
||||
}
|
||||
sprintf(unique_filename, "%s-%d%s", basename, i, fileext);
|
||||
}
|
||||
if (!*filename) {
|
||||
free(unique_filename);
|
||||
}
|
||||
free(basename);
|
||||
}
|
||||
|
||||
RealtimeScreen::RealtimeScreen(QString udid, QWidget *parent)
|
||||
: QWidget{parent}, timer(nullptr), capturing(false), shotrClient(nullptr)
|
||||
@@ -186,46 +134,6 @@ RealtimeScreen::RealtimeScreen(QString udid, QWidget *parent)
|
||||
} else {
|
||||
connect(timer, &QTimer::timeout, this,
|
||||
&RealtimeScreen::updateScreenshot);
|
||||
// char *imgdata = NULL;
|
||||
// uint64_t imgsize = 0;
|
||||
// if (screenshotr_take_screenshot(shotrClient, &imgdata,
|
||||
// &imgsize) == SCREENSHOTR_E_SUCCESS)
|
||||
// {
|
||||
// get_image_filename(imgdata, &filename);
|
||||
// if (!filename)
|
||||
// {
|
||||
// printf("FATAL: Could not find a unique filename!\n");
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// FILE *f = fopen(filename, "wb");
|
||||
// if (f)
|
||||
// {
|
||||
// if (fwrite(imgdata, 1, (size_t)imgsize, f) ==
|
||||
// (size_t)imgsize)
|
||||
// {
|
||||
// printf("Screenshot saved to %s\n", filename);
|
||||
// result = 0;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// printf("Could not save screenshot to file
|
||||
// %s!\n", filename);
|
||||
// }
|
||||
// fclose(f);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// printf("Could not open %s for writing: %s\n",
|
||||
// filename, strerror(errno));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// printf("Could not get screenshot!\n");
|
||||
// }
|
||||
// screenshotr_client_free(shotrClient);
|
||||
}
|
||||
} else {
|
||||
printf("Could not start screenshotr service: %s\nRemember that you "
|
||||
|
||||
+2
-10
@@ -381,18 +381,10 @@ void ToolboxWidget::onToolboxClicked(iDescriptorTool tool)
|
||||
virtualLocation->show();
|
||||
} break;
|
||||
case iDescriptorTool::Restart: {
|
||||
// TODO:WIP
|
||||
std::string udid = m_currentDevice->udid;
|
||||
AppContext::sharedInstance()->instanceRemoveDevice(
|
||||
QString::fromStdString(udid));
|
||||
// QMetaObject::invokeMethod(AppContext::sharedInstance(),
|
||||
// "removeDevice",
|
||||
// Qt::QueuedConnection,
|
||||
// Q_ARG(QString, QString(udid.c_str())));
|
||||
if (!(restart(udid)))
|
||||
if (!(restart(m_currentDevice->udid)))
|
||||
warn("Failed to restart device");
|
||||
else {
|
||||
warn("Device services restarted successfully", "Success");
|
||||
warn("Device will restart once unplugged", "Success");
|
||||
qDebug() << "Restarting device";
|
||||
}
|
||||
} break;
|
||||
|
||||
+39
-60
@@ -6,9 +6,11 @@
|
||||
#include <QDoubleValidator>
|
||||
#include <QGeoCoordinate>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
@@ -24,6 +26,7 @@
|
||||
VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
: QWidget{parent}, m_device(device)
|
||||
{
|
||||
setWindowTitle("Virtual Location - iDescriptor");
|
||||
// Create the main layout
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setContentsMargins(10, 10, 10, 10);
|
||||
@@ -32,8 +35,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
// Create left panel for controls
|
||||
QWidget *rightPanel = new QWidget();
|
||||
rightPanel->setFixedWidth(250);
|
||||
rightPanel->setStyleSheet(
|
||||
"QWidget { background-color: #f0f0f0; border-radius: 5px; }");
|
||||
|
||||
QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel);
|
||||
rightLayout->setContentsMargins(15, 15, 15, 15);
|
||||
@@ -41,54 +42,33 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
|
||||
// Title
|
||||
QLabel *titleLabel = new QLabel("Virtual Location Settings");
|
||||
titleLabel->setStyleSheet(
|
||||
"font-size: 14px; font-weight: bold; color: #333;");
|
||||
titleLabel->setStyleSheet("margin-bottom: 10px;");
|
||||
rightLayout->addWidget(titleLabel);
|
||||
|
||||
// Coordinates section
|
||||
QLabel *coordsLabel = new QLabel("Coordinates:");
|
||||
coordsLabel->setStyleSheet("font-weight: bold; color: #555;");
|
||||
rightLayout->addWidget(coordsLabel);
|
||||
QGroupBox *coordGroup = new QGroupBox("Coordinates");
|
||||
rightLayout->addWidget(coordGroup);
|
||||
|
||||
QVBoxLayout *coordLayout = new QVBoxLayout(coordGroup);
|
||||
|
||||
// Latitude input
|
||||
QLabel *latLabel = new QLabel("Latitude:");
|
||||
latLabel->setStyleSheet("color: #666;");
|
||||
rightLayout->addWidget(latLabel);
|
||||
coordLayout->addWidget(latLabel);
|
||||
|
||||
m_latitudeEdit = new QLineEdit();
|
||||
m_latitudeEdit->setPlaceholderText("e.g., 59.9139");
|
||||
m_latitudeEdit->setText("59.9139");
|
||||
m_latitudeEdit->setValidator(new QDoubleValidator(-90.0, 90.0, 6, this));
|
||||
m_latitudeEdit->setStyleSheet(
|
||||
"padding: 5px; border: 1px solid #ccc; border-radius: 3px;");
|
||||
rightLayout->addWidget(m_latitudeEdit);
|
||||
coordLayout->addWidget(m_latitudeEdit);
|
||||
|
||||
// Longitude input
|
||||
QLabel *lonLabel = new QLabel("Longitude:");
|
||||
lonLabel->setStyleSheet("color: #666;");
|
||||
rightLayout->addWidget(lonLabel);
|
||||
coordLayout->addWidget(lonLabel);
|
||||
|
||||
m_longitudeEdit = new QLineEdit();
|
||||
m_longitudeEdit->setPlaceholderText("e.g., 10.7522");
|
||||
m_longitudeEdit->setText("10.7522");
|
||||
m_longitudeEdit->setValidator(new QDoubleValidator(-180.0, 180.0, 6, this));
|
||||
m_longitudeEdit->setStyleSheet(
|
||||
"padding: 5px; border: 1px solid #ccc; border-radius: 3px;");
|
||||
rightLayout->addWidget(m_longitudeEdit);
|
||||
|
||||
// Altitude input
|
||||
QLabel *altLabel = new QLabel("Altitude (meters):");
|
||||
altLabel->setStyleSheet("color: #666;");
|
||||
rightLayout->addWidget(altLabel);
|
||||
|
||||
m_altitudeEdit = new QLineEdit();
|
||||
m_altitudeEdit->setPlaceholderText("e.g., 100.0");
|
||||
m_altitudeEdit->setText("100.0");
|
||||
m_altitudeEdit->setValidator(
|
||||
new QDoubleValidator(-500.0, 10000.0, 2, this));
|
||||
m_altitudeEdit->setStyleSheet(
|
||||
"padding: 5px; border: 1px solid #ccc; border-radius: 3px;");
|
||||
rightLayout->addWidget(m_altitudeEdit);
|
||||
coordLayout->addWidget(m_longitudeEdit);
|
||||
|
||||
// Add some spacing
|
||||
rightLayout->addItem(
|
||||
@@ -96,20 +76,7 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
|
||||
// Apply button
|
||||
m_applyButton = new QPushButton("Apply Settings");
|
||||
m_applyButton->setStyleSheet("QPushButton {"
|
||||
" background-color: #4CAF50;"
|
||||
" color: white;"
|
||||
" border: none;"
|
||||
" padding: 10px;"
|
||||
" border-radius: 5px;"
|
||||
" font-weight: bold;"
|
||||
"}"
|
||||
"QPushButton:hover {"
|
||||
" background-color: #45a049;"
|
||||
"}"
|
||||
"QPushButton:pressed {"
|
||||
" background-color: #3d8b40;"
|
||||
"}");
|
||||
m_applyButton->setDefault(true);
|
||||
rightLayout->addWidget(m_applyButton);
|
||||
|
||||
// Add stretch to push everything to the top
|
||||
@@ -136,8 +103,6 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
|
||||
&VirtualLocation::onInputChanged);
|
||||
connect(m_longitudeEdit, &QLineEdit::textChanged, this,
|
||||
&VirtualLocation::onInputChanged);
|
||||
connect(m_altitudeEdit, &QLineEdit::textChanged, this,
|
||||
&VirtualLocation::onInputChanged);
|
||||
connect(m_applyButton, &QPushButton::clicked, this,
|
||||
&VirtualLocation::onApplyClicked);
|
||||
|
||||
@@ -298,23 +263,34 @@ void VirtualLocation::updateInputsFromMap(double latitude, double longitude)
|
||||
|
||||
void VirtualLocation::onApplyClicked()
|
||||
{
|
||||
bool devImgSuccess =
|
||||
DevDiskManager::sharedInstance()->mountCompatibleImage(m_device);
|
||||
if (!devImgSuccess) {
|
||||
warn("Failed to mount developer image on device. Cannot set location.");
|
||||
qDebug() << "Failed to mount developer image on device. Cannot set "
|
||||
"location.";
|
||||
GetMountedImageResult result =
|
||||
DevDiskManager::sharedInstance()->getMountedImage(
|
||||
m_device->udid.c_str());
|
||||
|
||||
if (!result.success) {
|
||||
QMessageBox::warning(this, "Failure", result.message.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
bool latOk, lonOk, altOk;
|
||||
if (result.success || result.sig.empty()) {
|
||||
bool devImgSuccess =
|
||||
DevDiskManager::sharedInstance()->mountCompatibleImage(m_device);
|
||||
if (!devImgSuccess) {
|
||||
QMessageBox::warning(this, "Failure",
|
||||
"Failed to mount developer image on device. "
|
||||
"Try with a different cable.");
|
||||
qDebug() << "Failed to mount developer image on device. Cannot set "
|
||||
"location.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool latOk, lonOk;
|
||||
double latitude = m_latitudeEdit->text().toDouble(&latOk);
|
||||
double longitude = m_longitudeEdit->text().toDouble(&lonOk);
|
||||
double altitude = m_altitudeEdit->text().toDouble(&altOk);
|
||||
|
||||
if (latOk && lonOk && altOk) {
|
||||
// Emit signal or perform action with the coordinates
|
||||
emit locationChanged(latitude, longitude, altitude);
|
||||
if (latOk && lonOk) {
|
||||
emit locationChanged(latitude, longitude);
|
||||
|
||||
// Update map one final time
|
||||
updateMapFromInputs();
|
||||
@@ -336,8 +312,11 @@ void VirtualLocation::onApplyClicked()
|
||||
warn("Failed to set location on device");
|
||||
qDebug() << "Failed to set location on device";
|
||||
} else {
|
||||
// todo: thread safe?
|
||||
QMessageBox::information(this, "Success",
|
||||
"Location applied successfully!");
|
||||
qDebug() << "Applied location settings:" << latitude << ","
|
||||
<< longitude << "," << altitude;
|
||||
<< longitude;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Invalid coordinate values";
|
||||
|
||||
@@ -17,7 +17,7 @@ public:
|
||||
QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void locationChanged(double latitude, double longitude, double altitude);
|
||||
void locationChanged(double latitude, double longitude);
|
||||
|
||||
public slots:
|
||||
void updateInputsFromMap(double latitude, double longitude);
|
||||
@@ -33,7 +33,6 @@ private:
|
||||
QQuickWidget *m_quickWidget;
|
||||
QLineEdit *m_latitudeEdit;
|
||||
QLineEdit *m_longitudeEdit;
|
||||
QLineEdit *m_altitudeEdit;
|
||||
QPushButton *m_applyButton;
|
||||
QTimer m_updateTimer;
|
||||
bool m_updatingFromInput = false;
|
||||
|
||||
Reference in New Issue
Block a user