mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
418 lines
16 KiB
C++
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();
|
|
}
|
|
} |