Files
iDescriptor/src/deviceinfowidget.cpp
T
2025-12-08 21:21:45 +00:00

418 lines
16 KiB
C++

/*
* iDescriptor: A free and open-source idevice management tool.
*
* Copyright (C) 2025 Uncore <https://github.com/uncor3>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "deviceinfowidget.h"
#include "batterywidget.h"
#include "diskusagewidget.h"
#include "fileexplorerwidget.h"
#include "iDescriptor-ui.h"
#include "iDescriptor.h"
#include "infolabel.h"
#include "privateinfolabel.h"
#include "toolboxwidget.h"
#include <QApplication>
#include <QDebug>
#include <QGraphicsDropShadowEffect>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QList>
#include <QMessageBox>
#include <QPainter>
#include <QPair>
#include <QPixmap>
#include <QPushButton>
#include <QResizeEvent>
#include <QTabWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QtCore>
DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
: QWidget(parent), m_device(device)
{
// Main layout with horizontal orientation
QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 10, 0);
mainLayout->setSpacing(1);
// Left side container for image and actions
QWidget *leftContainer = new QWidget();
// leftContainer->setStyleSheet("margin-left: 100px");
QVBoxLayout *leftLayout = new QVBoxLayout(leftContainer);
leftLayout->setContentsMargins(0, 0, 0, 0);
leftLayout->setSpacing(1);
// Create responsive device image widget
m_deviceImageWidget = new DeviceImageWidget(device, this);
// Actions group box
QWidget *actionsWidget = new QWidget();
actionsWidget->setObjectName("actionsWidget");
actionsWidget->setFixedHeight(40);
actionsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
actionsWidget->setStyleSheet(
"QWidget#actionsWidget { background: transparent; border: none; }");
QHBoxLayout *actionsLayout = new QHBoxLayout(actionsWidget);
actionsLayout->setContentsMargins(1, 1, 1, 1);
actionsLayout->setSpacing(10);
ZIconWidget *shutdownBtn = new ZIconWidget(
QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown",
1.0, this);
connect(shutdownBtn, &ZIconWidget::clicked, this,
[device]() { ToolboxWidget::shutdownDevice(device); });
ZIconWidget *restartBtn =
new ZIconWidget(QIcon(":/resources/icons/IcTwotoneRestartAlt.png"),
"Restart", 1.0, this);
connect(restartBtn, &ZIconWidget::clicked, this,
[device]() { ToolboxWidget::restartDevice(device); });
ZIconWidget *recoveryBtn =
new ZIconWidget(QIcon(":/resources/icons/HugeiconsWrench01.png"),
"Recovery", 1.0, this);
connect(recoveryBtn, &ZIconWidget::clicked, this,
[device]() { ToolboxWidget::_enterRecoveryMode(device); });
actionsLayout->addWidget(shutdownBtn);
actionsLayout->addWidget(restartBtn);
actionsLayout->addWidget(recoveryBtn);
leftLayout->addStretch();
leftLayout->addWidget(m_deviceImageWidget);
leftLayout->addWidget(actionsWidget, 0, Qt::AlignCenter);
leftLayout->addStretch();
// Add stretches around leftContainer to center it horizontally
mainLayout->addStretch();
mainLayout->addWidget(leftContainer);
mainLayout->addStretch();
// Right side: Info Table
QWidget *infoContainer = new QWidget();
// 2. Change the horizontal size policy from Expanding to Preferred
infoContainer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QVBoxLayout *infoLayout = new QVBoxLayout(infoContainer);
// Header
QGroupBox *headerWidget = new QGroupBox();
QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget);
headerLayout->setContentsMargins(5, 5, 5, 5);
headerLayout->setSpacing(15);
QLabel *devProductType =
new QLabel(QString::fromStdString(device->deviceInfo.productType));
devProductType->setToolTip(
QString::fromStdString(device->deviceInfo.marketingName));
devProductType->setStyleSheet("font-size: 1rem; font-weight: bold;");
QLabel *diskCapacityLabel = new QLabel(
QString::number(device->deviceInfo.diskInfo.totalDiskCapacity /
(1000 * 1000 * 1000)) +
" GB");
diskCapacityLabel->setSizePolicy(QSizePolicy::Maximum,
QSizePolicy::Preferred);
diskCapacityLabel->setAttribute(Qt::WA_StyledBackground, true);
// background-color: rgba(0, 255, 30, 0.5);
diskCapacityLabel->setStyleSheet(QString("background-color: %1;"
"padding: 2px 4px;"
"color : white;"
"border-radius: 13px;")
.arg(COLOR_ACCENT_BLUE.name()));
m_chargingStatusLabel =
new QLabel(device->deviceInfo.batteryInfo.isCharging ? "Charging"
: "Not Charging");
m_chargingStatusLabel->setStyleSheet(
device->deviceInfo.batteryInfo.isCharging
? QString("color: %1;").arg(COLOR_GREEN.name())
: QString("color: %1;")
.arg(qApp->palette().color(QPalette::WindowText).name()));
// Create the layout without a parent widget
QHBoxLayout *chargingLayout = new QHBoxLayout();
chargingLayout->setContentsMargins(0, 0, 0, 0);
chargingLayout->setSpacing(5);
// Create icon label
m_lightningIconLabel =
new ZIconLabel(QIcon(":/resources/icons/MdiLightningBolt.png"),
" Charging", 1.0, this);
m_batteryWidget = new BatteryWidget(
qBound<int>(1, device->deviceInfo.batteryInfo.currentBatteryLevel, 100),
device->deviceInfo.batteryInfo.isCharging, this);
// Add the widgets to the new layout
chargingLayout->addWidget(m_chargingStatusLabel);
chargingLayout->addWidget(m_lightningIconLabel);
chargingLayout->addWidget(m_batteryWidget);
m_chargingWattsWithCableTypeLabel = new QLabel(
QString::number(device->deviceInfo.batteryInfo.watts) + "W" + "/" +
(device->deviceInfo.batteryInfo.usbConnectionType ==
BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C"));
headerLayout->addWidget(devProductType);
headerLayout->addWidget(diskCapacityLabel);
headerLayout->addStretch(); // Push items to the left
headerLayout->addLayout(chargingLayout);
headerLayout->addWidget(m_chargingWattsWithCableTypeLabel);
infoLayout->addWidget(headerWidget);
// add spacer
infoLayout->addSpacerItem(
new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding));
// Add maximum stretch between header and grid
infoLayout->addStretch();
QGroupBox *gridContainer = new QGroupBox("Device Information");
gridContainer->setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Preferred);
QGridLayout *gridLayout = new QGridLayout(); // Set layout on gridWidget
gridLayout->setSpacing(8);
gridLayout->setColumnStretch(1, 1); // Allow value column to stretch
gridLayout->setColumnStretch(
3, 1); // Allow value column for right side to stretch
gridLayout->setContentsMargins(17, 17, 17, 17);
gridContainer->setLayout(gridLayout);
QList<QPair<QString, QWidget *>> infoItems;
auto createValueLabel = [](const QString &text) {
return new InfoLabel(text);
};
infoItems.append({"iOS Version:", createValueLabel(QString::fromStdString(
device->deviceInfo.productVersion))});
infoItems.append({"Device Name:", createValueLabel(QString::fromStdString(
device->deviceInfo.deviceName))});
// Activation state label with color and tooltip
QLabel *activationLabel = new QLabel;
QString stateText;
QString tooltipText;
QColor color;
switch (device->deviceInfo.activationState) {
case DeviceInfo::ActivationState::Activated:
stateText = "Activated";
color = COLOR_GREEN;
tooltipText = "Device is activated and ready for use.";
break;
case DeviceInfo::ActivationState::FactoryActivated:
stateText = "Factory Activated";
color = COLOR_ORANGE;
tooltipText = "Activation is most likely bypassed.";
break;
default:
stateText = "Unactivated";
color = COLOR_RED;
tooltipText = "Device is not activated and requires setup.";
break;
}
activationLabel->setText(stateText);
activationLabel->setStyleSheet("color: " + color.name() + ";");
activationLabel->setToolTip(tooltipText);
infoItems.append({"Activation State:", activationLabel});
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({"Region:", createValueLabel(QString::fromStdString(
device->deviceInfo.region))});
infoItems.append(
{"Hardware Platform:", createValueLabel(QString::fromStdString(
device->deviceInfo.hardwarePlatform))});
infoItems.append(
{"Battery Cycle:", createValueLabel(QString::number(
m_device->deviceInfo.batteryInfo.cycleCount))});
infoItems.append(
{"Firmware Version:", createValueLabel(QString::fromStdString(
device->deviceInfo.firmwareVersion))});
// 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"))});
// Serial Number with privacy
if (!device->deviceInfo.serialNumber.empty()) {
infoItems.append(
{"Serial Number:",
new PrivateInfoLabel(
QString::fromStdString(device->deviceInfo.serialNumber),
this)});
}
// IMEI with privacy (Mobile Equipment Identifier)
if (!device->deviceInfo.mobileEquipmentIdentifier.empty()) {
infoItems.append(
{"IMEI:", new PrivateInfoLabel(
QString::fromStdString(
device->deviceInfo.mobileEquipmentIdentifier),
this)});
}
// 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->addWidget(gridContainer);
// infoLayout->addStretch(); // Pushes footer to the bottom
// Footer
QLabel *footerLabel =
new QLabel("UDID: " + QString::fromStdString(device->udid));
footerLabel->setToolTip("Unique Device Identifier");
footerLabel->setStyleSheet(
"font-size: 10px; color: #666; margin-top: 5px; ");
footerLabel->setWordWrap(true);
infoLayout->addWidget(footerLabel);
QVBoxLayout *rightSideLayout = new QVBoxLayout();
rightSideLayout->setSpacing(10);
rightSideLayout->addStretch();
rightSideLayout->addWidget(infoContainer);
rightSideLayout->addWidget(new DiskUsageWidget(device, this));
rightSideLayout->addStretch();
// TODO: layout shift cause ?
// rightSideLayout->setAlignment(Qt::AlignCenter);
mainLayout->addLayout(rightSideLayout);
mainLayout->addStretch();
m_updateTimer = new QTimer(this);
connect(m_updateTimer, &QTimer::timeout, this,
&DeviceInfoWidget::updateBatteryInfo);
m_updateTimer->start(30000); // Update every 30 seconds
}
DeviceInfoWidget::~DeviceInfoWidget() {}
void DeviceInfoWidget::onBatteryMoreClicked()
{
QMessageBox msgBox;
msgBox.setWindowTitle("Battery Details");
QString details =
"Battery Cycle Count: " +
QString::number(m_device->deviceInfo.batteryInfo.cycleCount) + "\n" +
"Battery Serial Number: " +
QString::fromStdString(m_device->deviceInfo.batteryInfo.serialNumber);
msgBox.setText(details);
msgBox.exec();
}
void DeviceInfoWidget::updateBatteryInfo()
{
qDebug() << "Updating battery info...";
plist_t diagnostics = nullptr;
get_battery_info(m_device->deviceInfo.rawProductType, m_device->device,
m_device->deviceInfo.is_iPhone, diagnostics);
if (!diagnostics) {
qDebug() << "Failed to get diagnostics plist.";
return;
}
/*DATA*/
DeviceInfo &d = m_device->deviceInfo;
qDebug() << "old device" << d.oldDevice;
PlistNavigator ioreg = PlistNavigator(diagnostics)["IORegistry"];
if (d.oldDevice)
parseOldDeviceBattery(ioreg, d);
else
parseDeviceBattery(ioreg, d);
/*UI*/
updateChargingStatusIcon();
m_chargingWattsWithCableTypeLabel->setText(
QString::number(d.batteryInfo.watts) + "W" + "/" +
(d.batteryInfo.usbConnectionType == BatteryInfo::ConnectionType::USB
? "USB"
: "USB-C"));
m_batteryWidget->updateContext(
d.batteryInfo.isCharging,
qBound<int>(1, d.batteryInfo.currentBatteryLevel, 100));
}
void DeviceInfoWidget::updateChargingStatusIcon()
{
if (m_device->deviceInfo.batteryInfo.isCharging) {
m_chargingStatusLabel->setText("Charging");
m_chargingStatusLabel->setStyleSheet(
QString("color: %1;").arg(COLOR_GREEN.name()));
m_lightningIconLabel->show();
} else {
m_chargingStatusLabel->setText("Not Charging");
m_chargingStatusLabel->setStyleSheet("");
m_lightningIconLabel->hide();
}
}