fix bugs & add new dialogs

- Introduced `CredDialog` to prompt users for access to secure backends (Windows Credential Manager or Secret Service).
- Integrated `CredDialog` into `AppsWidget` initialization flow, allowing users to skip signing in.
- Updated `SettingsManager` to manage user preferences for showing the keychain dialog.
- Enhanced `DevDiskManager` and related classes to utilize a new method for creating device disk image paths.
- Refactored `VirtualLocation` to support saving and displaying recent locations.
- Improved UI responsiveness and layout adjustments across various widgets.
- Cleaned up unused code and comments for better maintainability.
This commit is contained in:
uncor3
2025-11-10 01:01:20 +00:00
parent f0ab7efc6e
commit da559349e8
18 changed files with 536 additions and 146 deletions
+16 -6
View File
@@ -23,6 +23,7 @@
#include "appdownloaddialog.h"
#include "appinstalldialog.h"
#include "appstoremanager.h"
#include "creddialog.h"
#include "iDescriptor-ui.h"
#include "keychaindialog.h"
#include "logindialog.h"
@@ -228,6 +229,7 @@ void AppsWidget::setupUI()
void AppsWidget::init()
{
// FIXME:update url
QUrl sponsorsUrl("http://localhost:5173/sponsors.json");
QNetworkRequest request(sponsorsUrl);
QNetworkReply *reply = m_networkManager->get(request);
@@ -260,8 +262,6 @@ void AppsWidget::init()
QJsonObject silverObj = sponsorObj["silver"].toObject();
QJsonObject bronzeObj = sponsorObj["bronze"].toObject();
// Store the platinum members to be used when populating
// the grid
m_platinumSponsors = platinumObj["members"].toArray();
m_goldSponsors = goldObj["members"].toArray();
m_silverSponsors = silverObj["members"].toArray();
@@ -296,6 +296,12 @@ void AppsWidget::handleInit()
"4px; padding: 8px 16px; font-size: 14px;");
return;
}
/*
FIXME: ipatoolinitialze still uses the secure backends
when if the user rejects it, the moment he/she tries to sign in
prompt(keychain or secret-service whatever the backend is) will be seen
again
*/
if (!SettingsManager::sharedInstance()->useUnsecureBackend() &&
SettingsManager::sharedInstance()->showKeychainDialog()) {
#ifdef __APPLE__
@@ -306,9 +312,16 @@ void AppsWidget::handleInit()
showDefaultApps();
return;
}
#else
CredDialog dialog(this);
if (dialog.exec() == QDialog::Rejected) {
// pass empty QJsonObject to skip signing in
onAppStoreInitialized(QJsonObject());
showDefaultApps();
return;
}
#endif
}
// todo also change in the ipatoolinitialze as the backend is alrady enabled
onAppStoreInitialized(m_manager->getAccountInfo());
showDefaultApps();
}
@@ -437,7 +450,6 @@ void AppsWidget::populateDefaultApps()
int col = 0;
const int maxCols = 3;
// Helper lambda to advance the grid position
auto advanceGridPos = [&]() {
col++;
if (col >= maxCols) {
@@ -612,7 +624,6 @@ void AppsWidget::createAppCard(
QUrl url(logoUrl);
QNetworkRequest request(url);
QNetworkReply *reply = m_networkManager->get(request);
// Use Qt's parent-child relationship to auto-cleanup
connect(
reply, &QNetworkReply::finished, this, [reply, safeIconLabel]() {
if (reply->error() == QNetworkReply::NoError && safeIconLabel) {
@@ -638,7 +649,6 @@ void AppsWidget::createAppCard(
}
reply->deleteLater();
});
// Ensure reply is deleted if iconLabel is destroyed
connect(iconLabel, &QObject::destroyed, reply, &QNetworkReply::abort);
} else if (!bundleId.isEmpty()) {
fetchAppIconFromApple(
+128
View File
@@ -0,0 +1,128 @@
/*
* 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 "creddialog.h"
#include "settingsmanager.h"
#include <QApplication>
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
CredDialog::CredDialog(QWidget *parent)
: QDialog(parent), m_mainLayout(nullptr), m_okButton(nullptr),
m_titleLabel(nullptr), m_descriptionLabel(nullptr),
m_dontShowAgainCheckbox(nullptr)
{
setupUI();
}
CredDialog::~CredDialog() {}
void CredDialog::setupUI()
{
#ifdef WIN32
setWindowTitle("Windows Credential Manager Access Required");
#else
setWindowTitle("Secret Service Access Required");
#endif
setModal(true);
setMinimumSize(500, 250);
resize(600, 300);
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setContentsMargins(20, 20, 20, 20);
m_mainLayout->setSpacing(15);
// Title label
#ifdef WIN32
m_titleLabel = new QLabel("Windows Credential Manager Access Required");
#else
m_titleLabel = new QLabel("Secret Service Access Required");
#endif
m_titleLabel->setAlignment(Qt::AlignCenter);
m_titleLabel->setStyleSheet(
"font-size: 18px; font-weight: bold; margin-bottom: 10px;");
m_mainLayout->addWidget(m_titleLabel);
// Description label
#ifdef WIN32
QString description =
"In order to sign in to App Store we use the Windows Credential "
"Manager "
"to safely store and retrieve your credentials. You may be prompted to "
"allow access to the credential manager. "
"This is a security feature to protect your Apple ID credentials. You "
"can disable this in Settings.";
#else
QString description =
"In order to sign in to App Store we use the Secret Service "
"(gnome-keyring "
"or similar) to safely store and retrieve your credentials. You may be "
"prompted to unlock your keyring or allow access. "
"This is a security feature to protect your Apple ID credentials. You "
"can disable this in Settings.";
#endif
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();
m_dontShowAgainCheckbox = new QCheckBox("Don't show this again");
m_mainLayout->addWidget(m_dontShowAgainCheckbox, 0, Qt::AlignCenter);
QHBoxLayout *buttonsLayout = new QHBoxLayout();
m_skipSigningInButton = new QPushButton("Skip For Now");
m_skipSigningInButton->setFixedHeight(40);
m_okButton = new QPushButton("OK, I understand");
m_okButton->setDefault(true);
m_okButton->setFixedHeight(40);
buttonsLayout->addWidget(m_skipSigningInButton);
buttonsLayout->addWidget(m_okButton);
m_mainLayout->addLayout(buttonsLayout, Qt::AlignCenter);
connect(m_okButton, &QPushButton::clicked, this, &CredDialog::onOkClicked);
connect(m_skipSigningInButton, &QPushButton::clicked, this,
&CredDialog::onSkipSigningInClicked);
}
void CredDialog::onOkClicked()
{
if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) {
SettingsManager::sharedInstance()->setShowKeychainDialog(false);
}
accept();
}
void CredDialog::onSkipSigningInClicked()
{
if (m_dontShowAgainCheckbox && m_dontShowAgainCheckbox->isChecked()) {
SettingsManager::sharedInstance()->setShowKeychainDialog(false);
}
reject();
}
+52
View File
@@ -0,0 +1,52 @@
/*
* 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 CRED_DIALOG_H
#define CRED_DIALOG_H
#include <QCheckBox>
#include <QDialog>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
class CredDialog : public QDialog
{
Q_OBJECT
public:
explicit CredDialog(QWidget *parent = nullptr);
~CredDialog();
private slots:
void onOkClicked();
void onSkipSigningInClicked();
private:
void setupUI();
QVBoxLayout *m_mainLayout;
QPushButton *m_okButton;
QPushButton *m_skipSigningInButton;
QLabel *m_titleLabel;
QLabel *m_descriptionLabel;
QCheckBox *m_dontShowAgainCheckbox;
};
#endif // CRED_DIALOG_H
+13 -7
View File
@@ -20,6 +20,7 @@
#include "devdiskimagehelper.h"
#include "devdiskmanager.h"
#include "qprocessindicator.h"
#include "settingsmanager.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QMessageBox>
@@ -31,10 +32,9 @@ DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device,
: QDialog(parent), m_device(device), m_isDownloading(false),
m_isMounting(false)
{
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle("Developer Disk Image - iDescriptor");
setupUI();
QTimer::singleShot(0, this, &DevDiskImageHelper::start);
}
void DevDiskImageHelper::setupUI()
@@ -96,12 +96,18 @@ void DevDiskImageHelper::start()
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
// we dont have developer disk images for ios 6 and below
// FIXME:we dont have developer disk images for ios 6 and below
if (deviceMajorVersion > 5) {
// TODO: maybe check isMountAvailable and finishWithFailure if false
const bool isMountAvailable =
DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
QTimer::singleShot(500, this, &DevDiskImageHelper::checkAndMount);
DevDiskManager::sharedInstance()->downloadCompatibleImage(
m_device, [this](bool success) {
if (success) {
checkAndMount();
} else {
finishWithError("Failed to download compatible image.");
}
});
} else {
finishWithSuccess();
return;
@@ -122,7 +128,6 @@ void DevDiskImageHelper::checkAndMount()
// If image is already mounted
if (!result.sig.empty()) {
showStatus("Developer disk image already mounted");
finishWithSuccess();
return;
}
@@ -132,6 +137,7 @@ void DevDiskImageHelper::checkAndMount()
void DevDiskImageHelper::onMountButtonClicked()
{
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
m_mountButton->setVisible(false);
m_loadingIndicator->start();
m_isMounting = true;
@@ -142,7 +148,7 @@ void DevDiskImageHelper::onMountButtonClicked()
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
QList<ImageInfo> images = DevDiskManager::sharedInstance()->parseImageList(
deviceMajorVersion, deviceMinorVersion, "", 0);
path, deviceMajorVersion, deviceMinorVersion, "", 0);
// Check if compatible image is downloaded
bool hasDownloadedImage = false;
+2 -1
View File
@@ -195,9 +195,10 @@ void DevDiskImagesWidget::displayImages()
qDebug() << "Device version:" << deviceMajorVersion << "."
<< deviceMinorVersion << "displayImages";
// Parse images using manager
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
QList<ImageInfo> allImages =
DevDiskManager::sharedInstance()->parseImageList(
deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(),
path, deviceMajorVersion, deviceMinorVersion, m_mounted_sig.c_str(),
m_mounted_sig_len);
qDebug() << "Total images:" << allImages.size();
+29 -41
View File
@@ -134,24 +134,6 @@ QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
}
}
// Handle Trustcache URLs (for iOS 17+)
if (versionData.contains("Trustcache")) {
QJsonArray trustcacheArray = versionData["Trustcache"].toArray();
if (!trustcacheArray.isEmpty()) {
versionFiles["Image.dmg.trustcache"] =
trustcacheArray[0].toString();
}
}
// Handle BuildManifest URLs (for iOS 17+)
if (versionData.contains("BuildManifest")) {
QJsonArray manifestArray = versionData["BuildManifest"].toArray();
if (!manifestArray.isEmpty()) {
versionFiles["BuildManifest.plist"] =
manifestArray[0].toString();
}
}
// Only add versions that have at least an image file
if (!versionFiles.isEmpty() &&
versionFiles.contains("DeveloperDiskImage.dmg")) {
@@ -162,7 +144,8 @@ QMap<QString, QMap<QString, QString>> DevDiskManager::parseDiskDir()
return imageFiles;
}
QList<ImageInfo> DevDiskManager::parseImageList(int deviceMajorVersion,
QList<ImageInfo> DevDiskManager::parseImageList(QString path,
int deviceMajorVersion,
int deviceMinorVersion,
const char *mounted_sig,
uint64_t mounted_sig_len)
@@ -171,15 +154,16 @@ QList<ImageInfo> DevDiskManager::parseImageList(int deviceMajorVersion,
QMap<QString, QMap<QString, QString>> imageFiles = parseDiskDir();
QList<ImageInfo> sortedResult =
getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion,
mounted_sig, mounted_sig_len);
getImagesSorted(imageFiles, path, deviceMajorVersion,
deviceMinorVersion, mounted_sig, mounted_sig_len);
return sortedResult;
}
QList<ImageInfo> DevDiskManager::getImagesSorted(
QMap<QString, QMap<QString, QString>> imageFiles, int deviceMajorVersion,
int deviceMinorVersion, const char *mounted_sig, uint64_t mounted_sig_len)
QMap<QString, QMap<QString, QString>> imageFiles, QString path,
int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig,
uint64_t mounted_sig_len)
{
QList<ImageInfo> allImages;
// TODO: what is this ?
@@ -194,8 +178,7 @@ QList<ImageInfo> DevDiskManager::getImagesSorted(
info.version = version;
info.dmgPath = it.value()["DeveloperDiskImage.dmg"];
info.sigPath = it.value()["DeveloperDiskImage.dmg.signature"];
info.isDownloaded = isImageDownloaded(
version, SettingsManager::sharedInstance()->devdiskimgpath());
info.isDownloaded = isImageDownloaded(version, path);
// Determine compatibility
if (hasConnectedDevice) {
@@ -336,15 +319,17 @@ bool DevDiskManager::isImageDownloaded(const QString &version,
return QFile::exists(dmgPath) && QFile::exists(sigPath);
}
bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device,
std::function<void(bool)> callback)
{
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
unsigned int device_version = idevice_get_device_version(device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
qDebug() << "Device version:" << deviceMajorVersion << "."
<< deviceMinorVersion;
QList<ImageInfo> images =
parseImageList(deviceMajorVersion, deviceMinorVersion, "", 0);
parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0);
for (const ImageInfo &info : images) {
if (info.compatibility != ImageCompatibility::Compatible &&
@@ -352,8 +337,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
continue;
}
if (info.isDownloaded) {
qDebug() << "There is a compatible image already downloaded:"
<< info.version;
callback(true);
return true;
}
}
@@ -363,6 +347,17 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
if (info.compatibility == ImageCompatibility::Compatible ||
info.compatibility == ImageCompatibility::MaybeCompatible) {
const QString versionToDownload = info.version;
connect(
this, &DevDiskManager::imageDownloadFinished, this,
[this, versionToDownload,
callback](const QString &finishedVersion, bool success,
const QString &errorMessage) {
if (finishedVersion == versionToDownload) {
callback(success);
}
},
Qt::SingleShotConnection);
qDebug()
<< "No compatible image found locally. Downloading version:"
<< versionToDownload;
@@ -371,8 +366,7 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
downloadImage(versionToDownload);
auto *downloadItem = new DownloadItem();
downloadItem->version = versionToDownload;
downloadItem->downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
downloadItem->downloadPath = path;
downloadItem->dmgReply = replies.first;
downloadItem->sigReply = replies.second;
@@ -400,12 +394,13 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device)
// FIXME:DOES NOT CHECK IF THERE IS ALREADY AN IMAGE MOUNTED
bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
{
QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath();
unsigned int device_version = idevice_get_device_version(device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
unsigned int deviceMinorVersion = (device_version >> 8) & 0xFF;
QList<ImageInfo> images =
parseImageList(deviceMajorVersion, deviceMinorVersion, "", 0);
parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0);
// 1. Try to mount an already downloaded compatible image
for (const ImageInfo &info : images) {
@@ -431,8 +426,6 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
}
}
}
const QString downloadPath =
SettingsManager::sharedInstance()->devdiskimgpath();
// 2. If none are downloaded, download the newest compatible one
for (const ImageInfo &info : images) {
@@ -443,19 +436,15 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
<< "No compatible image found locally. Downloading version:"
<< versionToDownload;
// Connect a one-time slot to mount the image after download
// finishes
connect(
this, &DevDiskManager::imageDownloadFinished, this,
[this, device, downloadPath,
[this, device, path,
versionToDownload](const QString &finishedVersion,
bool success, const QString &errorMessage) {
if (success && finishedVersion == versionToDownload) {
qDebug() << "Download finished for" << finishedVersion
<< ". Now attempting to mount.";
mountImage(finishedVersion, device);
// TODO: You might want to emit another signal here to
// notify the UI of the final mount result.
} else if (!success) {
qDebug() << "Failed to download" << finishedVersion
<< ":" << errorMessage;
@@ -468,7 +457,7 @@ bool DevDiskManager::mountCompatibleImage(iDescriptorDevice *device)
downloadImage(versionToDownload);
auto *downloadItem = new DownloadItem();
downloadItem->version = versionToDownload;
downloadItem->downloadPath = downloadPath;
downloadItem->downloadPath = path;
downloadItem->dmgReply = replies.first;
downloadItem->sigReply = replies.second;
@@ -562,7 +551,6 @@ void DevDiskManager::onFileDownloadFinished()
QString path = QUrl::fromPercentEncoding(reply->url().path().toUtf8());
QFileInfo fileInfo(path);
QString filename = fileInfo.fileName();
QString targetPath = QDir(QDir(item->downloadPath).filePath(item->version))
.filePath(filename);
+6 -4
View File
@@ -36,7 +36,7 @@ public:
explicit DevDiskManager(QObject *parent = nullptr);
static DevDiskManager *sharedInstance();
QList<ImageInfo> parseImageList(int deviceMajorVersion,
QList<ImageInfo> parseImageList(QString path, int deviceMajorVersion,
int deviceMinorVersion,
const char *mounted_sig,
uint64_t mounted_sig_len);
@@ -61,7 +61,8 @@ public:
QByteArray getImageListData() const { return m_imageListJsonData; }
GetMountedImageResult getMountedImage(const char *udid);
bool mountCompatibleImage(iDescriptorDevice *device);
bool downloadCompatibleImage(iDescriptorDevice *device);
bool downloadCompatibleImage(iDescriptorDevice *device,
std::function<void(bool)> callback);
signals:
void imageListFetched(bool success,
@@ -93,8 +94,9 @@ private:
QMap<QString, QMap<QString, QString>> parseDiskDir();
QList<ImageInfo>
getImagesSorted(QMap<QString, QMap<QString, QString>> imageFiles,
int deviceMajorVersion, int deviceMinorVersion,
const char *mounted_sig, uint64_t mounted_sig_len);
QString path, int deviceMajorVersion,
int deviceMinorVersion, const char *mounted_sig,
uint64_t mounted_sig_len);
void populateImageList();
};
+1
View File
@@ -478,6 +478,7 @@ void GalleryWidget::onBackToAlbums()
{
// Switch back to album selection view
m_stackedWidget->setCurrentWidget(m_albumSelectionWidget);
m_model->clear();
// Disable controls and hide back button
setControlsEnabled(false);
+6
View File
@@ -83,6 +83,12 @@ LiveScreenWidget::LiveScreenWidget(iDescriptorDevice *device, QWidget *parent)
connect(m_timer, &QTimer::timeout, this,
&LiveScreenWidget::updateScreenshot);
// Defer the initialization to allow the main widget to show first
QTimer::singleShot(0, this, &LiveScreenWidget::startInitialization);
}
void LiveScreenWidget::startInitialization()
{
const bool initializeScreenshotServiceSuccess =
initializeScreenshotService(false);
if (initializeScreenshotServiceSuccess)
+2 -1
View File
@@ -47,7 +47,8 @@ private:
screenshotr_client_t m_shotrClient;
int m_fps;
signals:
private:
void startInitialization(); // Add this line
};
#endif // LIVESCREEN_H
+3 -3
View File
@@ -193,14 +193,14 @@ MainWindow::MainWindow(QWidget *parent)
ui->statusbar->addWidget(m_connectedDeviceCountLabel);
ui->statusbar->setContentsMargins(0, 0, 0, 0);
ui->statusbar->addPermanentWidget(githubButton);
ui->statusbar->addPermanentWidget(settingsButton);
QLabel *appVersionLabel = new QLabel(QString("v%1").arg(APP_VERSION));
appVersionLabel->setContentsMargins(5, 0, 5, 0);
appVersionLabel->setStyleSheet(
"QLabel:hover { background-color : #13131319; }");
ui->statusbar->addPermanentWidget(appVersionLabel);
ui->statusbar->addPermanentWidget(githubButton);
ui->statusbar->addPermanentWidget(settingsButton);
#ifdef __linux__
QList<QString> mounted_iFusePaths = iFuseManager::getMountPoints();
+9 -40
View File
@@ -57,9 +57,8 @@ PhotoModel::PhotoModel(iDescriptorDevice *device, FilterType filterType,
&PhotoModel::requestThumbnail, Qt::QueuedConnection);
}
PhotoModel::~PhotoModel()
void PhotoModel::clear()
{
qDebug() << "PhotoModel destructor called";
// Clean up any active watchers
for (auto *watcher : m_activeLoaders.values()) {
if (watcher) {
@@ -73,6 +72,12 @@ PhotoModel::~PhotoModel()
m_thumbnailCache.clear();
}
PhotoModel::~PhotoModel()
{
qDebug() << "PhotoModel destructor called";
clear();
}
QPixmap PhotoModel::generateVideoThumbnailFFmpeg(iDescriptorDevice *device,
const QString &filePath,
const QSize &requestedSize)
@@ -446,31 +451,6 @@ QVariant PhotoModel::data(const QModelIndex &index, int role) const
}
}
void PhotoModel::setThumbnailSize(const QSize &size)
{
if (m_thumbnailSize != size) {
m_thumbnailSize = size;
// Clear cache when size changes
clearCache();
}
}
void PhotoModel::clearCache()
{
m_thumbnailCache.clear();
// Reset all requested flags
for (PhotoInfo &info : m_photos) {
info.thumbnailRequested = false;
}
// Notify view to refresh
if (!m_photos.isEmpty()) {
emit dataChanged(createIndex(0, 0), createIndex(m_photos.size() - 1, 0),
{Qt::DecorationRole});
}
}
void PhotoModel::requestThumbnail(int index)
{
if (index < 0 || index >= m_photos.size())
@@ -897,19 +877,8 @@ PhotoInfo::FileType PhotoModel::determineFileType(const QString &fileName) const
void PhotoModel::setAlbumPath(const QString &albumPath)
{
if (m_albumPath != albumPath) {
// Clear cache when switching albums to prevent memory buildup
clearCache();
// Cancel any pending thumbnail requests
for (auto *watcher : m_activeLoaders.values()) {
if (watcher) {
watcher->cancel();
watcher->waitForFinished();
watcher->deleteLater();
}
}
m_activeLoaders.clear();
m_loadingPaths.clear();
qDebug() << "Setting new album path:" << albumPath;
clear();
m_albumPath = albumPath;
populatePhotoPaths();
+1 -4
View File
@@ -59,10 +59,6 @@ public:
QVariant data(const QModelIndex &index,
int role = Qt::DisplayRole) const override;
// Thumbnail management
void setThumbnailSize(const QSize &size);
void clearCache();
// Album management
void setAlbumPath(const QString &albumPath);
void refreshPhotos();
@@ -89,6 +85,7 @@ public:
static QPixmap loadThumbnailFromDevice(iDescriptorDevice *device,
const QString &filePath,
const QSize &size);
void clear();
signals:
void thumbnailNeedsToBeLoaded(int index);
void exportRequested(const QStringList &filePaths);
+66
View File
@@ -20,6 +20,7 @@
#include "settingsmanager.h"
#include "settingswidget.h"
#include <QDebug>
#include <QDir>
#include <QSettings>
#include <QStandardPaths>
@@ -59,6 +60,17 @@ QString SettingsManager::devdiskimgpath() const
return downloadPath(); // Use the new downloadPath method
}
QString SettingsManager::mkDevDiskImgPath() const
{
QString path = devdiskimgpath();
QDir dir(path);
if (!dir.exists()) {
dir.mkpath(path);
return path;
}
return path;
}
// Settings implementation
QString SettingsManager::downloadPath() const
{
@@ -291,4 +303,58 @@ void SettingsManager::clearKeys(const QString &keyPrefix)
m_settings->sync();
emit favoritePlacesChanged();
}
void SettingsManager::saveRecentLocation(double latitude, double longitude,
const QString &name)
{
// Get existing recent locations
QList<QVariantMap> recentLocations = getRecentLocations();
// Create new location entry
QVariantMap newLocation;
newLocation["latitude"] = latitude;
newLocation["longitude"] = longitude;
// Add to front of list
recentLocations.prepend(newLocation);
// Keep only last 10 locations
while (recentLocations.size() > 10) {
recentLocations.removeLast();
}
// Save to settings
QVariantList variantList;
for (const QVariantMap &loc : recentLocations) {
variantList.append(loc);
}
m_settings->setValue("recentLocations", variantList);
m_settings->sync();
qDebug() << "Saved recent location:" << latitude << "," << longitude;
emit recentLocationsChanged();
}
QList<QVariantMap> SettingsManager::getRecentLocations() const
{
QList<QVariantMap> recentLocations;
QVariantList variantList = m_settings->value("recentLocations").toList();
for (const QVariant &item : variantList) {
if (item.canConvert<QVariantMap>()) {
recentLocations.append(item.toMap());
}
}
return recentLocations;
}
void SettingsManager::clearRecentLocations()
{
m_settings->remove("recentLocations");
m_settings->sync();
}
+9 -1
View File
@@ -57,10 +57,17 @@ public:
getFavoritePlaces(const QString &keyPrefix) const;
void showSettingsDialog();
// New settings methods
// Recently used locations
void saveRecentLocation(double latitude, double longitude,
const QString &name = QString());
QList<QVariantMap> getRecentLocations() const;
void clearRecentLocations();
QString downloadPath() const;
void setDownloadPath(const QString &path);
QString mkDevDiskImgPath() const;
bool autoCheckUpdates() const;
void setAutoCheckUpdates(bool enabled);
@@ -94,6 +101,7 @@ public:
signals:
void favoritePlacesChanged();
void recentLocationsChanged();
private:
QDialog *m_dialog;
+181 -38
View File
@@ -22,6 +22,7 @@
#include "devdiskimagehelper.h"
#include "devdiskmanager.h"
#include "iDescriptor.h"
#include "settingsmanager.h"
#include <QDebug>
#include <QDoubleValidator>
#include <QGeoCoordinate>
@@ -44,6 +45,19 @@
VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
: QWidget{parent}, m_device(device)
{
unsigned int device_version = idevice_get_device_version(m_device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
if (deviceMajorVersion > 16) {
QMessageBox::warning(
this, "Unsupported iOS Version",
"Virtual Location feature requires iOS 16 or earlier.\n"
"Your device is running iOS " +
QString::number(deviceMajorVersion) +
", which is not yet supported.");
QTimer::singleShot(0, this, &QWidget::close);
return;
}
setWindowTitle("Virtual Location - iDescriptor");
// Create the main layout
QHBoxLayout *mainLayout = new QHBoxLayout(this);
@@ -54,17 +68,17 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
QWidget *rightPanel = new QWidget();
rightPanel->setFixedWidth(250);
QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel);
rightLayout->setContentsMargins(15, 15, 15, 15);
rightLayout->setSpacing(10);
m_rightLayout = new QVBoxLayout(rightPanel);
m_rightLayout->setContentsMargins(15, 15, 15, 15);
m_rightLayout->setSpacing(10);
// Title
QLabel *titleLabel = new QLabel("Virtual Location Settings");
titleLabel->setStyleSheet("margin-bottom: 10px;");
rightLayout->addWidget(titleLabel);
m_rightLayout->addWidget(titleLabel);
QGroupBox *coordGroup = new QGroupBox("Coordinates");
rightLayout->addWidget(coordGroup);
m_rightLayout->addWidget(coordGroup);
QVBoxLayout *coordLayout = new QVBoxLayout(coordGroup);
@@ -89,16 +103,24 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
coordLayout->addWidget(m_longitudeEdit);
// Add some spacing
rightLayout->addItem(
m_rightLayout->addItem(
new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Fixed));
// Apply button
m_applyButton = new QPushButton("Apply Settings");
m_applyButton = new QPushButton("Apply Location");
m_applyButton->setDefault(true);
rightLayout->addWidget(m_applyButton);
m_rightLayout->addWidget(m_applyButton);
// Recent locations section
loadRecentLocations(m_rightLayout);
// Add stretch to push everything to the top
rightLayout->addStretch();
m_rightLayout->addStretch();
// Connect to recent locations changes
connect(SettingsManager::sharedInstance(),
&SettingsManager::recentLocationsChanged, this,
&VirtualLocation::refreshRecentLocations);
// Create map widget
m_quickWidget = new QQuickWidget(this);
@@ -142,20 +164,7 @@ VirtualLocation::VirtualLocation(iDescriptorDevice *device, QWidget *parent)
}
});
DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
unsigned int device_version = idevice_get_device_version(m_device->device);
unsigned int deviceMajorVersion = (device_version >> 16) & 0xFF;
if (deviceMajorVersion > 16) {
QMessageBox::warning(
this, "Unsupported iOS Version",
"Virtual Location feature requires iOS 16 or earlier.\n"
"Your device is running iOS " +
QString::number(deviceMajorVersion) +
", which is not yet supported.");
QTimer::singleShot(0, this, &QWidget::close);
return;
}
// DevDiskManager::sharedInstance()->downloadCompatibleImage(m_device);
}
void VirtualLocation::onQuickWidgetStatusChanged(QQuickWidget::Status status)
@@ -290,6 +299,7 @@ void VirtualLocation::updateInputsFromMap(double latitude, double longitude)
void VirtualLocation::onApplyClicked()
{
m_applyButton->setEnabled(false);
bool latOk, lonOk;
double latitude = m_latitudeEdit->text().toDouble(&latOk);
double longitude = m_longitudeEdit->text().toDouble(&lonOk);
@@ -298,15 +308,16 @@ void VirtualLocation::onApplyClicked()
QMessageBox::warning(
this, "Invalid Input",
"Please enter valid latitude and longitude values.");
m_applyButton->setEnabled(true);
return;
}
// Create and show the helper dialog
auto *helper = new DevDiskImageHelper(m_device, this);
connect(helper, &DevDiskImageHelper::mountingCompleted, this,
[this, latitude, longitude, helper](bool success) {
helper->deleteLater();
DevDiskImageHelper *devDiskImageHelper =
new DevDiskImageHelper(m_device, this);
connect(devDiskImageHelper, &DevDiskImageHelper::mountingCompleted, this,
[this, latitude, longitude, devDiskImageHelper](bool success) {
if (devDiskImageHelper) {
devDiskImageHelper->deleteLater();
}
if (!success) {
return;
@@ -318,12 +329,6 @@ void VirtualLocation::onApplyClicked()
// Visual feedback
m_applyButton->setText("Applied!");
m_applyButton->setEnabled(false);
QTimer::singleShot(1000, this, [this]() {
m_applyButton->setText("Apply Settings");
m_applyButton->setEnabled(true);
});
bool locationSuccess = set_location(
m_device->device,
@@ -336,10 +341,148 @@ void VirtualLocation::onApplyClicked()
QMessageBox::warning(this, "Error",
"Failed to set location on device");
} else {
SettingsManager::sharedInstance()->saveRecentLocation(
latitude, longitude);
QMessageBox::information(this, "Success",
"Location applied successfully!");
}
});
helper->start();
connect(
devDiskImageHelper, &DevDiskImageHelper::destroyed, this,
[this]() {
QTimer::singleShot(1000, this, [this]() {
m_applyButton->setText("Apply Location");
m_applyButton->setEnabled(true);
});
},
Qt::SingleShotConnection);
devDiskImageHelper->start();
}
void VirtualLocation::loadRecentLocations(QVBoxLayout *layout)
{
QList<QVariantMap> recentLocations =
SettingsManager::sharedInstance()->getRecentLocations();
if (recentLocations.isEmpty()) {
return; // Don't render anything if no recent locations
}
layout->addItem(
new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Fixed));
m_recentGroup = new QGroupBox("Recent Locations");
layout->addWidget(m_recentGroup);
// A group box needs a layout to contain its children
QVBoxLayout *groupBoxLayout = new QVBoxLayout(m_recentGroup);
QScrollArea *scrollArea = new QScrollArea();
scrollArea->setWidgetResizable(true);
scrollArea->setFrameShape(QFrame::NoFrame);
groupBoxLayout->addWidget(scrollArea);
QWidget *scrollContent = new QWidget();
scrollArea->setWidget(scrollContent);
// This layout is for the content widget
QVBoxLayout *recentLayout = new QVBoxLayout(scrollContent);
addLocationButtons(recentLayout, recentLocations);
}
void VirtualLocation::onRecentLocationClicked(double latitude, double longitude)
{
// Update input fields
m_latitudeEdit->setText(QString::number(latitude, 'f', 6));
m_longitudeEdit->setText(QString::number(longitude, 'f', 6));
// Update map
updateMapFromInputs();
qDebug() << "Recent location clicked:" << latitude << "," << longitude;
}
void VirtualLocation::refreshRecentLocations()
{
if (!m_recentGroup) {
return;
}
// Get the group box's layout
QVBoxLayout *groupBoxLayout =
qobject_cast<QVBoxLayout *>(m_recentGroup->layout());
if (!groupBoxLayout) {
return;
}
// Get the scroll area from the group box layout
QScrollArea *scrollArea = nullptr;
if (groupBoxLayout->count() > 0) {
scrollArea =
qobject_cast<QScrollArea *>(groupBoxLayout->itemAt(0)->widget());
}
if (!scrollArea) {
return;
}
// Get the scroll content widget
QWidget *scrollContent = scrollArea->widget();
if (!scrollContent) {
return;
}
// Get the content layout
QVBoxLayout *recentLayout =
qobject_cast<QVBoxLayout *>(scrollContent->layout());
if (!recentLayout) {
return;
}
// Clear all existing buttons
QLayoutItem *item;
while ((item = recentLayout->takeAt(0)) != nullptr) {
if (item->widget()) {
item->widget()->deleteLater();
}
delete item;
}
// Reload recent locations
QList<QVariantMap> recentLocations =
SettingsManager::sharedInstance()->getRecentLocations();
if (recentLocations.isEmpty()) {
// Hide the group if no locations
m_recentGroup->hide();
return;
}
// Show the group if it was hidden
m_recentGroup->show();
addLocationButtons(recentLayout, recentLocations);
}
void VirtualLocation::addLocationButtons(QLayout *layout,
QList<QVariantMap> recentLocations)
{
for (const QVariantMap &location : recentLocations) {
double latitude = location["latitude"].toDouble();
double longitude = location["longitude"].toDouble();
QPushButton *locationBtn =
new QPushButton(QString("Lat: %1\nLon: %2")
.arg(latitude, 0, 'f', 4)
.arg(longitude, 0, 'f', 4));
connect(locationBtn, &QPushButton::clicked, this,
[this, latitude, longitude]() {
onRecentLocationClicked(latitude, longitude);
});
layout->addWidget(locationBtn);
}
}
+11
View File
@@ -20,11 +20,14 @@
#ifndef VIRTUAL_LOCATION_H
#define VIRTUAL_LOCATION_H
#include "devdiskimagehelper.h"
#include "iDescriptor.h"
#include <QGroupBox>
#include <QLineEdit>
#include <QPushButton>
#include <QQuickWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
class VirtualLocation : public QWidget
@@ -47,8 +50,14 @@ private slots:
void onMapCenterChanged();
void onApplyClicked();
void updateMapFromInputs();
void onRecentLocationClicked(double latitude, double longitude);
private:
void loadRecentLocations(QVBoxLayout *layout);
void refreshRecentLocations();
void addLocationButtons(QLayout *layout,
QList<QVariantMap> recentLocations);
QQuickWidget *m_quickWidget;
QLineEdit *m_latitudeEdit;
QLineEdit *m_longitudeEdit;
@@ -56,6 +65,8 @@ private:
QTimer m_updateTimer;
bool m_updatingFromInput = false;
iDescriptorDevice *m_device;
QVBoxLayout *m_rightLayout = nullptr;
QGroupBox *m_recentGroup = nullptr;
};
#endif // VIRTUAL_LOCATION_H
+1
View File
@@ -37,6 +37,7 @@ WirelessGalleryImportWidget::WirelessGalleryImportWidget(QWidget *parent)
m_loadingLabel(nullptr), m_tutorialLayout(nullptr)
{
setupUI();
setMinimumSize(800, 600);
QTimer::singleShot(100, this,
&WirelessGalleryImportWidget::setupTutorialVideo);
}