feat: add DeviceImageWidget to display device mockups and wallpapers with current time

This commit is contained in:
uncor3
2025-10-13 00:43:33 +00:00
parent a749d3f193
commit 2ce27fc968
17 changed files with 364 additions and 35 deletions
+23
View File
@@ -12,5 +12,28 @@
<file>qml/MapView.qml</file>
<file>resources/dump.js</file>
<file>resources/iphone.png</file>
<file>resources/ios-wallpapers/iphone-ios4.png</file>
<file>resources/ios-wallpapers/iphone-ios5.png</file>
<file>resources/ios-wallpapers/iphone-ios6.png</file>
<file>resources/ios-wallpapers/iphone-ios7.png</file>
<file>resources/ios-wallpapers/iphone-ios8.png</file>
<file>resources/ios-wallpapers/iphone-ios9.png</file>
<file>resources/ios-wallpapers/iphone-ios10.png</file>
<file>resources/ios-wallpapers/iphone-ios11.png</file>
<file>resources/ios-wallpapers/iphone-ios12.png</file>
<file>resources/ios-wallpapers/iphone-ios13.png</file>
<file>resources/ios-wallpapers/iphone-ios14.png</file>
<file>resources/ios-wallpapers/iphone-ios15.png</file>
<file>resources/ios-wallpapers/iphone-ios16.png</file>
<file>resources/ios-wallpapers/iphone-ios17.png</file>
<file>resources/ios-wallpapers/iphone-ios18.png</file>
<file>resources/ios-wallpapers/iphone-ios26.png</file>
<file>resources/iphone-mockups/iphone-3.png</file>
<file>resources/iphone-mockups/iphone-4.png</file>
<file>resources/iphone-mockups/iphone-5.png</file>
<file>resources/iphone-mockups/iphone-6.png</file>
<file>resources/iphone-mockups/iphone-x.png</file>
<file>resources/iphone-mockups/iphone-15.png</file>
<file>resources/iphone-mockups/iphone-16.png</file>
</qresource>
</RCC>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

+297
View File
@@ -0,0 +1,297 @@
#include "deviceimagewidget.h"
#include <QDateTime>
#include <QDebug>
#include <QMap>
#include <QPainter>
#include <QVBoxLayout>
#include <libimobiledevice/libimobiledevice.h>
DeviceImageWidget::DeviceImageWidget(iDescriptorDevice *device, QWidget *parent)
: QWidget(parent), m_device(device)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_imageLabel = new ResponsiveQLabel(this);
m_imageLabel->setMinimumWidth(200);
m_imageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_imageLabel->setStyleSheet("background: transparent; border: none;");
layout->addWidget(m_imageLabel);
setupDeviceImage();
m_timeUpdateTimer = new QTimer(this);
connect(m_timeUpdateTimer, &QTimer::timeout, this,
&DeviceImageWidget::updateTime);
m_timeUpdateTimer->start(60000); // Update every minute
updateTime();
}
DeviceImageWidget::~DeviceImageWidget()
{
if (m_timeUpdateTimer) {
m_timeUpdateTimer->stop();
}
}
void DeviceImageWidget::setupDeviceImage()
{
m_mockupPath = getDeviceMockupPath();
m_wallpaperPath = getWallpaperPath();
qDebug() << "Using mockup:" << m_mockupPath;
qDebug() << "Using wallpaper:" << m_wallpaperPath;
}
QString DeviceImageWidget::getDeviceMockupPath() const
{
QString displayName =
QString::fromStdString(m_device->deviceInfo.productType);
QString mockupName = getMockupNameFromDisplayName(displayName);
return QString(":/resources/iphone-mockups/iphone-%1.png").arg(mockupName);
}
QString DeviceImageWidget::getWallpaperPath() const
{
int iosVersion = getIosVersionFromDevice();
// Map iOS version to available wallpapers
QString wallpaperVersion;
if (iosVersion >= 18) {
wallpaperVersion = "ios18";
} else if (iosVersion >= 17) {
wallpaperVersion = "ios17";
} else if (iosVersion >= 16) {
wallpaperVersion = "ios16";
} else if (iosVersion >= 15) {
wallpaperVersion = "ios15";
} else if (iosVersion >= 14) {
wallpaperVersion = "ios14";
} else if (iosVersion >= 13) {
wallpaperVersion = "ios13";
} else if (iosVersion >= 12) {
wallpaperVersion = "ios12";
} else if (iosVersion >= 11) {
wallpaperVersion = "ios11";
} else if (iosVersion >= 10) {
wallpaperVersion = "ios10";
} else if (iosVersion >= 9) {
wallpaperVersion = "ios9";
} else if (iosVersion >= 8) {
wallpaperVersion = "ios8";
} else if (iosVersion >= 7) {
wallpaperVersion = "ios7";
} else if (iosVersion >= 6) {
wallpaperVersion = "ios6";
} else if (iosVersion >= 5) {
wallpaperVersion = "ios5";
} else if (iosVersion >= 4) {
wallpaperVersion = "ios4";
} else {
// Unknown version, use ios26 as fallback
wallpaperVersion = "ios26";
}
return QString(":/resources/ios-wallpapers/iphone-%1.png")
.arg(wallpaperVersion);
}
QString DeviceImageWidget::getMockupNameFromDisplayName(
const QString &displayName) const
{
// Map device names to mockup files
if (displayName.contains("iPhone 16", Qt::CaseInsensitive)) {
return "16";
} else if (displayName.contains("iPhone 15", Qt::CaseInsensitive)) {
return "15";
} else if (displayName.contains("iPhone X", Qt::CaseInsensitive) ||
displayName.contains("iPhone 11", Qt::CaseInsensitive) ||
displayName.contains("iPhone 12", Qt::CaseInsensitive) ||
displayName.contains("iPhone 13", Qt::CaseInsensitive) ||
displayName.contains("iPhone 14", Qt::CaseInsensitive)) {
return "x";
} else if (displayName.contains("iPhone 6", Qt::CaseInsensitive) ||
displayName.contains("iPhone 7", Qt::CaseInsensitive) ||
displayName.contains("iPhone 8", Qt::CaseInsensitive)) {
return "6";
} else if (displayName.contains("iPhone 5", Qt::CaseInsensitive) ||
displayName.contains("iPhone SE", Qt::CaseInsensitive)) {
return "5";
} else if (displayName.contains("iPhone 4", Qt::CaseInsensitive)) {
return "4";
} else if (displayName.contains("iPhone 3", Qt::CaseInsensitive)) {
return "3";
} else {
// Unknown device, use iPhone X as default
return "x";
}
}
int DeviceImageWidget::getIosVersionFromDevice() const
{
unsigned int version = idevice_get_device_version(m_device->device);
if (version > 0) {
int majorVersion = (version >> 16) & 0xFF;
return majorVersion;
}
// Fallback: parse from productVersion string
QString versionString =
QString::fromStdString(m_device->deviceInfo.productVersion);
QStringList parts = versionString.split('.');
if (!parts.isEmpty()) {
bool ok;
int majorVersion = parts.first().toInt(&ok);
if (ok) {
return majorVersion;
}
}
// If all else fails, return unknown version (will use ios26 wallpaper)
return 0;
}
/*
this method is only here to calculate the screen area
so that wallpaper perfectly fits to the screen size
it's costy so if you want to add a new mock run
through this method qDebug the result and add it to createCompositeImage
example : screenRect = QRect(152, 79, 195, 296);
*/
QRect DeviceImageWidget::findScreenArea(const QPixmap &mockup) const
{
QImage image = mockup.toImage().convertToFormat(QImage::Format_ARGB32);
if (image.isNull()) {
return QRect();
}
int width = image.width();
int height = image.height();
int centerX = width / 2;
int centerY = height / 2;
if (qAlpha(image.pixel(centerX, centerY)) != 0) {
qWarning() << "Cannot find screen area: center pixel is not "
"transparent. Falling back to default.";
return QRect(width * 0.1, height * 0.1, width * 0.8, height * 0.8);
}
int left = centerX;
int right = centerX;
int top = centerY;
int bottom = centerY;
// Scan left from center
while (left > 0 && qAlpha(image.pixel(left, centerY)) == 0) {
left--;
}
// Scan right from center
while (right < width - 1 && qAlpha(image.pixel(right, centerY)) == 0) {
right++;
}
// Scan up from center
while (top > 0 && qAlpha(image.pixel(centerX, top)) == 0) {
top--;
}
// Scan down from center
while (bottom < height - 1 && qAlpha(image.pixel(centerX, bottom)) == 0) {
bottom++;
}
// Add a small margin to avoid drawing over the bezel anti-aliasing
int margin = 2;
return QRect(left + 1 + margin, top + 1 + margin,
right - left - 2 - (margin * 2),
bottom - top - 2 - (margin * 2));
}
QPixmap DeviceImageWidget::createCompositeImage() const
{
QPixmap mockup(m_mockupPath);
QPixmap wallpaper(m_wallpaperPath);
if (mockup.isNull()) {
qWarning() << "Failed to load mockup:" << m_mockupPath;
return QPixmap(":/resources/iphone.png"); // Fallback
}
if (wallpaper.isNull()) {
qWarning() << "Failed to load wallpaper:" << m_wallpaperPath;
return mockup; // Return just the mockup
}
// Start with the mockup as the base layer
QPixmap composite = mockup.copy();
QPainter painter(&composite);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
// Use pre-calculated screen areas for optimal performance
QRect screenRect;
QString mockupName = getMockupNameFromDisplayName(
QString::fromStdString(m_device->deviceInfo.productType));
if (mockupName == "3") {
screenRect = QRect(152, 79, 195, 296);
} else if (mockupName == "4") {
screenRect = QRect(421, 188, 366, 534);
} else if (mockupName == "5") {
screenRect = QRect(34, 113, 290, 523);
} else if (mockupName == "6") {
screenRect = QRect(75, 355, 1265, 2256);
} else if (mockupName == "x") {
screenRect = QRect(252, 436, 2375, 4989);
} else if (mockupName == "15") {
screenRect = QRect(22, 56, 323, 674);
} else if (mockupName == "16") {
screenRect = QRect(24, 61, 319, 668);
} else {
// Fallback for unknown devices
screenRect = QRect(mockup.width() * 0.12, mockup.height() * 0.08,
mockup.width() * 0.76, mockup.height() * 0.84);
}
// Draw wallpaper BEHIND the mockup (into the screen area)
QPixmap scaledWallpaper = wallpaper.scaled(
screenRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
painter.drawPixmap(screenRect, scaledWallpaper);
// Draw current time in the center of the screen
QString currentTime = QDateTime::currentDateTime().toString("hh:mm");
// Setup text rendering with better font sizing
QFont timeFont;
timeFont.setFamily("SF Pro Display, Helvetica, Arial");
// Scale font size based on screen dimensions
int fontSize = screenRect.width() / 5;
timeFont.setPointSize(fontSize);
timeFont.setWeight(QFont::Light);
painter.setFont(timeFont);
// Draw text shadow for better readability
painter.setPen(QColor(0, 0, 0, 150));
painter.drawText(screenRect.adjusted(2, 2, 2, 2), Qt::AlignCenter,
currentTime);
// Draw main text - perfectly centered in the screen area
painter.setPen(QColor(255, 255, 255, 255));
painter.drawText(screenRect, Qt::AlignCenter, currentTime);
painter.end();
return composite;
}
void DeviceImageWidget::updateTime()
{
QPixmap composite = createCompositeImage();
m_imageLabel->setPixmap(composite);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef DEVICEIMAGEWIDGET_H
#define DEVICEIMAGEWIDGET_H
#include "iDescriptor.h"
#include "responsiveqlabel.h"
#include <QTimer>
#include <QWidget>
class DeviceImageWidget : public QWidget
{
Q_OBJECT
public:
explicit DeviceImageWidget(iDescriptorDevice *device,
QWidget *parent = nullptr);
~DeviceImageWidget();
private slots:
void updateTime();
private:
void setupDeviceImage();
QString getDeviceMockupPath() const;
QString getWallpaperPath() const;
QString getMockupNameFromDisplayName(const QString &displayName) const;
int getIosVersionFromDevice() const;
QPixmap createCompositeImage() const;
QRect findScreenArea(const QPixmap &mockup) const;
iDescriptorDevice *m_device;
ResponsiveQLabel *m_imageLabel;
QTimer *m_timeUpdateTimer;
QString m_mockupPath;
QString m_wallpaperPath;
};
#endif // DEVICEIMAGEWIDGET_H
+4 -30
View File
@@ -35,16 +35,13 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
// 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 image label
m_deviceImageLabel = new ResponsiveQLabel(this);
m_deviceImageLabel->setPixmap(QPixmap(":/resources/iphone.png"));
m_deviceImageLabel->setMinimumWidth(200);
m_deviceImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_deviceImageLabel->setStyleSheet("background: transparent; border: none;");
// Create responsive device image widget
m_deviceImageWidget = new DeviceImageWidget(device, this);
// Actions group box
QWidget *actionsWidget = new QWidget();
@@ -80,7 +77,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent)
actionsLayout->addWidget(recoveryBtn);
leftLayout->addStretch();
leftLayout->addWidget(m_deviceImageLabel);
leftLayout->addWidget(m_deviceImageWidget);
leftLayout->addWidget(actionsWidget, 0, Qt::AlignCenter);
leftLayout->addStretch();
@@ -329,29 +326,6 @@ void DeviceInfoWidget::onBatteryMoreClicked()
msgBox.exec();
}
QPixmap DeviceInfoWidget::getDeviceIcon(const std::string &productType)
{
// Create a simple colored icon based on device type
QPixmap icon(16, 16);
icon.fill(Qt::transparent);
QPainter painter(&icon);
painter.setRenderHint(QPainter::Antialiasing);
if (productType.find("iPhone") != std::string::npos) {
painter.setBrush(QColor(0, 122, 255)); // iOS blue
painter.drawEllipse(2, 2, 12, 12);
} else if (productType.find("iPad") != std::string::npos) {
painter.setBrush(QColor(255, 149, 0)); // Orange
painter.drawRect(2, 2, 12, 12);
} else {
painter.setBrush(QColor(128, 128, 128)); // Gray for unknown
painter.drawEllipse(2, 2, 12, 12);
}
return icon;
}
void DeviceInfoWidget::updateBatteryInfo()
{
qDebug() << "Updating battery info...";
+2 -3
View File
@@ -1,8 +1,8 @@
#ifndef DEVICEINFOWIDGET_H
#define DEVICEINFOWIDGET_H
#include "batterywidget.h"
#include "deviceimagewidget.h"
#include "iDescriptor.h"
#include "responsiveqlabel.h"
#include <QLabel>
#include <QTimer>
#include <QWidget>
@@ -18,7 +18,6 @@ private slots:
void onBatteryMoreClicked();
private:
QPixmap getDeviceIcon(const std::string &productType);
iDescriptorDevice *m_device;
QTimer *m_updateTimer;
void updateBatteryInfo();
@@ -28,7 +27,7 @@ private:
BatteryWidget *m_batteryWidget;
QLabel *m_lightningIconLabel;
ResponsiveQLabel *m_deviceImageLabel = nullptr;
DeviceImageWidget *m_deviceImageWidget;
};
#endif // DEVICEINFOWIDGET_H
-2
View File
@@ -22,8 +22,6 @@ public:
static void shutdownDevice(iDescriptorDevice *device);
static void _enterRecoveryMode(iDescriptorDevice *device);
private slots:
void onDeviceAdded();
void onDeviceRemoved();
void onDeviceSelectionChanged();
void onToolboxClicked(iDescriptorTool tool);