Files
iDescriptor/src/deviceimagewidget.cpp
T
2026-04-02 05:32:11 +00:00

341 lines
11 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 "deviceimagewidget.h"
#include "iDescriptor.h"
#include <QDateTime>
#include <QDebug>
#include <QMap>
#include <QPainter>
#include <QPainterPath>
#include <QVBoxLayout>
DeviceImageWidget::DeviceImageWidget(
const std::shared_ptr<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;");
m_mockupName = getMockupNameFromDisplayName(
QString::fromStdString(m_device->deviceInfo.productType));
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
{
if (m_mockupName == "iPad") {
return QString(":/resources/ipad-mockups/ipad.png");
}
if (m_mockupName == "unknown") {
return QString(":/resources/ipad-mockups/ipad.png");
}
return QString(":/resources/iphone-mockups/iphone-%1.png")
.arg(m_mockupName);
}
QString DeviceImageWidget::getWallpaperPath() const
{
int iosVersion = getIosVersionFromDevice();
qDebug() << "Detected iOS version:" << iosVersion;
// Map iOS version to available wallpapers
QString wallpaperVersion;
if (iosVersion <= 4) {
wallpaperVersion = "ios4";
} else if (iosVersion == 5) {
wallpaperVersion = "ios5";
} else if (iosVersion == 6) {
wallpaperVersion = "ios6";
} else if (iosVersion == 7) {
wallpaperVersion = "ios7";
} else if (iosVersion == 8) {
wallpaperVersion = "ios8";
} else if (iosVersion == 9) {
wallpaperVersion = "ios9";
} else if (iosVersion == 10) {
wallpaperVersion = "ios10";
} else if (iosVersion == 11) {
wallpaperVersion = "ios11";
} else if (iosVersion == 12) {
wallpaperVersion = "ios12";
} else if (iosVersion == 13) {
wallpaperVersion = "ios13";
} else if (iosVersion == 14) {
wallpaperVersion = "ios14";
} else if (iosVersion == 15) {
wallpaperVersion = "ios15";
} else if (iosVersion == 16) {
wallpaperVersion = "ios16";
} else if (iosVersion == 17) {
wallpaperVersion = "ios17";
} else if (iosVersion == 18) {
wallpaperVersion = "ios18";
} else if (iosVersion == 26) {
wallpaperVersion = "ios26";
} else {
// For future versions, use the latest available wallpaper
wallpaperVersion = "ios26";
}
return QString(":/resources/ios-wallpapers/iphone-%1.png")
.arg(wallpaperVersion);
}
QString DeviceImageWidget::getMockupNameFromDisplayName(
const QString &displayName) const
{
if (displayName.contains("iPhone 16", Qt::CaseInsensitive) ||
displayName.contains("iPhone 17", Qt::CaseInsensitive)) {
return "16";
} else if (displayName.contains("iPhone 15", Qt::CaseInsensitive) ||
displayName.contains("iPhone 14", 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)) {
return "x";
} else if (displayName.contains("iPhone 6", Qt::CaseInsensitive) ||
displayName.contains("iPhone 7", Qt::CaseInsensitive) ||
displayName.contains("iPhone 8", Qt::CaseInsensitive) ||
displayName.contains("iPhone SE 2nd", Qt::CaseInsensitive) ||
displayName.contains("iPhone SE 3rd", 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 if (displayName.contains("iPad", Qt::CaseInsensitive)) {
return "iPad";
} else {
return "unknown";
}
}
int DeviceImageWidget::getIosVersionFromDevice() const
{
unsigned int version = m_device->deviceInfo.parsedDeviceVersion.major;
if (version > 0) {
return version;
}
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 mockup 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++;
}
return QRect(left + 1, top + 1, right - left - 2, bottom - top - 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
}
// Create composite with mockup dimensions
QPixmap composite(mockup.size());
composite.fill(Qt::transparent);
QPainter painter(&composite);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
QRect screenRect;
bool useRoundedCorners = false;
int cornerRadius = 35;
bool isUnknown = (m_mockupName == "unknown");
if (m_mockupName == "3") {
screenRect = QRect(145, 72, 209, 310);
} else if (m_mockupName == "4") {
screenRect = QRect(414, 181, 380, 548);
} else if (m_mockupName == "5") {
screenRect = QRect(27, 106, 304, 537);
} else if (m_mockupName == "6") {
screenRect = QRect(68, 348, 1279, 2270);
} else if (m_mockupName == "x") {
screenRect = QRect(245, 200, 2389, 5303);
useRoundedCorners = true;
} else if (m_mockupName == "15") {
screenRect = QRect(15, 10, 337, 730);
useRoundedCorners = true;
} else if (m_mockupName == "16") {
screenRect = QRect(17, 16, 333, 730);
useRoundedCorners = true;
} else if (m_mockupName == "iPad") {
screenRect = QRect(30, 30, 480, 690);
} else if (m_mockupName == "unknown") {
screenRect = QRect(33, 36, 471, 680);
} else {
screenRect = QRect(mockup.width() * 0.12, mockup.height() * 0.08,
mockup.width() * 0.76, mockup.height() * 0.84);
}
QPixmap scaledWallpaper = wallpaper.scaled(
screenRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
// Create a clipping path with rounded corners
if (useRoundedCorners) {
QPainterPath clipPath;
clipPath.addRoundedRect(screenRect, cornerRadius, cornerRadius);
painter.setClipPath(clipPath);
}
painter.drawPixmap(screenRect, scaledWallpaper);
if (useRoundedCorners) {
painter.setClipping(false);
}
painter.drawPixmap(0, 0, mockup);
// Draw question mark for unknown devices
if (isUnknown) {
QFont questionFont;
questionFont.setFamily("SF Pro Display, Helvetica, Arial");
int questionSize = screenRect.width() / 3;
questionFont.setPointSize(questionSize);
questionFont.setWeight(QFont::Bold);
painter.setFont(questionFont);
// Question mark shadow
painter.setPen(QColor(0, 0, 0, 150));
painter.drawText(screenRect.adjusted(3, 3, 3, 3), Qt::AlignCenter, "?");
// Question mark main
painter.setPen(QColor(255, 255, 255, 255));
painter.drawText(screenRect, Qt::AlignCenter, "?");
}
QString currentTime = QDateTime::currentDateTime().toString("hh:mm");
QFont timeFont;
#ifndef WIN32
timeFont.setFamily("SF Pro Display, Helvetica, Arial");
#else
timeFont.setFamily("Segoe UI");
#endif
int fontSize = screenRect.width() / 5;
timeFont.setPointSize(fontSize);
timeFont.setWeight(QFont::Light);
painter.setFont(timeFont);
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);
}