From b3875fe16fa455e15184f14cc6b76edaa5517ad7 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 30 Nov 2025 14:23:31 +0000 Subject: [PATCH 01/15] show QInputDialog for root password --- src/sshterminalwidget.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/sshterminalwidget.cpp b/src/sshterminalwidget.cpp index f570916..7c043d1 100644 --- a/src/sshterminalwidget.cpp +++ b/src/sshterminalwidget.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -339,6 +340,22 @@ void SSHTerminalWidget::startSSH(const QString &host, uint16_t port) { qDebug() << "Starting SSH to" << host << "on port" << port; + QString defaultPassword = + SettingsManager::sharedInstance()->defaultJailbrokenRootPassword(); + QByteArray passwordBytes = defaultPassword.toUtf8(); + + bool ok; + QString code = + QInputDialog::getText(nullptr, "SSH Root Password", + "Enter the root password: \n(leave empty if you " + "want to use the default)", + QLineEdit::Normal, QString(), &ok); + + if (!ok) { + showError("Root password input canceled"); + return; + } + if (m_sshConnected) return; @@ -385,9 +402,6 @@ void SSHTerminalWidget::startSSH(const QString &host, uint16_t port) qDebug() << "SSH connected successfully, attempting authentication..."; - QString defaultPassword = - SettingsManager::sharedInstance()->defaultJailbrokenRootPassword(); - QByteArray passwordBytes = defaultPassword.toUtf8(); rc = ssh_userauth_password(m_sshSession, nullptr, passwordBytes.constData()); if (rc != SSH_AUTH_SUCCESS) { From d5a461adafd1cf97171f3759199007dc86786eb9 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 30 Nov 2025 14:26:27 +0000 Subject: [PATCH 02/15] set modificationTime and birthTime on exported files --- src/exportmanager.cpp | 75 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index c37ff01..469be90 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -208,23 +208,60 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, QString outputPath = QDir(destinationDir).filePath(item.suggestedFileName); outputPath = generateUniqueOutputPath(outputPath); result.outputFilePath = outputPath; - + QDateTime modificationTime; + QDateTime birthTime; // Get file size first - char **info = nullptr; - afc_error_t infoResult = ServiceManager::safeAfcGetFileInfo( - device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); + // Example { + // "st_size": 64523, + // "st_blocks": 128, + // "st_nlink": 1, + // "st_ifmt": "S_IFREG", + // "st_mtime": 1754987735634348907, + // "st_birthtime": 1754987735633715011 + // } - qint64 totalFileSize = 0; - if (infoResult == AFC_E_SUCCESS && info) { - for (int i = 0; info[i]; i += 2) { - if (strcmp(info[i], "st_size") == 0) { - totalFileSize = QString::fromUtf8(info[i + 1]).toLongLong(); - break; - } - } - afc_dictionary_free(info); + plist_t info = nullptr; + afc_error_t infoResult = ServiceManager::safeAfcGetFileInfoPlist( + device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); + int totalFileSize = 0; + if (infoResult != AFC_E_SUCCESS || !info) { + qDebug() << "File info retrieval failed for" << item.sourcePathOnDevice; + return result; } + PlistNavigator fileInfo = PlistNavigator(info); + + bool valid = fileInfo["st_size"].valid(); + if (!valid) { + qDebug() << "File size info not valid for" << item.sourcePathOnDevice; + return result; + } + + // make sure st_size is a float + totalFileSize = fileInfo["st_size"].getUInt(); + + valid = fileInfo["st_mtime"].valid(); + if (!valid) { + qDebug() << "File modification time info not valid for" + << item.sourcePathOnDevice; + return result; + } + + uint64_t modTimeNs = fileInfo["st_mtime"].getUInt(); + // The timestamp from the device is in nanoseconds, convert to seconds + modificationTime = QDateTime::fromSecsSinceEpoch(modTimeNs / 1000000000); + + valid = fileInfo["st_birthtime"].valid(); + if (!valid) { + qDebug() << "File birth time info not valid for" + << item.sourcePathOnDevice; + return result; + } + uint64_t birthTimeNs = fileInfo["st_birthtime"].getUInt(); + birthTime = QDateTime::fromSecsSinceEpoch(birthTimeNs / 1000000000); + + plist_free(info); + // Open file on device uint64_t handle = 0; afc_error_t openResult = ServiceManager::safeAfcFileOpen( @@ -293,6 +330,18 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, // Clean up outputFile.close(); + + // Set file times after closing the file. + if (!outputFile.setFileTime(modificationTime, + QFileDevice::FileModificationTime)) { + qWarning() << "Could not set modification time for" << outputPath; + } + if (birthTime.isValid()) { + if (!outputFile.setFileTime(birthTime, QFileDevice::FileBirthTime)) { + qWarning() << "Could not set birth time for" << outputPath; + } + } + ServiceManager::safeAfcFileClose(device, handle, altAfc); if (totalBytes == 0) { From 5e3e08eea24a2f38bb1e0e062ce76570f48a3c23 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 1 Dec 2025 20:29:01 +0000 Subject: [PATCH 03/15] fix memory leaks in case of an error --- src/exportmanager.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index 469be90..3573f6d 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -223,7 +223,7 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, plist_t info = nullptr; afc_error_t infoResult = ServiceManager::safeAfcGetFileInfoPlist( device, item.sourcePathOnDevice.toUtf8().constData(), &info, altAfc); - int totalFileSize = 0; + quint64 totalFileSize = 0; if (infoResult != AFC_E_SUCCESS || !info) { qDebug() << "File info retrieval failed for" << item.sourcePathOnDevice; return result; @@ -234,16 +234,19 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, bool valid = fileInfo["st_size"].valid(); if (!valid) { qDebug() << "File size info not valid for" << item.sourcePathOnDevice; + if (info) + plist_free(info); return result; } - // make sure st_size is a float totalFileSize = fileInfo["st_size"].getUInt(); valid = fileInfo["st_mtime"].valid(); if (!valid) { qDebug() << "File modification time info not valid for" << item.sourcePathOnDevice; + if (info) + plist_free(info); return result; } @@ -255,6 +258,8 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, if (!valid) { qDebug() << "File birth time info not valid for" << item.sourcePathOnDevice; + if (info) + plist_free(info); return result; } uint64_t birthTimeNs = fileInfo["st_birthtime"].getUInt(); @@ -288,7 +293,7 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, char buffer[8192]; uint32_t bytesRead = 0; - qint64 totalBytes = 0; + quint64 totalBytes = 0; while (true) { // Check for cancellation during file copy From ad0e2d7c2495ffb2b78223b68352faad219b8818 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 01:17:35 +0000 Subject: [PATCH 04/15] refactor: file timestamps --- src/exportmanager.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/exportmanager.cpp b/src/exportmanager.cpp index 3573f6d..9f6713f 100644 --- a/src/exportmanager.cpp +++ b/src/exportmanager.cpp @@ -333,22 +333,25 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device, } } - // Clean up outputFile.close(); + ServiceManager::safeAfcFileClose(device, handle, altAfc); - // Set file times after closing the file. - if (!outputFile.setFileTime(modificationTime, + // reopen is required for timestamps + QFile reopen(outputPath); + reopen.open(QIODevice::ReadOnly); + if (modificationTime.isValid()) { + if (!reopen.setFileTime(modificationTime, QFileDevice::FileModificationTime)) { - qWarning() << "Could not set modification time for" << outputPath; + qWarning() << "Could not set modification time for" << outputPath; + } } if (birthTime.isValid()) { - if (!outputFile.setFileTime(birthTime, QFileDevice::FileBirthTime)) { + // fails on linux + if (!reopen.setFileTime(birthTime, QFileDevice::FileBirthTime)) { qWarning() << "Could not set birth time for" << outputPath; } } - ServiceManager::safeAfcFileClose(device, handle, altAfc); - if (totalBytes == 0) { result.errorMessage = "No data read from device file"; outputFile.remove(); // Clean up empty file From 48cc6de3492eb3e799867e8485f055d63bf742d0 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 19:31:44 +0000 Subject: [PATCH 05/15] remove old updater reference --- lib/zupdater | 1 - 1 file changed, 1 deletion(-) delete mode 160000 lib/zupdater diff --git a/lib/zupdater b/lib/zupdater deleted file mode 160000 index 3821cf8..0000000 --- a/lib/zupdater +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3821cf82c850b2e90bdc78a926b7e533b96466f7 From 46d36733e3648640e59989a7b333e2705c2e4090 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 19:34:48 +0000 Subject: [PATCH 06/15] update submodules --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1e3396f..7555416 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,9 +4,6 @@ [submodule "lib/ipatool-go"] path = lib/ipatool-go url = https://github.com/uncor3/libipatool-go.git -[submodule "lib/zupdater"] - path = lib/zupdater - url = https://github.com/uncor3/ZUpdater [submodule "lib/win-ifuse"] path = lib/win-ifuse url = https://github.com/uncor3/win-ifuse.git From f0d049a2da1900a7a68ee2487d268826f5fa83a5 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 19:41:56 +0000 Subject: [PATCH 07/15] remove dead code & update ZUpdater reference --- .gitmodules | 3 +++ lib/zupdater | 1 + src/detailwindow.cpp | 36 ------------------------------------ src/detailwindow.h | 38 -------------------------------------- 4 files changed, 4 insertions(+), 74 deletions(-) create mode 160000 lib/zupdater delete mode 100644 src/detailwindow.cpp delete mode 100644 src/detailwindow.h diff --git a/.gitmodules b/.gitmodules index 7555416..a5f05f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/win-ifuse"] path = lib/win-ifuse url = https://github.com/uncor3/win-ifuse.git +[submodule "lib/zupdater"] + path = lib/zupdater + url = https://github.com/libZQT/ZUpdater diff --git a/lib/zupdater b/lib/zupdater new file mode 160000 index 0000000..61aea85 --- /dev/null +++ b/lib/zupdater @@ -0,0 +1 @@ +Subproject commit 61aea855c82a67a3536ec00aca7acd583bcbfc83 diff --git a/src/detailwindow.cpp b/src/detailwindow.cpp deleted file mode 100644 index 0f34620..0000000 --- a/src/detailwindow.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * iDescriptor: A free and open-source idevice management tool. - * - * Copyright (C) 2025 Uncore - * - * 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 . - */ - -#include "detailwindow.h" -#include -#include -#include - -DetailWindow::DetailWindow(const QString &title, int userId, QWidget *parent) - : QMainWindow(parent), m_title(title), m_userId(userId) -{ - - setWindowTitle(m_title); - - QLabel *label = new QLabel(QString("User ID: %1").arg(m_userId)); - QWidget *central = new QWidget; - QVBoxLayout *layout = new QVBoxLayout(central); - layout->addWidget(label); - setCentralWidget(central); -} diff --git a/src/detailwindow.h b/src/detailwindow.h deleted file mode 100644 index 2bee3b9..0000000 --- a/src/detailwindow.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * iDescriptor: A free and open-source idevice management tool. - * - * Copyright (C) 2025 Uncore - * - * 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 . - */ - -#ifndef DETAILWINDOW_H -#define DETAILWINDOW_H - -#include - -class DetailWindow : public QMainWindow -{ - Q_OBJECT -public: - explicit DetailWindow(const QString &title, int userId, - QWidget *parent = nullptr); - -signals: -private: - QString m_title; - int m_userId; -}; - -#endif // DETAILWINDOW_H From c1f38dbd18a714f9fc4665cf3e50cfdd10b00dbc Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 20:02:38 +0000 Subject: [PATCH 08/15] v0.1.2 -Properly scale icons -Show changelog -Add iconSizeBaseMultiplier --- .vscode/c_cpp_properties.json | 3 +- .vscode/settings.json | 4 +- CMakeLists.txt | 4 +- src/deviceinfowidget.cpp | 22 +++---- src/iDescriptor-ui.h | 79 ++++++++++++++++++---- src/iDescriptor.h | 2 + src/ifusediskunmountbutton.cpp | 2 +- src/jailbrokenwidget.cpp | 5 +- src/mainwindow.cpp | 30 ++++++--- src/privateinfolabel.cpp | 2 +- src/releasechangelogdialog.cpp | 115 +++++++++++++++++++++++++++++++++ src/releasechangelogdialog.h | 33 ++++++++++ src/settingsmanager.cpp | 22 +++++++ src/settingsmanager.h | 6 ++ src/settingswidget.cpp | 39 ++++++++++- src/settingswidget.h | 2 + src/toolboxwidget.cpp | 5 +- 17 files changed, 327 insertions(+), 48 deletions(-) create mode 100644 src/releasechangelogdialog.cpp create mode 100644 src/releasechangelogdialog.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0b6e02f..64890af 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -12,7 +12,8 @@ "/usr/include/qt6/**", "/usr/include/qt6/QtBluetooth/**", "${workspaceFolder}/lib/zupdater/src", - "/usr/include/qt6/QtConcurrent" + "/usr/include/qt6/QtConcurrent", + "/usr/include/qt6" ], "defines": [], "compilerPath": "/usr/bin/gcc", diff --git a/.vscode/settings.json b/.vscode/settings.json index 4ec39cc..912d071 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -202,6 +202,8 @@ "qimage": "cpp", "qabstractbutton": "cpp", "qtnetwork": "cpp", - "qtcore": "cpp" + "qtcore": "cpp", + "qbluetoothdevicediscoveryagent": "cpp", + "qbluetoothuuid": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d2d993..42286c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(iDescriptor VERSION 0.1.0 LANGUAGES CXX) +project(iDescriptor VERSION 0.1.2 LANGUAGES CXX) # Feature options option(ENABLE_RECOVERY_DEVICE_SUPPORT "Enable recovery device support (requires libirecovery)" ON) @@ -55,7 +55,7 @@ endforeach() list(APPEND _qt_pkg_dirs ${CUSTOM_PKGCONFIG_PATH}) find_package(PkgConfig REQUIRED) -find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) +find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets Network QuickControls2 SerialPort Positioning Location QuickWidgets) # Add QTermWidget # Prefer CMake-native qtermwidget6, fallback to pkg-config if needed diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index 7f02ea0..a3c68df 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -76,20 +76,19 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) ZIconWidget *shutdownBtn = new ZIconWidget( QIcon(":/resources/icons/IcOutlinePowerSettingsNew.png"), "Shutdown", - this); - shutdownBtn->setIconSize(QSize(20, 20)); + 1.0, this); connect(shutdownBtn, &ZIconWidget::clicked, this, [device]() { ToolboxWidget::shutdownDevice(device); }); - ZIconWidget *restartBtn = new ZIconWidget( - QIcon(":/resources/icons/IcTwotoneRestartAlt.png"), "Restart", this); - restartBtn->setIconSize(QSize(20, 20)); + 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", this); - recoveryBtn->setIconSize(QSize(20, 20)); + ZIconWidget *recoveryBtn = + new ZIconWidget(QIcon(":/resources/icons/HugeiconsWrench01.png"), + "Recovery", 1.0, this); connect(recoveryBtn, &ZIconWidget::clicked, this, [device]() { ToolboxWidget::_enterRecoveryMode(device); }); @@ -155,9 +154,10 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) chargingLayout->setSpacing(5); // Create icon label - m_lightningIconLabel = new ZIconLabel( - QIcon(":/resources/icons/MdiLightningBolt.png"), " Charging", this); - m_lightningIconLabel->setFixedSize(QSize(20, 20)); + m_lightningIconLabel = + new ZIconLabel(QIcon(":/resources/icons/MdiLightningBolt.png"), + " Charging", 1.0, this); + m_batteryWidget = new BatteryWidget( qBound(1, device->deviceInfo.batteryInfo.currentBatteryLevel, 100), device->deviceInfo.batteryInfo.isCharging, this); diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 952e544..681eb36 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -18,6 +18,7 @@ */ #pragma once +#include "settingsmanager.h" #include #include #include @@ -141,18 +142,35 @@ class ZIconWidget : public QAbstractButton { Q_OBJECT public: - ZIconWidget(const QIcon &icon, const QString &tooltip, - QWidget *parent = nullptr) - : QAbstractButton(parent), m_icon(icon) + ZIconWidget(const QIcon &icon, const QString &tooltip = "", + qreal iconSizeMultiplier = 1.0, QWidget *parent = nullptr) + : QAbstractButton(parent), m_icon(icon), + m_iconSizeMultiplier(iconSizeMultiplier) { - setToolTip(tooltip); - setFixedSize(32, 32); - setIconSize(QSize(24, 24)); + if (tooltip != "") { + setToolTip(tooltip); + } + + QFontMetrics fm(font()); + double baseSize = + fm.height() * m_iconSizeMultiplier * + SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); + int intBaseSize = qRound(baseSize); + m_iconSize = QSize(intBaseSize, intBaseSize); + setFixedSize(intBaseSize + 10, intBaseSize + 10); + + update(); setCursor(Qt::PointingHandCursor); connect(qApp, &QApplication::paletteChanged, this, [this]() { update(); }); } + void setIconSizeMultiplier(qreal multiplier) + { + m_iconSizeMultiplier = multiplier; + updateIconSize(); + } + void setIcon(const QIcon &icon) { m_icon = ZIcon(icon); @@ -176,14 +194,31 @@ protected: } QRect iconRect = rect(); - iconRect.setSize(iconSize()); // Use iconSize() from QAbstractButton + iconRect.setSize(m_iconSize); iconRect.moveCenter(rect().center()); m_icon.paint(&painter, iconRect, palette()); } +private: + void updateIconSize() + { + QFontMetrics fm(font()); + double baseSize = + fm.height() * m_iconSizeMultiplier * + SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); + int intBaseSize = qRound(baseSize); + + setFixedSize(intBaseSize + 10, intBaseSize + 10); + + m_iconSize = QSize(intBaseSize, intBaseSize); + update(); + } + private: ZIcon m_icon; + QSize m_iconSize; + qreal m_iconSizeMultiplier; }; // Add this new class for display-only icons @@ -192,15 +227,16 @@ class ZIconLabel : public QLabel Q_OBJECT public: ZIconLabel(const QIcon &icon, const QString &tooltip, - QWidget *parent = nullptr) - : QLabel(parent), m_icon(icon), m_iconSize(24, 24) + qreal iconSizeMultiplier = 1.0, QWidget *parent = nullptr) + : QLabel(parent), m_icon(icon), m_iconSizeMultiplier(iconSizeMultiplier) { setToolTip(tooltip); - // setFixedSize(32, 32); + updateIconSize(); connect(qApp, &QApplication::paletteChanged, this, [this]() { update(); }); + connect(qApp, &QApplication::fontChanged, this, + [this]() { updateIconSize(); }); } - void setIcon(const QIcon &icon) { m_icon = ZIcon(icon); @@ -213,10 +249,10 @@ public: update(); } - void setIconSize(const QSize &size) + void setIconSizeMultiplier(qreal multiplier) { - m_iconSize = size; - update(); + m_iconSizeMultiplier = multiplier; + updateIconSize(); } protected: @@ -234,8 +270,23 @@ protected: } private: + void updateIconSize() + { + QFontMetrics fm(font()); + double baseSize = + fm.height() * m_iconSizeMultiplier * + SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); + int intBaseSize = qRound(baseSize); + + setFixedSize(intBaseSize + 10, intBaseSize + 10); + + m_iconSize = QSize(intBaseSize, intBaseSize); + update(); + } + ZIcon m_icon; QSize m_iconSize; + qreal m_iconSizeMultiplier; }; enum class iDescriptorTool { diff --git a/src/iDescriptor.h b/src/iDescriptor.h index defb1cf..d00dc82 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -54,6 +54,8 @@ "https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/heads/" \ "main/DeveloperDiskImages.json" +#define DONATE_URL "https://opencollective.com/idescriptor" + // This is because afc_read_directory accepts "/var/mobile/Media" as "/" #define POSSIBLE_ROOT "../../../../" diff --git a/src/ifusediskunmountbutton.cpp b/src/ifusediskunmountbutton.cpp index 965f00e..2dbb4b0 100644 --- a/src/ifusediskunmountbutton.cpp +++ b/src/ifusediskunmountbutton.cpp @@ -25,7 +25,7 @@ iFuseDiskUnmountButton::iFuseDiskUnmountButton(const QString &path, QWidget *parent) : ZIconWidget{QIcon(":/resources/icons/ClarityHardDiskSolidAlerted.png"), - "Unmount iFuse at " + path, parent} + "Unmount iFuse at " + path, 1.0, parent} { setCursor(Qt::PointingHandCursor); setFixedSize(24, 24); diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index de1906e..a06797b 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -89,7 +89,7 @@ JailbrokenWidget::createJailbreakTool(const JailbreakToolInfo &info) // Icon (using the theme-aware ZIcon pattern) // ZIconLabel *iconLabel = new ZIconLabel(); - ZIconLabel *iconLabel = new ZIconLabel(QIcon(), nullptr, this); + ZIconLabel *iconLabel = new ZIconLabel(QIcon(), nullptr, 1.5, this); // iconLabel->setAlignment(Qt::AlignCenter); // ZIcon toolIcon(QIcon(info.iconPath)); @@ -140,8 +140,7 @@ JailbrokenWidget::createJailbreakTool(const JailbreakToolInfo &info) iconLabel->setIcon( QIcon(":/resources/icons/IconParkTwotoneMoreTwo.png")); } - iconLabel->setFixedSize(60, 60); - iconLabel->setIconSize(QSize(45, 45)); + iconLabel->setIconSizeMultiplier(2); return b; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6ccdcdc..c82d111 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -19,16 +19,15 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" -#include "detailwindow.h" -#include "ifusediskunmountbutton.h" -#include "ifusemanager.h" -#include "settingswidget.h" - #include "appswidget.h" #include "devicemanagerwidget.h" #include "iDescriptor-ui.h" #include "iDescriptor.h" +#include "ifusediskunmountbutton.h" +#include "ifusemanager.h" #include "jailbrokenwidget.h" +#include "releasechangelogdialog.h" +#include "settingswidget.h" #ifdef ENABLE_RECOVERY_DEVICE_SUPPORT #include "libirecovery.h" #endif @@ -55,7 +54,7 @@ void handleCallback(const idevice_event_t *event, void *userData) { - printf("Device event received: "); + qDebug() << "Device event received"; switch (event->event) { case IDEVICE_DEVICE_ADD: { @@ -140,7 +139,6 @@ MainWindow::MainWindow(QWidget *parent) const QSize minSize(900, 600); setMinimumSize(minSize); resize(minSize); - m_ZTabWidget = new ZTabWidget(this); m_ZTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, false); @@ -179,7 +177,6 @@ MainWindow::MainWindow(QWidget *parent) ZIconWidget *settingsButton = new ZIconWidget( QIcon(":/resources/icons/MingcuteSettings7Line.png"), "Settings"); settingsButton->setCursor(Qt::PointingHandCursor); - settingsButton->setFixedSize(24, 24); connect(settingsButton, &ZIconWidget::clicked, this, [this]() { SettingsManager::sharedInstance()->showSettingsDialog(); }); @@ -187,7 +184,6 @@ MainWindow::MainWindow(QWidget *parent) ZIconWidget *githubButton = new ZIconWidget( QIcon(":/resources/icons/MdiGithub.png"), "iDescriptor on GitHub"); githubButton->setCursor(Qt::PointingHandCursor); - githubButton->setFixedSize(24, 24); connect(githubButton, &ZIconWidget::clicked, this, []() { QDesktopServices::openUrl(QUrl(REPO_URL)); }); @@ -325,6 +321,22 @@ MainWindow::MainWindow(QWidget *parent) .arg(PACKAGE_MANAGER_HINT)); #endif + QString lastAppVersion = SettingsManager::sharedInstance()->appVersion(); + bool shouldShowReleaseChangelog = lastAppVersion != APP_VERSION; + SettingsManager::sharedInstance()->setAppVersion(APP_VERSION); + + if (shouldShowReleaseChangelog) { + connect( + m_updater, &ZUpdater::dataAvailable, this, + [this](const QJsonDocument data, bool isUpdateAvailable) { + if (!isUpdateAvailable) { + ReleaseChangelogDialog dialog(data, this); + dialog.exec(); + } + }, + Qt::SingleShotConnection); + } + SettingsManager::sharedInstance()->doIfEnabled( SettingsManager::Setting::AutoCheckUpdates, [this]() { qDebug() << "Checking for updates..."; diff --git a/src/privateinfolabel.cpp b/src/privateinfolabel.cpp index 6c7c9bc..dcc188f 100644 --- a/src/privateinfolabel.cpp +++ b/src/privateinfolabel.cpp @@ -32,7 +32,7 @@ PrivateInfoLabel::PrivateInfoLabel(const QString &fullText, QWidget *parent) layout->addWidget(m_textLabel); m_toggleButton = new ZIconWidget( - QIcon(":/resources/icons/ClarityEyeHideLine.png"), "Show", this); + QIcon(":/resources/icons/ClarityEyeHideLine.png"), "Show", 1.0, this); m_toggleButton->setIconSize(QSize(20, 20)); connect(m_toggleButton, &ZIconWidget::clicked, this, &PrivateInfoLabel::toggleVisibility); diff --git a/src/releasechangelogdialog.cpp b/src/releasechangelogdialog.cpp new file mode 100644 index 0000000..761f761 --- /dev/null +++ b/src/releasechangelogdialog.cpp @@ -0,0 +1,115 @@ + + +/* + * iDescriptor: A free and open-source idevice management tool. + * + * Copyright (C) 2025 Uncore + * + * 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 . + */ + +#include "releasechangelogdialog.h" +#include "iDescriptor.h" +#include "settingsmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include + +ReleaseChangelogDialog::ReleaseChangelogDialog(QJsonDocument data, + QWidget *parent) + : QDialog(parent) +{ + setupUI(data); +} + +ReleaseChangelogDialog::~ReleaseChangelogDialog() {} + +void ReleaseChangelogDialog::setupUI(const QJsonDocument &data) +{ + setWindowTitle("iDescriptor - Release Changelog"); + setModal(true); + setMinimumSize(500, 250); + resize(600, 300); + + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setContentsMargins(20, 20, 20, 20); + m_mainLayout->setSpacing(15); + + m_titleLabel = + new QLabel(QString("iDescriptor has been updated to v") + APP_VERSION); + m_titleLabel->setAlignment(Qt::AlignCenter); + m_titleLabel->setStyleSheet( + "font-size: 18px; font-weight: bold; margin-bottom: 10px;"); + m_mainLayout->addWidget(m_titleLabel); + + QString description = "Failed to load changelog data."; + QJsonArray dataArr = data.array(); + if (!dataArr.isEmpty()) { + for (const QJsonValue &releaseVal : dataArr) { + QJsonObject releaseObj = releaseVal.toObject(); + if (!releaseObj.isEmpty()) { + QString tagName = releaseObj.value("tag_name").toString(); + + if (tagName.isEmpty()) { + continue; + } + if (tagName == QString("v") + APP_VERSION) { + if (releaseObj.value("body").isUndefined()) + break; + description = releaseObj.value("body").toString(); + break; + } + } + } + } + + m_descriptionLabel = new QLabel(description); + m_descriptionLabel->setAlignment(Qt::AlignCenter); + m_descriptionLabel->setWordWrap(true); + m_descriptionLabel->setStyleSheet("font-size: 14px; margin: 10px;"); + m_mainLayout->addWidget(m_descriptionLabel); + + m_mainLayout->addStretch(); + + QHBoxLayout *buttonsLayout = new QHBoxLayout(); + m_skipButton = new QPushButton("Ok, Thanks!"); + m_skipButton->setFixedHeight(40); + + m_donateButton = new QPushButton("Donate"); + m_donateButton->setDefault(true); + m_donateButton->setFixedHeight(40); + + buttonsLayout->addWidget(m_skipButton); + buttonsLayout->addWidget(m_donateButton); + + m_mainLayout->addLayout(buttonsLayout, Qt::AlignCenter); + + connect(m_donateButton, &QPushButton::clicked, this, + &ReleaseChangelogDialog::onDonateClicked); + connect(m_skipButton, &QPushButton::clicked, this, + &ReleaseChangelogDialog::onSkipButtonClicked); +} + +void ReleaseChangelogDialog::onDonateClicked() +{ + QDesktopServices::openUrl(QUrl(DONATE_URL)); + accept(); +} + +void ReleaseChangelogDialog::onSkipButtonClicked() { accept(); } diff --git a/src/releasechangelogdialog.h b/src/releasechangelogdialog.h new file mode 100644 index 0000000..c6acb15 --- /dev/null +++ b/src/releasechangelogdialog.h @@ -0,0 +1,33 @@ +#ifndef RELEASECHANGELOG_H +#define RELEASECHANGELOG_H + +#include +#include +#include +#include +#include + +class ReleaseChangelogDialog : public QDialog +{ + Q_OBJECT +public: + explicit ReleaseChangelogDialog(QJsonDocument data, + QWidget *parent = nullptr); + + ~ReleaseChangelogDialog(); +signals: + +private: + void setupUI(const QJsonDocument &data); + + QVBoxLayout *m_mainLayout = nullptr; + QPushButton *m_skipButton = nullptr; + QPushButton *m_donateButton = nullptr; + QLabel *m_titleLabel = nullptr; + QLabel *m_descriptionLabel = nullptr; + + void onDonateClicked(); + void onSkipButtonClicked(); +}; + +#endif // RELEASECHANGELOG_H diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index 5b7158d..a8be4f6 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -368,4 +368,26 @@ void SettingsManager::clearRecentLocations() { m_settings->remove("recentLocations"); m_settings->sync(); +} + +QString SettingsManager::appVersion() +{ + return m_settings->value("__APP_VERSION__", "").toString(); +} + +void SettingsManager::setAppVersion(const QString &version) +{ + m_settings->setValue("__APP_VERSION__", version); + m_settings->sync(); +} + +double SettingsManager::iconSizeBaseMultiplier() const +{ + return m_settings->value("iconSizeBaseMultiplier", 1.0).toDouble(); +} + +void SettingsManager::setIconSizeBaseMultiplier(double multiplier) +{ + m_settings->setValue("iconSizeBaseMultiplier", multiplier); + m_settings->sync(); } \ No newline at end of file diff --git a/src/settingsmanager.h b/src/settingsmanager.h index f0e3208..0e3b47a 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -99,6 +99,12 @@ public: void resetToDefaults(); void clear(); + + QString appVersion(); + void setAppVersion(const QString &version); + + double iconSizeBaseMultiplier() const; + void setIconSizeBaseMultiplier(double multiplier); signals: void favoritePlacesChanged(); void recentLocationsChanged(); diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp index f38002e..1c06e78 100644 --- a/src/settingswidget.cpp +++ b/src/settingswidget.cpp @@ -153,6 +153,29 @@ void SettingsWidget::setupUI() scrollLayout->addWidget(jailbrokenGroup); + // === MISCELLANEOUS SETTINGS === + auto *miscGroup = new QGroupBox("Miscellaneous"); + auto *miscLayout = new QVBoxLayout(miscGroup); + + auto *iconSizeBaseMultiplierLayout = new QHBoxLayout(); + m_iconSizeBaseMultiplier = new QDoubleSpinBox(); + m_iconSizeBaseMultiplier->setRange(1.0, 5.0); + m_iconSizeBaseMultiplier->setSingleStep(0.1); + m_iconSizeBaseMultiplier->setDecimals(1); + m_iconSizeBaseMultiplier->setSuffix("x"); + m_iconSizeBaseMultiplier->setToolTip( + "Adjust the base multiplier for icon sizes. This affects how large " + "icons appear throughout the application. Requires restart to take " + "effect."); + + iconSizeBaseMultiplierLayout->addWidget( + new QLabel("Icon Size Base Multiplier:")); + iconSizeBaseMultiplierLayout->addWidget(m_iconSizeBaseMultiplier); + iconSizeBaseMultiplierLayout->addStretch(); + miscLayout->addLayout(iconSizeBaseMultiplierLayout); + + scrollLayout->addWidget(miscGroup); + scrollLayout->addSpacing(30); // Add a footer Author & Version & app info & app description @@ -163,7 +186,7 @@ void SettingsWidget::setupUI() "© 2025 See AUTHORS for details. Licensed under AGPLv3.") .arg(APP_VERSION)); footerLabel->setAlignment(Qt::AlignCenter); - footerLabel->setStyleSheet("color: gray; font-size: 10pt;"); + footerLabel->setStyleSheet("color: gray; font-size: 8pt;"); scrollLayout->addWidget(footerLabel); // Add stretch to push everything to the top @@ -225,6 +248,8 @@ void SettingsWidget::loadSettings() // Disable apply button initially m_applyButton->setEnabled(false); + + m_iconSizeBaseMultiplier->setValue(sm->iconSizeBaseMultiplier()); } void SettingsWidget::connectSignals() @@ -245,12 +270,20 @@ void SettingsWidget::connectSignals() connect(m_connectionTimeout, QOverload::of(&QSpinBox::valueChanged), this, &SettingsWidget::onSettingChanged); + connect(m_iconSizeBaseMultiplier, + QOverload::of(&QDoubleSpinBox::valueChanged), this, + [this]() { + m_restartRequired = true; + onSettingChanged(); + }); + connect(m_useUnsecureBackend, &QCheckBox::toggled, this, [this]() { // since this is unsafe if its being enabled, show a warning if (m_useUnsecureBackend->isChecked()) { auto reply = QMessageBox::warning( this, "Warning", - "Enabling this will not encrypt your Apple account which is a " + "Enabling this will not encrypt your Apple account which " + "is a " "security risk. Are you sure you want to enable this?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No); @@ -345,6 +378,8 @@ void SettingsWidget::saveSettings() sm->setDefaultJailbrokenRootPassword( m_defaultJailbrokenRootPassword->text()); + sm->setIconSizeBaseMultiplier(m_iconSizeBaseMultiplier->value()); + m_applyButton->setEnabled(false); } diff --git a/src/settingswidget.h b/src/settingswidget.h index 36869aa..82276d9 100644 --- a/src/settingswidget.h +++ b/src/settingswidget.h @@ -66,6 +66,8 @@ private: // Jailbroken QLineEdit *m_defaultJailbrokenRootPassword; + QDoubleSpinBox *m_iconSizeBaseMultiplier; + // Buttons QPushButton *m_checkUpdatesButton; QPushButton *m_resetButton; diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 2bb55d0..d6afc3d 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -220,7 +220,7 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, QVBoxLayout *layout = new QVBoxLayout(b); - ZIconLabel *icon = new ZIconLabel(QIcon(), nullptr, this); + ZIconLabel *icon = new ZIconLabel(QIcon(), nullptr, 1.5, this); QString title; switch (tool) { case iDescriptorTool::Airplayer: @@ -298,8 +298,7 @@ ClickableWidget *ToolboxWidget::createToolbox(iDescriptorTool tool, descLabel->setWordWrap(true); descLabel->setAlignment(Qt::AlignCenter); descLabel->setStyleSheet("color: #666; font-size: 12px;"); - icon->setFixedSize(60, 60); - icon->setIconSize(QSize(45, 45)); + icon->setIconSizeMultiplier(1.90); layout->addWidget(icon, 0, Qt::AlignCenter); layout->addWidget(titleLabel); From 9949b87f18bf5f247876d010ed9d19efaa03ba81 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 20:55:08 +0000 Subject: [PATCH 09/15] enable ci workflow --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75d3068..067eba0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,17 @@ name: iDescriptor CI (Linux) on: workflow_dispatch: + push: + paths: + - "**.cpp" + - "**.h" + - "**.hpp" + - "**.cxx" + - "**.cc" + - "CMakeLists.txt" + - "**.cmake" + - ".github/workflows/ci.yml" + env: QT_VERSION: "6.7.2" GO_VERSION: "1.23.0" From ce4a90a29c6714ff9255db644babd6ecd053cfe6 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 20:55:17 +0000 Subject: [PATCH 10/15] fix build --- src/releasechangelogdialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/releasechangelogdialog.cpp b/src/releasechangelogdialog.cpp index 761f761..811054c 100644 --- a/src/releasechangelogdialog.cpp +++ b/src/releasechangelogdialog.cpp @@ -1,5 +1,3 @@ - - /* * iDescriptor: A free and open-source idevice management tool. * @@ -25,6 +23,8 @@ #include #include #include +#include +#include #include #include #include From 4a5acabc19cd0bdfe7484974f1ee039f076b670f Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 21:21:45 +0000 Subject: [PATCH 11/15] fix text color --- src/deviceinfowidget.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index a3c68df..2ef7417 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -143,10 +143,12 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) 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()) - : "color: white;"); + : QString("color: %1;") + .arg(qApp->palette().color(QPalette::WindowText).name())); // Create the layout without a parent widget QHBoxLayout *chargingLayout = new QHBoxLayout(); From aa7c516bbb876d272188434d3d40bb3b56c684e3 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 8 Dec 2025 21:23:32 +0000 Subject: [PATCH 12/15] target macOS 13 --- .github/workflows/build-macos.yml | 2 +- CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 5709f83..994fce2 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: include: - - runner: macos-15-intel + - runner: macos-13 arch: x86_64 - runner: macos-14 arch: arm64 diff --git a/CMakeLists.txt b/CMakeLists.txt index 42286c9..327ce56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,8 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) if (APPLE) - # Target at least macOS 14.0 - set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0") + # Target at least macOS 13.0 + set(CMAKE_OSX_DEPLOYMENT_TARGET "13.0") endif() # Platform-specific paths for libraries built from source From 9f36c23768bb15e26ea2110cbf84d7acbddf989d Mon Sep 17 00:00:00 2001 From: uncor3 Date: Tue, 9 Dec 2025 15:01:03 +0000 Subject: [PATCH 13/15] scale based on dpi --- src/iDescriptor-ui.h | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index 681eb36..ca17fcb 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -21,11 +21,14 @@ #include "settingsmanager.h" #include #include +// #include // Qt6: removed, use QScreen #include +#include #include #include #include #include +#include #include #include #include @@ -43,6 +46,34 @@ #define COLOR_BLUE QColor("#2b5693") #define COLOR_ACCENT_BLUE QColor("#0b5ed7") +class ScaledSize +{ +public: + static QSize getScaledSize(const QSize &size) + { + return {static_cast(size.width() * getXScaleFactor()), + static_cast(size.height() * getYScaleFactor())}; + } + + static qreal getXScaleFactor() + { + QScreen *screen = QGuiApplication::primaryScreen(); + if (!screen) + return 1.0; + return screen->logicalDotsPerInchX() / getReferenceDpiValue(); + } + static qreal getYScaleFactor() + { + QScreen *screen = QGuiApplication::primaryScreen(); + if (!screen) + return 1.0; + return screen->logicalDotsPerInchY() / getReferenceDpiValue(); + } + +private: + static qreal getReferenceDpiValue() { return 96.0; } +}; + // A custom QGraphicsView that keeps the content fitted with aspect ratio on // resize class ResponsiveGraphicsView : public QGraphicsView @@ -157,7 +188,8 @@ public: SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); int intBaseSize = qRound(baseSize); m_iconSize = QSize(intBaseSize, intBaseSize); - setFixedSize(intBaseSize + 10, intBaseSize + 10); + setFixedSize(ScaledSize::getScaledSize( + QSize(intBaseSize + 10, intBaseSize + 10))); update(); setCursor(Qt::PointingHandCursor); @@ -209,7 +241,8 @@ private: SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); int intBaseSize = qRound(baseSize); - setFixedSize(intBaseSize + 10, intBaseSize + 10); + setFixedSize(ScaledSize::getScaledSize( + QSize(intBaseSize + 10, intBaseSize + 10))); m_iconSize = QSize(intBaseSize, intBaseSize); update(); @@ -221,7 +254,6 @@ private: qreal m_iconSizeMultiplier; }; -// Add this new class for display-only icons class ZIconLabel : public QLabel { Q_OBJECT @@ -278,7 +310,9 @@ private: SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); int intBaseSize = qRound(baseSize); - setFixedSize(intBaseSize + 10, intBaseSize + 10); + // Make label DPI-aware too + setFixedSize(ScaledSize::getScaledSize( + QSize(intBaseSize + 10, intBaseSize + 10))); m_iconSize = QSize(intBaseSize, intBaseSize); update(); From b343dd59559db267323ac02d466589bfdd2d2ec5 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Tue, 9 Dec 2025 17:28:44 +0000 Subject: [PATCH 14/15] refactor: remove unused ScaledSize class and update icon handling for DPI awareness --- src/iDescriptor-ui.h | 158 ++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 100 deletions(-) diff --git a/src/iDescriptor-ui.h b/src/iDescriptor-ui.h index ca17fcb..7140264 100644 --- a/src/iDescriptor-ui.h +++ b/src/iDescriptor-ui.h @@ -21,7 +21,6 @@ #include "settingsmanager.h" #include #include -// #include // Qt6: removed, use QScreen #include #include #include @@ -46,36 +45,6 @@ #define COLOR_BLUE QColor("#2b5693") #define COLOR_ACCENT_BLUE QColor("#0b5ed7") -class ScaledSize -{ -public: - static QSize getScaledSize(const QSize &size) - { - return {static_cast(size.width() * getXScaleFactor()), - static_cast(size.height() * getYScaleFactor())}; - } - - static qreal getXScaleFactor() - { - QScreen *screen = QGuiApplication::primaryScreen(); - if (!screen) - return 1.0; - return screen->logicalDotsPerInchX() / getReferenceDpiValue(); - } - static qreal getYScaleFactor() - { - QScreen *screen = QGuiApplication::primaryScreen(); - if (!screen) - return 1.0; - return screen->logicalDotsPerInchY() / getReferenceDpiValue(); - } - -private: - static qreal getReferenceDpiValue() { return 96.0; } -}; - -// A custom QGraphicsView that keeps the content fitted with aspect ratio on -// resize class ResponsiveGraphicsView : public QGraphicsView { public: @@ -104,8 +73,6 @@ signals: void clicked(); protected: - // On mouse release, if the click is inside the widget, emit the clicked - // signal void mouseReleaseEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton && @@ -126,43 +93,45 @@ public: void setThemable(bool themable) { m_themable = themable; } - QPixmap getThemedPixmap(const QSize &size, const QPalette &palette) const + QPixmap getThemedPixmap(const QSize &logicalSize, const QPalette &palette, + qreal dpr = 1.0) const { - // If not themable, return the original pixmap without color filling. - if (!m_themable) { - return QIcon::pixmap(size); - } + QSize physical = logicalSize * dpr; - QPixmap pixmap = QIcon::pixmap(size); - if (pixmap.isNull()) { + QPixmap pixmap = QIcon::pixmap(physical); + if (pixmap.isNull()) return pixmap; - } - // Get the appropriate icon color based on theme + pixmap.setDevicePixelRatio(dpr); + + if (!m_themable) + return pixmap; + + // theme color QColor iconColor = palette.color(QPalette::WindowText); - // Create a colored version of the icon - QPixmap coloredPixmap(pixmap.size()); - coloredPixmap.fill(Qt::transparent); + QPixmap colored(pixmap.size()); + colored.setDevicePixelRatio(dpr); + colored.fill(Qt::transparent); - QPainter iconPainter(&coloredPixmap); - iconPainter.setCompositionMode(QPainter::CompositionMode_SourceOver); - iconPainter.drawPixmap(0, 0, pixmap); - iconPainter.setCompositionMode(QPainter::CompositionMode_SourceIn); - iconPainter.fillRect(coloredPixmap.rect(), iconColor); + QPainter p(&colored); + p.setRenderHint(QPainter::Antialiasing); + p.drawPixmap(0, 0, pixmap); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(colored.rect(), iconColor); + p.end(); - return coloredPixmap; + return colored; } - void paint(QPainter *painter, const QRect &rect, - const QPalette &palette) const + void paint(QPainter *painter, const QRect &logicalRect, + const QPalette &palette, qreal dpr = 1.0) const { - QPixmap themedPixmap = getThemedPixmap(rect.size(), palette); - if (!themedPixmap.isNull()) { - painter->drawPixmap(rect, themedPixmap); - } else { - QIcon::paint(painter, rect); - } + QPixmap pm = getThemedPixmap(logicalRect.size(), palette, dpr); + if (pm.isNull()) + return; + + painter->drawPixmap(logicalRect, pm); } private: @@ -178,23 +147,22 @@ public: : QAbstractButton(parent), m_icon(icon), m_iconSizeMultiplier(iconSizeMultiplier) { - if (tooltip != "") { + if (!tooltip.isEmpty()) setToolTip(tooltip); - } - QFontMetrics fm(font()); - double baseSize = - fm.height() * m_iconSizeMultiplier * - SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); - int intBaseSize = qRound(baseSize); - m_iconSize = QSize(intBaseSize, intBaseSize); - setFixedSize(ScaledSize::getScaledSize( - QSize(intBaseSize + 10, intBaseSize + 10))); - - update(); + updateIconSize(); setCursor(Qt::PointingHandCursor); + connect(qApp, &QApplication::paletteChanged, this, - [this]() { update(); }); + [this] { update(); }); + connect(qApp, &QApplication::fontChanged, this, + [this] { updateIconSize(); }); + } + + void setIcon(const ZIcon &icon) + { + m_icon = icon; + update(); } void setIconSizeMultiplier(qreal multiplier) @@ -203,24 +171,17 @@ public: updateIconSize(); } - void setIcon(const QIcon &icon) - { - m_icon = ZIcon(icon); - update(); - } - protected: void paintEvent(QPaintEvent *event) override { - Q_UNUSED(event) + Q_UNUSED(event); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); - // Draw background circle when hovered or pressed if (underMouse() || isDown()) { - QColor bgColor = palette().color(QPalette::Highlight); - bgColor.setAlpha(isDown() ? 60 : 30); - painter.setBrush(bgColor); + QColor bg = palette().color(QPalette::Highlight); + bg.setAlpha(isDown() ? 60 : 30); + painter.setBrush(bg); painter.setPen(Qt::NoPen); painter.drawEllipse(rect().adjusted(2, 2, -2, -2)); } @@ -229,22 +190,21 @@ protected: iconRect.setSize(m_iconSize); iconRect.moveCenter(rect().center()); - m_icon.paint(&painter, iconRect, palette()); + m_icon.paint(&painter, iconRect, palette(), devicePixelRatioF()); } private: void updateIconSize() { QFontMetrics fm(font()); - double baseSize = - fm.height() * m_iconSizeMultiplier * - SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); - int intBaseSize = qRound(baseSize); + int base = + qRound(fm.height() * m_iconSizeMultiplier * + SettingsManager::sharedInstance()->iconSizeBaseMultiplier()); - setFixedSize(ScaledSize::getScaledSize( - QSize(intBaseSize + 10, intBaseSize + 10))); + m_iconSize = QSize(base, base); + + setFixedSize(base + 10, base + 10); - m_iconSize = QSize(intBaseSize, intBaseSize); update(); } @@ -298,23 +258,21 @@ protected: iconRect.setSize(m_iconSize); iconRect.moveCenter(rect().center()); - m_icon.paint(&painter, iconRect, palette()); + m_icon.paint(&painter, iconRect, palette(), devicePixelRatioF()); } private: void updateIconSize() { QFontMetrics fm(font()); - double baseSize = - fm.height() * m_iconSizeMultiplier * - SettingsManager::sharedInstance()->iconSizeBaseMultiplier(); - int intBaseSize = qRound(baseSize); + int base = + qRound(fm.height() * m_iconSizeMultiplier * + SettingsManager::sharedInstance()->iconSizeBaseMultiplier()); - // Make label DPI-aware too - setFixedSize(ScaledSize::getScaledSize( - QSize(intBaseSize + 10, intBaseSize + 10))); + m_iconSize = QSize(base, base); + + setFixedSize(base + 10, base + 10); - m_iconSize = QSize(intBaseSize, intBaseSize); update(); } From e8fec8d227f7e84277bd2db37ce8bedd13342799 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Tue, 9 Dec 2025 17:37:54 +0000 Subject: [PATCH 15/15] fix deprecated macos runner --- .github/workflows/build-macos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 994fce2..5709f83 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: include: - - runner: macos-13 + - runner: macos-15-intel arch: x86_64 - runner: macos-14 arch: arm64