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:
uncor3
2025-10-06 15:30:47 -07:00
parent 92847227be
commit b15b205f52
34 changed files with 851 additions and 1113 deletions
+1
View File
@@ -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>
+5 -15
View 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,
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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
-7
View File
@@ -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
View File
@@ -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);
+5 -3
View File
@@ -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
+4 -23
View File
@@ -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;}
+29 -33
View File
@@ -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
View File
@@ -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);
}
+4 -1
View File
@@ -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
View File
@@ -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"};
}
+3 -1
View File
@@ -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
+3 -2
View File
@@ -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 =
+59 -8
View File
@@ -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 &section)
{
+6 -9
View File
@@ -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 &section);
@@ -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;
+9 -40
View File
@@ -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); }
+1 -5
View File
@@ -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
View File
@@ -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);
+12
View File
@@ -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
+8 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
+12 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+18 -45
View File
@@ -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)
-92
View File
@@ -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
View File
@@ -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
View File
@@ -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";
+1 -2
View File
@@ -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;