From da559349e870348e3989eabb957565a8fa556e0f Mon Sep 17 00:00:00 2001 From: uncor3 Date: Mon, 10 Nov 2025 01:01:20 +0000 Subject: [PATCH] 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. --- src/appswidget.cpp | 22 ++- src/creddialog.cpp | 128 ++++++++++++++++ src/creddialog.h | 52 +++++++ src/devdiskimagehelper.cpp | 20 ++- src/devdiskimageswidget.cpp | 3 +- src/devdiskmanager.cpp | 70 ++++----- src/devdiskmanager.h | 10 +- src/gallerywidget.cpp | 1 + src/livescreenwidget.cpp | 6 + src/livescreenwidget.h | 3 +- src/mainwindow.cpp | 6 +- src/photomodel.cpp | 49 ++----- src/photomodel.h | 5 +- src/settingsmanager.cpp | 66 +++++++++ src/settingsmanager.h | 10 +- src/virtuallocationwidget.cpp | 219 +++++++++++++++++++++++----- src/virtuallocationwidget.h | 11 ++ src/wirelessgalleryimportwidget.cpp | 1 + 18 files changed, 536 insertions(+), 146 deletions(-) create mode 100644 src/creddialog.cpp create mode 100644 src/creddialog.h diff --git a/src/appswidget.cpp b/src/appswidget.cpp index 1f10352..478a747 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -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( diff --git a/src/creddialog.cpp b/src/creddialog.cpp new file mode 100644 index 0000000..89d2475 --- /dev/null +++ b/src/creddialog.cpp @@ -0,0 +1,128 @@ +/* + * 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 "creddialog.h" +#include "settingsmanager.h" +#include +#include +#include +#include +#include + +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(); +} diff --git a/src/creddialog.h b/src/creddialog.h new file mode 100644 index 0000000..6d8319d --- /dev/null +++ b/src/creddialog.h @@ -0,0 +1,52 @@ +/* + * 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 CRED_DIALOG_H +#define CRED_DIALOG_H + +#include +#include +#include +#include +#include + +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 diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index 26ad7c2..d08af6a 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -20,6 +20,7 @@ #include "devdiskimagehelper.h" #include "devdiskmanager.h" #include "qprocessindicator.h" +#include "settingsmanager.h" #include #include #include @@ -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 images = DevDiskManager::sharedInstance()->parseImageList( - deviceMajorVersion, deviceMinorVersion, "", 0); + path, deviceMajorVersion, deviceMinorVersion, "", 0); // Check if compatible image is downloaded bool hasDownloadedImage = false; diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 6363480..868c03d 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -195,9 +195,10 @@ void DevDiskImagesWidget::displayImages() qDebug() << "Device version:" << deviceMajorVersion << "." << deviceMinorVersion << "displayImages"; // Parse images using manager + QString path = SettingsManager::sharedInstance()->mkDevDiskImgPath(); QList 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(); diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp index c0367ab..a502a9d 100644 --- a/src/devdiskmanager.cpp +++ b/src/devdiskmanager.cpp @@ -134,24 +134,6 @@ QMap> 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> DevDiskManager::parseDiskDir() return imageFiles; } -QList DevDiskManager::parseImageList(int deviceMajorVersion, +QList DevDiskManager::parseImageList(QString path, + int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig, uint64_t mounted_sig_len) @@ -171,15 +154,16 @@ QList DevDiskManager::parseImageList(int deviceMajorVersion, QMap> imageFiles = parseDiskDir(); QList sortedResult = - getImagesSorted(imageFiles, deviceMajorVersion, deviceMinorVersion, - mounted_sig, mounted_sig_len); + getImagesSorted(imageFiles, path, deviceMajorVersion, + deviceMinorVersion, mounted_sig, mounted_sig_len); return sortedResult; } QList DevDiskManager::getImagesSorted( - QMap> imageFiles, int deviceMajorVersion, - int deviceMinorVersion, const char *mounted_sig, uint64_t mounted_sig_len) + QMap> imageFiles, QString path, + int deviceMajorVersion, int deviceMinorVersion, const char *mounted_sig, + uint64_t mounted_sig_len) { QList allImages; // TODO: what is this ? @@ -194,8 +178,7 @@ QList 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 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 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 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); diff --git a/src/devdiskmanager.h b/src/devdiskmanager.h index 9441072..ba995d3 100644 --- a/src/devdiskmanager.h +++ b/src/devdiskmanager.h @@ -36,7 +36,7 @@ public: explicit DevDiskManager(QObject *parent = nullptr); static DevDiskManager *sharedInstance(); - QList parseImageList(int deviceMajorVersion, + QList 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 callback); signals: void imageListFetched(bool success, @@ -93,8 +94,9 @@ private: QMap> parseDiskDir(); QList getImagesSorted(QMap> 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(); }; diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp index 20a8248..f134290 100644 --- a/src/gallerywidget.cpp +++ b/src/gallerywidget.cpp @@ -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); diff --git a/src/livescreenwidget.cpp b/src/livescreenwidget.cpp index 1ea3e60..aa3e726 100644 --- a/src/livescreenwidget.cpp +++ b/src/livescreenwidget.cpp @@ -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) diff --git a/src/livescreenwidget.h b/src/livescreenwidget.h index 19ebf4f..361608f 100644 --- a/src/livescreenwidget.h +++ b/src/livescreenwidget.h @@ -47,7 +47,8 @@ private: screenshotr_client_t m_shotrClient; int m_fps; -signals: +private: + void startInitialization(); // Add this line }; #endif // LIVESCREEN_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4f8cfc4..ae4ac7f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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 mounted_iFusePaths = iFuseManager::getMountPoints(); diff --git a/src/photomodel.cpp b/src/photomodel.cpp index 1b00498..36eb319 100644 --- a/src/photomodel.cpp +++ b/src/photomodel.cpp @@ -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(); diff --git a/src/photomodel.h b/src/photomodel.h index c590972..bc9866f 100644 --- a/src/photomodel.h +++ b/src/photomodel.h @@ -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); diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index 39ac707..91ae4f6 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -20,6 +20,7 @@ #include "settingsmanager.h" #include "settingswidget.h" #include +#include #include #include @@ -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 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 SettingsManager::getRecentLocations() const +{ + QList recentLocations; + + QVariantList variantList = m_settings->value("recentLocations").toList(); + + for (const QVariant &item : variantList) { + if (item.canConvert()) { + recentLocations.append(item.toMap()); + } + } + + return recentLocations; +} + +void SettingsManager::clearRecentLocations() +{ + m_settings->remove("recentLocations"); + m_settings->sync(); } \ No newline at end of file diff --git a/src/settingsmanager.h b/src/settingsmanager.h index 8367f54..e48e03a 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -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 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; diff --git a/src/virtuallocationwidget.cpp b/src/virtuallocationwidget.cpp index 8582d44..4d781b7 100644 --- a/src/virtuallocationwidget.cpp +++ b/src/virtuallocationwidget.cpp @@ -22,6 +22,7 @@ #include "devdiskimagehelper.h" #include "devdiskmanager.h" #include "iDescriptor.h" +#include "settingsmanager.h" #include #include #include @@ -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 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(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(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(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 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 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); + } } diff --git a/src/virtuallocationwidget.h b/src/virtuallocationwidget.h index 60d806d..b63bc91 100644 --- a/src/virtuallocationwidget.h +++ b/src/virtuallocationwidget.h @@ -20,11 +20,14 @@ #ifndef VIRTUAL_LOCATION_H #define VIRTUAL_LOCATION_H +#include "devdiskimagehelper.h" #include "iDescriptor.h" +#include #include #include #include #include +#include #include 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 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 \ No newline at end of file diff --git a/src/wirelessgalleryimportwidget.cpp b/src/wirelessgalleryimportwidget.cpp index 294e06c..ae174e3 100644 --- a/src/wirelessgalleryimportwidget.cpp +++ b/src/wirelessgalleryimportwidget.cpp @@ -37,6 +37,7 @@ WirelessGalleryImportWidget::WirelessGalleryImportWidget(QWidget *parent) m_loadingLabel(nullptr), m_tutorialLayout(nullptr) { setupUI(); + setMinimumSize(800, 600); QTimer::singleShot(100, this, &WirelessGalleryImportWidget::setupTutorialVideo); }