add DiskUsageWidget and enhance DeviceInfoWidget layout and functionality

This commit is contained in:
uncor3
2025-07-28 06:51:55 +00:00
parent 0d383e8535
commit 636a15355a
5 changed files with 533 additions and 58 deletions
+182 -55
View File
@@ -1,36 +1,103 @@
#include "deviceinfowidget.h"
#include "diskusagewidget.h"
#include "fileexplorerwidget.h"
#include "iDescriptor.h"
#include <QDebug>
#include <QGraphicsPixmapItem>
#include <QGraphicsView>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QList>
#include <QMessageBox>
#include <QPainter>
#include <QPair>
#include <QPixmap>
#include <QPushButton>
#include <QResizeEvent>
#include <QTabWidget>
#include <QVBoxLayout>
// 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<QPair<QString, QWidget *>> 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)
+3 -1
View File
@@ -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;
};
+258
View File
@@ -0,0 +1,258 @@
#include "diskusagewidget.h"
#include "iDescriptor.h"
#include <QDebug>
#include <QFutureWatcher>
#include <QPainter>
#include <QVariantMap>
#include <QtConcurrent/QtConcurrent>
#include <libimobiledevice/installation_proxy.h>
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/lockdown.h>
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<QVariantMap>(this);
connect(watcher, &QFutureWatcher<QVariantMap>::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<QVariantMap> 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);
}
+36
View File
@@ -0,0 +1,36 @@
#ifndef DISKUSAGEWIDGET_H
#define DISKUSAGEWIDGET_H
#include "iDescriptor.h"
#include <QWidget>
#include <cstdint>
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
+54 -2
View File
@@ -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."
#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; }
};