Merge pull request #31 from iDescriptor/dev (v0.1.2)
This commit is contained in:
uncor3
2025-12-09 11:00:35 -07:00
committed by GitHub
24 changed files with 469 additions and 188 deletions
+11
View File
@@ -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"
+3 -3
View File
@@ -4,9 +4,9 @@
[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
[submodule "lib/zupdater"]
path = lib/zupdater
url = https://github.com/libZQT/ZUpdater
+2 -1
View File
@@ -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",
+3 -1
View File
@@ -202,6 +202,8 @@
"qimage": "cpp",
"qabstractbutton": "cpp",
"qtnetwork": "cpp",
"qtcore": "cpp"
"qtcore": "cpp",
"qbluetoothdevicediscoveryagent": "cpp",
"qbluetoothuuid": "cpp"
}
}
+4 -4
View File
@@ -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)
@@ -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
@@ -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
-36
View File
@@ -1,36 +0,0 @@
/*
* 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 "detailwindow.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>
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);
}
-38
View File
@@ -1,38 +0,0 @@
/*
* 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/>.
*/
#ifndef DETAILWINDOW_H
#define DETAILWINDOW_H
#include <QMainWindow>
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
+14 -12
View File
@@ -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); });
@@ -144,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();
@@ -155,9 +156,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<int>(1, device->deviceInfo.batteryInfo.currentBatteryLevel, 100),
device->deviceInfo.batteryInfo.isCharging, this);
+72 -15
View File
@@ -208,23 +208,65 @@ 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);
quint64 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;
if (info)
plist_free(info);
return result;
}
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;
}
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;
if (info)
plist_free(info);
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(
@@ -251,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
@@ -291,10 +333,25 @@ ExportResult ExportManager::exportSingleItem(iDescriptorDevice *device,
}
}
// Clean up
outputFile.close();
ServiceManager::safeAfcFileClose(device, handle, altAfc);
// 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;
}
}
if (birthTime.isValid()) {
// fails on linux
if (!reopen.setFileTime(birthTime, QFileDevice::FileBirthTime)) {
qWarning() << "Could not set birth time for" << outputPath;
}
}
if (totalBytes == 0) {
result.errorMessage = "No data read from device file";
outputFile.remove(); // Clean up empty file
+98 -55
View File
@@ -18,13 +18,16 @@
*/
#pragma once
#include "settingsmanager.h"
#include <QAbstractButton>
#include <QApplication>
#include <QGraphicsView>
#include <QGuiApplication>
#include <QLabel>
#include <QMainWindow>
#include <QMouseEvent>
#include <QPainter>
#include <QScreen>
#include <QSlider>
#include <QSplitter>
#include <QSplitterHandle>
@@ -42,8 +45,6 @@
#define COLOR_BLUE QColor("#2b5693")
#define COLOR_ACCENT_BLUE QColor("#0b5ed7")
// A custom QGraphicsView that keeps the content fitted with aspect ratio on
// resize
class ResponsiveGraphicsView : public QGraphicsView
{
public:
@@ -72,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 &&
@@ -94,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:
@@ -141,66 +142,93 @@ 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.isEmpty())
setToolTip(tooltip);
updateIconSize();
setCursor(Qt::PointingHandCursor);
connect(qApp, &QApplication::paletteChanged, this,
[this]() { update(); });
[this] { update(); });
connect(qApp, &QApplication::fontChanged, this,
[this] { updateIconSize(); });
}
void setIcon(const QIcon &icon)
void setIcon(const ZIcon &icon)
{
m_icon = ZIcon(icon);
m_icon = icon;
update();
}
void setIconSizeMultiplier(qreal multiplier)
{
m_iconSizeMultiplier = multiplier;
updateIconSize();
}
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));
}
QRect iconRect = rect();
iconRect.setSize(iconSize()); // Use iconSize() from QAbstractButton
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());
int base =
qRound(fm.height() * m_iconSizeMultiplier *
SettingsManager::sharedInstance()->iconSizeBaseMultiplier());
m_iconSize = QSize(base, base);
setFixedSize(base + 10, base + 10);
update();
}
private:
ZIcon m_icon;
QSize m_iconSize;
qreal m_iconSizeMultiplier;
};
// Add this new class for display-only icons
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 +241,10 @@ public:
update();
}
void setIconSize(const QSize &size)
void setIconSizeMultiplier(qreal multiplier)
{
m_iconSize = size;
update();
m_iconSizeMultiplier = multiplier;
updateIconSize();
}
protected:
@@ -230,12 +258,27 @@ 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());
int base =
qRound(fm.height() * m_iconSizeMultiplier *
SettingsManager::sharedInstance()->iconSizeBaseMultiplier());
m_iconSize = QSize(base, base);
setFixedSize(base + 10, base + 10);
update();
}
ZIcon m_icon;
QSize m_iconSize;
qreal m_iconSizeMultiplier;
};
enum class iDescriptorTool {
+2
View File
@@ -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 "../../../../"
+1 -1
View File
@@ -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);
+2 -3
View File
@@ -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;
}
+21 -9
View File
@@ -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...";
+1 -1
View File
@@ -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);
+115
View File
@@ -0,0 +1,115 @@
/*
* 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 "releasechangelogdialog.h"
#include "iDescriptor.h"
#include "settingsmanager.h"
#include <QApplication>
#include <QCheckBox>
#include <QDesktopServices>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QPushButton>
#include <QUrl>
#include <QVBoxLayout>
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(); }
+33
View File
@@ -0,0 +1,33 @@
#ifndef RELEASECHANGELOG_H
#define RELEASECHANGELOG_H
#include <QDialog>
#include <QJsonArray>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
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
+22
View File
@@ -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();
}
+6
View File
@@ -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();
+37 -2
View File
@@ -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<int>::of(&QSpinBox::valueChanged),
this, &SettingsWidget::onSettingChanged);
connect(m_iconSizeBaseMultiplier,
QOverload<double>::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);
}
+2
View File
@@ -66,6 +66,8 @@ private:
// Jailbroken
QLineEdit *m_defaultJailbrokenRootPassword;
QDoubleSpinBox *m_iconSizeBaseMultiplier;
// Buttons
QPushButton *m_checkUpdatesButton;
QPushButton *m_resetButton;
+17 -3
View File
@@ -25,6 +25,7 @@
#include <QFile>
#include <QHBoxLayout>
#include <QHostAddress>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QProcess>
@@ -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) {
+2 -3
View File
@@ -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);