From 636a15355aeeb13d0060e0136c0e018f08008d96 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 28 Jul 2025 06:51:55 +0000 Subject: [PATCH] add DiskUsageWidget and enhance DeviceInfoWidget layout and functionality --- src/deviceinfowidget.cpp | 237 ++++++++++++++++++++++++++--------- src/deviceinfowidget.h | 4 +- src/diskusagewidget.cpp | 258 +++++++++++++++++++++++++++++++++++++++ src/diskusagewidget.h | 36 ++++++ src/iDescriptor.h | 56 ++++++++- 5 files changed, 533 insertions(+), 58 deletions(-) create mode 100644 src/diskusagewidget.cpp create mode 100644 src/diskusagewidget.h diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index c4ebb4e..30fce18 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -1,36 +1,103 @@ #include "deviceinfowidget.h" +#include "diskusagewidget.h" #include "fileexplorerwidget.h" #include "iDescriptor.h" #include +#include +#include +#include +#include #include +#include +#include #include +#include #include #include +#include #include #include +// A custom QGraphicsView that keeps the content fitted with aspect ratio on +// resize +class ResponsiveGraphicsView : public QGraphicsView +{ +public: + ResponsiveGraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr) + : QGraphicsView(scene, parent) + { + } + +protected: + void resizeEvent(QResizeEvent *event) override + { + if (scene() && !scene()->items().isEmpty()) { + fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); + } + QGraphicsView::resizeEvent(event); + } +}; + DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) : QWidget(parent), device(device) { - // Create main layout for this widget - QVBoxLayout *mainLayout = new QVBoxLayout(this); + // Main layout with horizontal orientation + QHBoxLayout *mainLayout = new QHBoxLayout(this); mainLayout->setContentsMargins(10, 10, 10, 10); mainLayout->setSpacing(10); - // Create device info section - QWidget *devWidget = new QWidget(); - QVBoxLayout *devLayout = new QVBoxLayout(devWidget); - devLayout->setContentsMargins(10, 10, 10, 10); - devLayout->setSpacing(10); + QGraphicsScene *scene = new QGraphicsScene(this); + QGraphicsPixmapItem *pixmapItem = + new QGraphicsPixmapItem(QPixmap(":/resources/iphone.png")); + scene->addItem(pixmapItem); + + QGraphicsView *graphicsView = new ResponsiveGraphicsView(scene, this); + graphicsView->setRenderHint(QPainter::Antialiasing); + graphicsView->setMinimumWidth(200); + graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); + graphicsView->setStyleSheet("background: transparent; border: none;"); + + mainLayout->addWidget(graphicsView, 1); // Stretch factor 1 + + // Right side: Info Table + QWidget *infoContainer = new QWidget(); + infoContainer->setObjectName("infoContainer"); + infoContainer->setStyleSheet("QWidget#infoContainer { " + " border: 1px solid #ccc; " + " border-radius: 6px; " + "}"); + infoContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + QVBoxLayout *infoLayout = new QVBoxLayout(infoContainer); + infoLayout->setContentsMargins(15, 15, 15, 15); + infoLayout->setSpacing(10); + + // Header + QLabel *headerLabel = new QLabel( + "Device: " + QString::fromStdString(device->deviceInfo.productType)); + headerLabel->setStyleSheet("font-size: 1rem; padding-bottom: 10px; " + "border-bottom: 1px solid #eee; " + "font-weight: bold;"); + infoLayout->addWidget(headerLabel); + + // Grid for device details + QGridLayout *gridLayout = new QGridLayout(); + gridLayout->setSpacing(8); + gridLayout->setColumnStretch(1, 1); // Allow value column to stretch + gridLayout->setColumnStretch( + 3, 1); // Allow value column for right side to stretch + + QList> infoItems; + + auto createValueLabel = [](const QString &text) { + return new QLabel(text); + }; + + infoItems.append({"iOS Version:", createValueLabel(QString::fromStdString( + device->deviceInfo.productVersion))}); + infoItems.append({"Device Name:", createValueLabel(QString::fromStdString( + device->deviceInfo.deviceName))}); - devLayout->addWidget(new QLabel( - "Device: " + QString::fromStdString(device->deviceInfo.productType))); - devLayout->addWidget( - new QLabel("iOS Version: " + - QString::fromStdString(device->deviceInfo.productVersion))); - devLayout->addWidget( - new QLabel("Device Name: " + - QString::fromStdString(device->deviceInfo.deviceName))); // Activation state label with color and tooltip QLabel *activationLabel = new QLabel; QString stateText; @@ -55,50 +122,110 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) break; } - activationLabel->setText("Activation State: " + stateText); + activationLabel->setText(stateText); activationLabel->setStyleSheet("color: " + color.name() + ";"); activationLabel->setToolTip(tooltipText); - devLayout->addWidget(activationLabel); - devLayout->addWidget( - new QLabel("Device Class: " + - QString::fromStdString(device->deviceInfo.deviceClass))); - devLayout->addWidget( - new QLabel("Device Color: " + - QString::fromStdString(device->deviceInfo.deviceColor))); - devLayout->addWidget(new QLabel( - "Jailbroken: " + - QString::fromStdString(device->deviceInfo.jailbroken ? "Yes" : "No"))); - devLayout->addWidget( - new QLabel("Model Number: " + - QString::fromStdString(device->deviceInfo.modelNumber))); - devLayout->addWidget( - new QLabel("CPU Architecture: " + - QString::fromStdString(device->deviceInfo.cpuArchitecture))); - devLayout->addWidget( - new QLabel("Build Version: " + - QString::fromStdString(device->deviceInfo.buildVersion))); - devLayout->addWidget( - new QLabel("Hardware Model: " + - QString::fromStdString(device->deviceInfo.hardwareModel))); - devLayout->addWidget(new QLabel( - "Hardware Platform: " + - QString::fromStdString(device->deviceInfo.hardwarePlatform))); - devLayout->addWidget( - new QLabel("Ethernet Address: " + - QString::fromStdString(device->deviceInfo.ethernetAddress))); - devLayout->addWidget(new QLabel( - "Bluetooth Address: " + - QString::fromStdString(device->deviceInfo.bluetoothAddress))); - devLayout->addWidget( - new QLabel("Firmware Version: " + - QString::fromStdString(device->deviceInfo.firmwareVersion))); + infoItems.append({"Activation State:", activationLabel}); - devWidget->setStyleSheet( - "QWidget { border: 1px solid #ccc; margin: 8px; border-radius: 6px; " - "background: #fff; color: #000; }"); + infoItems.append({"Device Class:", createValueLabel(QString::fromStdString( + device->deviceInfo.deviceClass))}); + infoItems.append({"Device Color:", createValueLabel(QString::fromStdString( + device->deviceInfo.deviceColor))}); + infoItems.append( + {"Jailbroken:", createValueLabel(QString::fromStdString( + device->deviceInfo.jailbroken ? "Yes" : "No"))}); + infoItems.append({"Model Number:", createValueLabel(QString::fromStdString( + device->deviceInfo.modelNumber))}); + infoItems.append( + {"CPU Architecture:", createValueLabel(QString::fromStdString( + device->deviceInfo.cpuArchitecture))}); + infoItems.append({"Build Version:", createValueLabel(QString::fromStdString( + device->deviceInfo.buildVersion))}); + infoItems.append( + {"Hardware Model:", createValueLabel(QString::fromStdString( + device->deviceInfo.hardwareModel))}); + infoItems.append( + {"Hardware Platform:", createValueLabel(QString::fromStdString( + device->deviceInfo.hardwarePlatform))}); + infoItems.append( + {"Ethernet Address:", createValueLabel(QString::fromStdString( + device->deviceInfo.ethernetAddress))}); + infoItems.append( + {"Bluetooth Address:", createValueLabel(QString::fromStdString( + device->deviceInfo.bluetoothAddress))}); + infoItems.append( + {"Firmware Version:", createValueLabel(QString::fromStdString( + device->deviceInfo.firmwareVersion))}); - // Add device info widget to main layout - mainLayout->addWidget(devWidget); + // Battery Info + QWidget *batteryWidget = new QWidget(); + QHBoxLayout *batteryLayout = new QHBoxLayout(batteryWidget); + batteryLayout->setContentsMargins(0, 0, 0, 0); + batteryLayout->setSpacing(5); + batteryLayout->addWidget(new QLabel(device->deviceInfo.batteryInfo.health)); + QPushButton *moreButton = new QPushButton("More"); + connect(moreButton, &QPushButton::clicked, this, + &DeviceInfoWidget::onBatteryMoreClicked); + batteryLayout->addWidget(moreButton); + batteryLayout->addStretch(); + infoItems.append({"Battery Health:", batteryWidget}); + + infoItems.append( + {"Production Device:", + createValueLabel(QString::fromStdString( + device->deviceInfo.productionDevice ? "Yes" : "No"))}); + + // Distribute items into the grid + int numRows = (infoItems.size() + 1) / 2; + for (int i = 0; i < numRows; ++i) { + // Left column item + QLabel *keyLabelLeft = new QLabel(infoItems[i].first); + keyLabelLeft->setStyleSheet("font-weight: bold;"); + gridLayout->addWidget(keyLabelLeft, i, 0); + gridLayout->addWidget(infoItems[i].second, i, 1); + + // Right column item + int rightIndex = i + numRows; + if (rightIndex < infoItems.size()) { + QLabel *keyLabelRight = new QLabel(infoItems[rightIndex].first); + keyLabelRight->setStyleSheet("font-weight: bold;"); + gridLayout->addWidget(keyLabelRight, i, 2); + gridLayout->addWidget(infoItems[rightIndex].second, i, 3); + } + } + + infoLayout->addLayout(gridLayout); + infoLayout->addStretch(); // Pushes footer to the bottom + + // Footer + QLabel *footerLabel = + new QLabel("UDID: " + QString::fromStdString(device->udid)); + footerLabel->setStyleSheet( + "font-size: 10px; color: #666; padding-top: 5px; " + "border-top: 1px solid #eee;"); + footerLabel->setWordWrap(true); + infoLayout->addWidget(footerLabel); + + // Create a vertical layout for the right side to stack info and disk usage + QVBoxLayout *rightSideLayout = new QVBoxLayout(); + rightSideLayout->setSpacing(10); + rightSideLayout->addWidget(infoContainer); + rightSideLayout->addWidget(new DiskUsageWidget(device, this)); + rightSideLayout->setAlignment(Qt::AlignCenter); + mainLayout->addLayout(rightSideLayout, 2); // Stretch factor 2 +} + +void DeviceInfoWidget::onBatteryMoreClicked() +{ + QMessageBox msgBox; + msgBox.setWindowTitle("Battery Details"); + QString details = + "Battery Cycle Count: " + + QString::number(device->deviceInfo.batteryInfo.cycleCount) + "\n" + + "Battery Serial Number: " + + QString::fromStdString(device->deviceInfo.batteryInfo.serialNumber); + msgBox.setText(details); + msgBox.exec(); } QPixmap DeviceInfoWidget::getDeviceIcon(const std::string &productType) diff --git a/src/deviceinfowidget.h b/src/deviceinfowidget.h index 8cb8d1d..f917e65 100644 --- a/src/deviceinfowidget.h +++ b/src/deviceinfowidget.h @@ -10,8 +10,10 @@ public: explicit DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent = nullptr); +private slots: + void onBatteryMoreClicked(); + private: - QWidget *deviceInfoWidget; QPixmap getDeviceIcon(const std::string &productType); iDescriptorDevice *device; }; diff --git a/src/diskusagewidget.cpp b/src/diskusagewidget.cpp new file mode 100644 index 0000000..e1c3069 --- /dev/null +++ b/src/diskusagewidget.cpp @@ -0,0 +1,258 @@ +#include "diskusagewidget.h" +#include "iDescriptor.h" +#include +#include +#include +#include +#include + +#include +#include +#include + +DiskUsageWidget::DiskUsageWidget(iDescriptorDevice *device, QWidget *parent) + : QWidget(parent), m_device(device), m_state(Loading), m_totalCapacity(0), + m_systemUsage(0), m_appsUsage(0), m_mediaUsage(0), m_othersUsage(0), + m_freeSpace(0) +{ + setMinimumHeight(80); + fetchData(); +} + +QSize DiskUsageWidget::sizeHint() const { return QSize(400, 80); } + +void DiskUsageWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + if (m_state == Loading) { + painter.setPen(Qt::black); + painter.drawText(rect(), Qt::AlignCenter, "Loading disk usage..."); + return; + } + + if (m_state == Error) { + painter.setPen(Qt::black); + painter.drawText(rect(), Qt::AlignCenter, "Error: " + m_errorMessage); + return; + } + + // Title + painter.setPen(Qt::black); + QFont titleFont = font(); + titleFont.setBold(true); + painter.setFont(titleFont); + QRectF titleRect(0, 5, width(), 20); + painter.drawText(titleRect, Qt::AlignHCenter | Qt::AlignTop, "Disk Usage"); + painter.setFont(font()); // Reset font + + if (m_totalCapacity == 0) { + painter.setPen(Qt::black); + painter.drawText(QRect(0, 30, width(), height() - 30), Qt::AlignCenter, + "No disk information available."); + return; + } + + painter.setPen(Qt::NoPen); + + const int barHeight = 20; + QRectF barRect(10, 30, width() - 20, barHeight); + + double scale = (double)barRect.width() / m_totalCapacity; + double currentX = barRect.left(); + + auto drawSegment = [&](uint64_t value, const QColor &color) { + if (value > 0) { + double segmentWidth = value * scale; + painter.fillRect( + QRectF(currentX, barRect.top(), segmentWidth, barRect.height()), + color); + currentX += segmentWidth; + } + }; + + const QColor systemColor("#E74C3C"); + const QColor appsColor("#3498DB"); + const QColor mediaColor("#2ECC71"); + const QColor othersColor("#F39C12"); + const QColor freeColor("#BDC3C7"); + + // System + drawSegment(m_systemUsage, systemColor); + // Apps + drawSegment(m_appsUsage, appsColor); + // Media + drawSegment(m_mediaUsage, mediaColor); + // Others + drawSegment(m_othersUsage, othersColor); + // Free + drawSegment(m_freeSpace, freeColor); + + // Legend + painter.setPen(Qt::black); + qreal legendY = barRect.bottom() + 15; + const int legendBoxSize = 10; + const int legendSpacing = 5; + qreal currentLegendX = barRect.left(); + + auto drawLegendItem = [&](const QColor &color, const QString &text) { + painter.fillRect( + QRectF(currentLegendX, legendY, legendBoxSize, legendBoxSize), + color); + currentLegendX += legendBoxSize + legendSpacing; + + QFontMetrics fm(font()); + QRect textRect = fm.boundingRect(text); + painter.drawText(QPointF(currentLegendX, legendY + legendBoxSize), + text); + currentLegendX += textRect.width() + legendSpacing * 2; + }; + + drawLegendItem(systemColor, "System"); + drawLegendItem(appsColor, "Apps"); + drawLegendItem(mediaColor, "Media"); + drawLegendItem(othersColor, "Others"); + drawLegendItem(freeColor, "Free"); +} + +void DiskUsageWidget::fetchData() +{ + auto *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, + [this, watcher]() { + QVariantMap result = watcher->result(); + if (result.contains("error")) { + m_state = Error; + m_errorMessage = result["error"].toString(); + } else { + m_totalCapacity = result["totalCapacity"].toULongLong(); + m_systemUsage = result["systemUsage"].toULongLong(); + m_appsUsage = result["appsUsage"].toULongLong(); + m_mediaUsage = result["mediaUsage"].toULongLong(); + m_freeSpace = result["freeSpace"].toULongLong(); + + uint64_t usedKnown = + m_systemUsage + m_appsUsage + m_mediaUsage; + if (m_totalCapacity > (m_freeSpace + usedKnown)) { + m_othersUsage = + m_totalCapacity - m_freeSpace - usedKnown; + } else { + m_othersUsage = 0; + } + + m_state = Ready; + } + update(); // Trigger repaint + watcher->deleteLater(); + }); + + QFuture future = QtConcurrent::run([this]() { + QVariantMap result; + if (!m_device || !m_device->device) { + result["error"] = "Invalid device."; + return result; + } + + result["totalCapacity"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDiskCapacity); + result["freeSpace"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalDataAvailable); + result["systemUsage"] = QVariant::fromValue( + m_device->deviceInfo.diskInfo.totalSystemCapacity); + + // Apps usage + uint64_t totalAppsSpace = 0; + instproxy_client_t instproxy = nullptr; + lockdownd_client_t lockdownClient = nullptr; + + if (lockdownd_client_new_with_handshake(m_device->device, + &lockdownClient, APP_LABEL) != + LOCKDOWN_E_SUCCESS) { + result["error"] = "Could not connect to lockdown service."; + return result; + } + lockdownd_service_descriptor_t lockdowndService = nullptr; + if (lockdownd_start_service(lockdownClient, + "com.apple.mobile.installation_proxy", + &lockdowndService) != LOCKDOWN_E_SUCCESS) { + } + + if (instproxy_client_new(m_device->device, lockdowndService, + &instproxy) != INSTPROXY_E_SUCCESS) { + lockdownd_service_descriptor_free(lockdowndService); + } + + if (instproxy_client_new(m_device->device, lockdowndService, + &instproxy) != INSTPROXY_E_SUCCESS) { + result["error"] = "Could not connect to installation proxy."; + return result; + } + + lockdownd_service_descriptor_free(lockdowndService); + + plist_t client_opts = instproxy_client_options_new(); + plist_dict_set_item(client_opts, "ApplicationType", + plist_new_string("User")); + + plist_t return_attrs = plist_new_array(); + plist_array_append_item(return_attrs, + plist_new_string("StaticDiskUsage")); + plist_array_append_item(return_attrs, + plist_new_string("DynamicDiskUsage")); + plist_dict_set_item(client_opts, "ReturnAttributes", return_attrs); + + plist_t apps = nullptr; + if (instproxy_browse(instproxy, client_opts, &apps) == + INSTPROXY_E_SUCCESS && + apps) { + if (plist_get_node_type(apps) == PLIST_ARRAY) { + for (uint32_t i = 0; i < plist_array_get_size(apps); i++) { + plist_t app_info = plist_array_get_item(apps, i); + if (!app_info) + continue; + + plist_t static_usage = + plist_dict_get_item(app_info, "StaticDiskUsage"); + if (static_usage && + plist_get_node_type(static_usage) == PLIST_UINT) { + uint64_t static_size = 0; + plist_get_uint_val(static_usage, &static_size); + totalAppsSpace += static_size; + } + + plist_t dynamic_usage = + plist_dict_get_item(app_info, "DynamicDiskUsage"); + if (dynamic_usage && + plist_get_node_type(dynamic_usage) == PLIST_UINT) { + uint64_t dynamic_size = 0; + plist_get_uint_val(dynamic_usage, &dynamic_size); + totalAppsSpace += dynamic_size; + } + } + } + plist_free(apps); + } + result["appsUsage"] = QVariant::fromValue(totalAppsSpace); + plist_free(client_opts); + instproxy_client_free(instproxy); + + // Media usage + uint64_t mediaSpace = 0; + plist_t node = nullptr; + if (lockdownd_get_value(lockdownClient, "com.apple.mobile.iTunes", + nullptr, &node) == LOCKDOWN_E_SUCCESS && + node) { + plist_t mediaNode = plist_dict_get_item(node, "MediaLibrarySize"); + if (mediaNode && plist_get_node_type(mediaNode) == PLIST_UINT) { + plist_get_uint_val(mediaNode, &mediaSpace); + } + plist_free(node); + } + result["mediaUsage"] = QVariant::fromValue(mediaSpace); + + return result; + }); + watcher->setFuture(future); +} diff --git a/src/diskusagewidget.h b/src/diskusagewidget.h new file mode 100644 index 0000000..cf370ff --- /dev/null +++ b/src/diskusagewidget.h @@ -0,0 +1,36 @@ +#ifndef DISKUSAGEWIDGET_H +#define DISKUSAGEWIDGET_H +#include "iDescriptor.h" + +#include +#include + +class DiskUsageWidget : public QWidget +{ + Q_OBJECT +public: + explicit DiskUsageWidget(iDescriptorDevice *device, + QWidget *parent = nullptr); + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void fetchData(); + + enum State { Loading, Ready, Error }; + + iDescriptorDevice *m_device; + State m_state; + QString m_errorMessage; + + uint64_t m_totalCapacity; + uint64_t m_systemUsage; + uint64_t m_appsUsage; + uint64_t m_mediaUsage; + uint64_t m_othersUsage; + uint64_t m_freeSpace; +}; + +#endif // DISKUSAGEWIDGET_H diff --git a/src/iDescriptor.h b/src/iDescriptor.h index d94aeb0..3bfa55e 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -12,8 +12,36 @@ #define RECOVERY_CLIENT_CONNECTION_TRIES 3 #define APPLE_VENDOR_ID 0x05ac +struct BatteryInfo { + QString health; + uint64_t cycleCount; + // uint64_t designCapacity; + // uint64_t maxCapacity; + // uint64_t fullChargeCapacity; + std::string serialNumber; +}; + +//! IOS 12 +/* { +"AmountDataAvailable": 6663077888, +"AmountDataReserved": 209715200, +"AmountRestoreAvailable": 11524079616, +"CalculateDiskUsage": "OkilyDokily", +"NANDInfo": <01000000 01000000 01000000 00000080 ... 00 00000000 000000>, +"TotalDataAvailable": 6872793088, +"TotalDataCapacity": 11306721280, +"TotalDiskCapacity": 16000000000, +"TotalSystemAvailable": 0, +"TotalSystemCapacity": 4693204992 +}*/ +struct DiskInfo { + uint64_t totalDiskCapacity; + uint64_t totalDataCapacity; + uint64_t totalSystemCapacity; + uint64_t totalDataAvailable; +}; + struct DeviceInfo { - std::string _0; enum class ActivationState { Activated, FactoryActivated, @@ -79,6 +107,9 @@ struct DeviceInfo { bool systemAudioVolumeSaved; bool autoBoot; int backlightLevel; + bool productionDevice; + BatteryInfo batteryInfo; + DiskInfo diskInfo; }; struct iDescriptorDevice { @@ -165,4 +196,25 @@ enum class AddType { Regular, Pairing }; #define APP_LABEL "iDescriptor" #define APP_VERSION "0.0.1" -#define APP_COPYRIGHT "© 2023 Uncore. All rights reserved." \ No newline at end of file +#define APP_COPYRIGHT "© 2023 Uncore. All rights reserved." + +class PlistNavigator +{ +private: + plist_t current_node; + +public: + PlistNavigator(plist_t node) : current_node(node) {} + + PlistNavigator operator[](const char *key) + { + if (!current_node || plist_get_node_type(current_node) != PLIST_DICT) { + return PlistNavigator(nullptr); + } + plist_t next = plist_dict_get_item(current_node, key); + return PlistNavigator(next); + } + + operator plist_t() const { return current_node; } + bool valid() const { return current_node != nullptr; } +};