From ebd256eae0dd6a1511aac0fe371b8a66a8c16847 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Sun, 23 Nov 2025 04:47:38 +0000 Subject: [PATCH] refactor AppImage deployment and enhance device pairing logic - Updated AppImage zip path in build workflow. - Updated submodule references for ipatool-go and zupdater. - Fix deploy-appimage.sh by manully deploying geoservices plugin - Added retry logic for app downloads in AppDownloadBaseDialog. - Fixed cancel download functionality in AppStoreManager. - Added default jailbroken root password settings in SettingsManager and UI. - Updated device sidebar item selection handling. - General code cleanup and UI improvements across various components. --- .github/workflows/build-linux.yml | 2 +- lib/ipatool-go | 2 +- lib/zupdater | 2 +- scripts/deploy-appimage.sh | 33 ++++++++++++++- src/appcontext.cpp | 52 ++++++++++++----------- src/appdownloadbasedialog.cpp | 68 ++++++++++++++++++++++--------- src/appdownloadbasedialog.h | 5 ++- src/appstoremanager.cpp | 10 +++++ src/appstoremanager.h | 11 ++--- src/devdiskimagehelper.cpp | 13 +++--- src/devdiskmanager.cpp | 10 ++++- src/devicesidebarwidget.cpp | 42 +++++++++++++++---- src/devicesidebarwidget.h | 13 +++++- src/iDescriptor.h | 6 ++- src/mainwindow.cpp | 11 +++-- src/settingsmanager.cpp | 13 ++++++ src/settingsmanager.h | 3 ++ src/settingswidget.cpp | 40 ++++++++++++++++++ src/settingswidget.h | 3 ++ src/sshterminalwidget.cpp | 8 +++- src/toolboxwidget.cpp | 4 -- 21 files changed, 269 insertions(+), 82 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 010c612..65adca0 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -172,4 +172,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: iDescriptor-AppImage - path: iDescriptor-*.AppImage + path: iDescriptor-*.AppImage.zip diff --git a/lib/ipatool-go b/lib/ipatool-go index 5affabd..6872f99 160000 --- a/lib/ipatool-go +++ b/lib/ipatool-go @@ -1 +1 @@ -Subproject commit 5affabd2b4482c66201d1de5eca8c0c008d2a15e +Subproject commit 6872f99821ee24c04d6ae816caae2e75a93449d7 diff --git a/lib/zupdater b/lib/zupdater index 95c6759..3821cf8 160000 --- a/lib/zupdater +++ b/lib/zupdater @@ -1 +1 @@ -Subproject commit 95c675924977dbf04a7223662aa6942fdfb3fce1 +Subproject commit 3821cf82c850b2e90bdc78a926b7e533b96466f7 diff --git a/scripts/deploy-appimage.sh b/scripts/deploy-appimage.sh index 9ea75ac..a8d511d 100755 --- a/scripts/deploy-appimage.sh +++ b/scripts/deploy-appimage.sh @@ -1,6 +1,14 @@ #!/bin/bash # if you get errors try -# QMAKE=/usr/lib/qt6/bin/qmake NO_STRIP=1 ./scripts/deploy-appimage.sh 1.0.0 +# QMAKE=/usr/lib/qt6/bin/qmake NO_STRIP=1 ./scripts/deploy-appimage.sh v1.0.0 +# or even more explicit +#export QT_HOME=~/Qt/$YOUR_QT_VERSION/gcc_64 +#export PATH="$QT_HOME/bin:$PATH" +#export LD_LIBRARY_PATH="$QT_HOME/lib" +#export QML2_IMPORT_PATH="$QT_HOME/qml" +#export QT_PLUGIN_PATH="$QT_HOME/plugins" +#QMAKE="$QT_HOME/bin/qmake6" ./scripts/deploy-appimage.sh v0.1.0 + set -e VERSION=$1 if [ -z "$VERSION" ]; then @@ -135,9 +143,29 @@ chmod +x "$APPDIR/apprun-hooks/linuxdeploy-plugin-env.sh" # .desktop file cp iDescriptor.desktop "$APPDIR/usr/share/applications/" +# Manually deploy geoservices plugins (workaround for linuxdeploy-plugin-qt not finding them) +if [ -n "$Qt6_DIR" ] && [ -d "$Qt6_DIR/plugins/geoservices" ]; then + echo "Manually deploying geoservices plugins from $Qt6_DIR/plugins/geoservices" + mkdir -p "$APPDIR/usr/plugins/geoservices" + cp -v "$Qt6_DIR/plugins/geoservices"/*.so "$APPDIR/usr/plugins/geoservices/" || echo "Warning: Could not copy geoservices plugins" + + echo "Setting RPATH for geoservices plugins" + for plugin in "$APPDIR/usr/plugins/geoservices"/*.so; do + if [ -f "$plugin" ]; then + echo "Setting rpath for $plugin" + patchelf --set-rpath '$ORIGIN/../../lib' "$plugin" + fi + done +else + echo "Warning: Could not find geoservices plugins directory" + echo "Qt6_DIR=$Qt6_DIR" + echo "QT_HOME=$QT_HOME" +fi + export LD_LIBRARY_PATH="$APPDIR/usr/local/lib:$LD_LIBRARY_PATH" export LINUXDEPLOY_EXCLUDED_LIBRARIES="*sql*" export QML_SOURCES_PATHS="./qml" +export EXTRA_QT_MODULES="geoservices;position" ./linuxdeploy-x86_64.AppImage \ @@ -154,7 +182,8 @@ APPIMAGE_FILE=$(find . -maxdepth 1 -name "iDescriptor*.AppImage") if [ -n "$APPIMAGE_FILE" ]; then mv "$APPIMAGE_FILE" "iDescriptor-${VERSION}-Linux_x86_64.AppImage" chmod +x "iDescriptor-${VERSION}-Linux_x86_64.AppImage" - echo "Renamed AppImage to iDescriptor-${VERSION}-Linux_x86_64.AppImage" + zip -r "iDescriptor-${VERSION}-Linux_x86_64.AppImage.zip" "iDescriptor-${VERSION}-Linux_x86_64.AppImage" + echo "AppImage created and zipped: iDescriptor-${VERSION}-Linux_x86_64.AppImage.zip" else echo "Error: Could not find generated AppImage file." exit 1 diff --git a/src/appcontext.cpp b/src/appcontext.cpp index fe8e832..e7b9ab6 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -56,9 +56,31 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, m_pendingDevices.append(udid); emit devicePasswordProtected(udid); emit deviceChange(); - // After 30 seconds, if the device is still pending, - // consider the pairing expired - QTimer::singleShot(30000, this, [this, udid]() { + QTimer::singleShot( + SettingsManager::sharedInstance()->connectionTimeout() * + 1000, + this, [this, udid]() { + if (m_pendingDevices.contains(udid)) { + qDebug() << "Pairing expired for device UDID: " + << udid; + m_pendingDevices.removeAll(udid); + emit devicePairingExpired(udid); + emit deviceChange(); + } + }); + } + } else if (initResult.error == + LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING || + initResult.error == LOCKDOWN_E_INVALID_HOST_ID) { + m_pendingDevices.append(udid); + emit devicePairPending(udid); + emit deviceChange(); + QTimer::singleShot( + SettingsManager::sharedInstance()->connectionTimeout() * + 1000, + this, [this, udid]() { + qDebug() + << "Pairing timer fired for device UDID: " << udid; if (m_pendingDevices.contains(udid)) { qDebug() << "Pairing expired for device UDID: " << udid; @@ -67,24 +89,6 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, emit deviceChange(); } }); - } - } else if (initResult.error == - LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING || - initResult.error == LOCKDOWN_E_INVALID_HOST_ID) { - m_pendingDevices.append(udid); - emit devicePairPending(udid); - emit deviceChange(); - // After 30 seconds, if the device is still pending, - // consider the pairing expired - QTimer::singleShot(30000, this, [this, udid]() { - qDebug() << "Pairing timer fired for device UDID: " << udid; - if (m_pendingDevices.contains(udid)) { - qDebug() << "Pairing expired for device UDID: " << udid; - m_pendingDevices.removeAll(udid); - emit devicePairingExpired(udid); - emit deviceChange(); - } - }); } else { qDebug() << "Unhandled error for device UDID: " << udid << " Error code: " << initResult.error; @@ -104,7 +108,6 @@ void AppContext::addDevice(QString udid, idevice_connection_type conn_type, }; m_devices[device->udid] = device; if (addType == AddType::Regular) { - // Apply settings-based behaviors SettingsManager::sharedInstance()->doIfEnabled( SettingsManager::Setting::AutoRaiseWindow, []() { if (MainWindow *mainWindow = MainWindow::sharedInstance()) { @@ -173,6 +176,8 @@ void AppContext::removeDevice(QString _udid) if (device->afcClient) afc_client_free(device->afcClient); + if (device->afc2Client) + afc_client_free(device->afc2Client); idevice_free(device->device); delete device->mutex; delete device; @@ -285,7 +290,8 @@ void AppContext::setCurrentDeviceSelection(const DeviceSelection &selection) << " Type:" << selection.type << " UDID:" << QString::fromStdString(selection.udid) << " ECID:" << selection.ecid << " Section:" << selection.section; - if (m_currentSelection.udid == selection.udid && + if (m_currentSelection.type == selection.type && + m_currentSelection.udid == selection.udid && m_currentSelection.ecid == selection.ecid && m_currentSelection.section == selection.section) { qDebug() << "setCurrentDeviceSelection: No change in selection"; diff --git a/src/appdownloadbasedialog.cpp b/src/appdownloadbasedialog.cpp index 9725d38..04d508b 100644 --- a/src/appdownloadbasedialog.cpp +++ b/src/appdownloadbasedialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,7 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName, const QString &bundleId, QWidget *parent) : QDialog(parent), m_appName(appName), m_downloadProcess(nullptr), - m_progressTimer(nullptr) + m_progressTimer(nullptr), m_bundleId(bundleId) { // Common UI: progress bar and action button m_layout = new QVBoxLayout(this); @@ -73,7 +74,6 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, int index, bool promptToOpenDir) { - bool acquireLicense = true; if (bundleId.isEmpty()) { QMessageBox::critical(this, "Error", "Bundle ID not provided."); @@ -86,7 +86,13 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, m_actionButton->setEnabled(false); m_operationInProgress = true; + tryToDownload(bundleId, outputDir, promptToOpenDir); +} +void AppDownloadBaseDialog::tryToDownload(const QString &bundleId, + const QString &outputDir, + bool promptToOpenDir) +{ AppStoreManager *manager = AppStoreManager::sharedInstance(); if (!manager) { QMessageBox::critical(this, "Error", @@ -94,34 +100,44 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, reject(); return; } + bool acquireLicense = true; + m_operationInProgress = true; + QPointer safeThis = this; - auto progressCallback = [this](long long current, long long total) { + auto progressCallback = [safeThis](long long current, long long total) { + if (!safeThis) { + return; + } int percentage = 0; if (total > 0) { percentage = static_cast((current * 100) / total); } - updateProgressBar(percentage); + safeThis->updateProgressBar(percentage); }; manager->downloadApp( bundleId, outputDir, "", acquireLicense, - [this, promptToOpenDir, outputDir](int result) { - m_operationInProgress = false; + [safeThis, promptToOpenDir, outputDir](int result) { + if (!safeThis) { + return; + } + safeThis->m_operationInProgress = false; if (result == 0) { // Success - emit downloadFinished(true, "Success"); - m_progressBar->setValue(100); + emit safeThis->downloadFinished(true, "Success"); + if (safeThis->m_progressBar) + safeThis->m_progressBar->setValue(100); if (promptToOpenDir) { if (QMessageBox::Yes == QMessageBox::question( - this, "Download Successful", + safeThis, "Download Successful", QString("Successfully downloaded. Would you like " "to open the output directory: %1?") .arg(outputDir))) { QDir dir(outputDir); if (!dir.exists()) { QMessageBox::warning( - this, "Directory Not Found", + safeThis, "Directory Not Found", QString("The directory %1 does not exist.") .arg(outputDir)); } else { @@ -129,18 +145,28 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, QUrl::fromLocalFile(outputDir)); } } - accept(); } + safeThis->accept(); } else { // Failure - emit downloadFinished(false, "Failed"); + // 3 attempts + if (safeThis->m_tries < 3) { + safeThis->m_tries++; + qDebug() + << "Retrying download for" + safeThis->m_bundleId + + "Attempt:" + QString::number(safeThis->m_tries); + safeThis->tryToDownload(safeThis->m_bundleId, outputDir, + promptToOpenDir); + return; + } + emit safeThis->downloadFinished(false, "Failed"); // if (promptToOpenDir) QMessageBox::critical( - this, "Download Failed", + safeThis, "Download Failed", QString("Failed to download %1. Try signing out and back " "in. Error code: %2") - .arg(m_appName) + .arg(safeThis->m_appName) .arg(result)); - reject(); + safeThis->reject(); } }, progressCallback); @@ -148,11 +174,13 @@ void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId, void AppDownloadBaseDialog::reject() { - // FIXME: we need to cancel download if it gets closed - // if (m_operationInProgress) { - // AppStoreManager *manager = AppStoreManager::sharedInstance(); - // m_operationInProgress = false; - // } + if (m_operationInProgress) { + AppStoreManager *manager = AppStoreManager::sharedInstance(); + if (manager) { + manager->cancelDownload(m_bundleId); + } + m_operationInProgress = false; + } cleanup(); QDialog::reject(); diff --git a/src/appdownloadbasedialog.h b/src/appdownloadbasedialog.h index 28486f1..79bbe2e 100644 --- a/src/appdownloadbasedialog.h +++ b/src/appdownloadbasedialog.h @@ -49,6 +49,8 @@ protected: const QString &appName, const QString &outputDir); void addProgressBar(int index); + void tryToDownload(const QString &bundleId, const QString &outputDir, + bool promptToOpenDir); QProgressBar *m_progressBar; QTimer *m_progressTimer; QProcess *m_downloadProcess; @@ -56,7 +58,8 @@ protected: QPushButton *m_actionButton; QVBoxLayout *m_layout; bool m_operationInProgress = false; - + QString m_bundleId; + uint m_tries = 0; private slots: void cleanup(); }; diff --git a/src/appstoremanager.cpp b/src/appstoremanager.cpp index 46733b4..76e3132 100644 --- a/src/appstoremanager.cpp +++ b/src/appstoremanager.cpp @@ -253,4 +253,14 @@ void AppStoreManager::downloadApp( callback(result); }); watcher->setFuture(future); +} + +void AppStoreManager::cancelDownload(const QString &bundleId) +{ + if (!m_initialized) { + return; + } + qDebug() << "[AppStoreManager::cancelDownload] : Cancelling download for" + << bundleId; + IpaToolCancelDownload(bundleId.toUtf8().data()); } \ No newline at end of file diff --git a/src/appstoremanager.h b/src/appstoremanager.h index 1a0e18e..595a3b8 100644 --- a/src/appstoremanager.h +++ b/src/appstoremanager.h @@ -43,11 +43,12 @@ public: void searchApps( const QString &searchTerm, int limit, std::function callback); - void downloadApp(const QString &bundleId, const QString &outputDir, - const QString &externalVersionId, bool acquireLicense, - std::function callback, - std::function - progressCallback = nullptr); + void + downloadApp(const QString &bundleId, const QString &outputPath, + const QString &externalVersionId, bool acquireLicense, + std::function completionCallback, + std::function progressCallback); + void cancelDownload(const QString &bundleId); signals: void loginSuccessful(const QJsonObject &accountInfo); diff --git a/src/devdiskimagehelper.cpp b/src/devdiskimagehelper.cpp index dd5b108..56bb589 100644 --- a/src/devdiskimagehelper.cpp +++ b/src/devdiskimagehelper.cpp @@ -35,6 +35,11 @@ DevDiskImageHelper::DevDiskImageHelper(iDescriptorDevice *device, setAttribute(Qt::WA_DeleteOnClose); setWindowTitle("Developer Disk Image - iDescriptor"); setupUI(); + + connect(this, &QDialog::accepted, this, + [this]() { emit mountingCompleted(true); }); + connect(this, &QDialog::rejected, this, + [this]() { emit mountingCompleted(false); }); } void DevDiskImageHelper::setupUI() @@ -55,8 +60,8 @@ void DevDiskImageHelper::setupUI() // Status label m_statusLabel = new QLabel("Checking developer disk image..."); - m_statusLabel->setAlignment(Qt::AlignCenter); m_statusLabel->setWordWrap(true); + m_statusLabel->setAlignment(Qt::AlignCenter); mainLayout->addWidget(m_statusLabel); // Button layout @@ -107,6 +112,7 @@ void DevDiskImageHelper::start() finishWithError("Failed to download compatible image."); } }); + qDebug() << "isMountAvailable:" << isMountAvailable; if (!isMountAvailable) { finishWithError("Failed to download compatible image."); } @@ -273,14 +279,11 @@ void DevDiskImageHelper::showStatus(const QString &message, bool isError) void DevDiskImageHelper::finishWithSuccess() { m_loadingIndicator->stop(); - emit mountingCompleted(true); - - QTimer::singleShot(0, this, [this]() { accept(); }); + accept(); } void DevDiskImageHelper::finishWithError(const QString &errorMessage) { m_loadingIndicator->stop(); showStatus(errorMessage, true); - emit mountingCompleted(false); } diff --git a/src/devdiskmanager.cpp b/src/devdiskmanager.cpp index 13eef5d..d2d110d 100644 --- a/src/devdiskmanager.cpp +++ b/src/devdiskmanager.cpp @@ -72,8 +72,7 @@ void DevDiskManager::populateImageList() "Image list will be empty until network fetch succeeds."; } } - QUrl url("https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/" - "heads/main/DeveloperDiskImages.json"); + QUrl url(DEVELOPER_DISK_IMAGE_JSON_URL); QNetworkRequest request(url); auto *reply = m_networkManager->get(request); @@ -335,6 +334,13 @@ bool DevDiskManager::downloadCompatibleImage(iDescriptorDevice *device, QList images = parseImageList(path, deviceMajorVersion, deviceMinorVersion, "", 0); + if (images.isEmpty()) { + qDebug() << "No images found for device version:" << deviceMajorVersion + << "." << deviceMinorVersion; + callback(false); + return false; + } + for (const ImageInfo &info : images) { if (info.compatibility != ImageCompatibility::Compatible && info.compatibility != ImageCompatibility::MaybeCompatible) { diff --git a/src/devicesidebarwidget.cpp b/src/devicesidebarwidget.cpp index c140707..d3afda0 100644 --- a/src/devicesidebarwidget.cpp +++ b/src/devicesidebarwidget.cpp @@ -141,15 +141,11 @@ void DeviceSidebarItem::setupUI() updateToggleButton(); toggleCollapse(); - setStyleSheet("DeviceSidebarItem { border: " - "1px solid #e0e0e0; border-radius: 5px; }"); + setSelected(false); } void DeviceSidebarItem::setSelected(bool selected) { - if (m_selected == selected) - return; - m_selected = selected; // todo : bug the first device selected style is not applied if (selected) { @@ -233,8 +229,6 @@ void RecoveryDeviceSidebarItem::setupUI() mainLayout->addWidget(headerWidget); - // Set initial style - // Set initial style setStyleSheet("RecoveryDeviceSidebarItem { border: " "1px solid #e0e0e0; border-radius: 5px; }"); } @@ -322,6 +316,9 @@ DevicePendingSidebarItem * DeviceSidebarWidget::addPendingDevice(const QString &uuid) { DevicePendingSidebarItem *item = new DevicePendingSidebarItem(uuid, this); + connect(item, &DevicePendingSidebarItem::clicked, this, [this, uuid]() { + onItemSelected(DeviceSelection::pending(uuid.toStdString())); + }); m_pendingItems[uuid.toStdString()] = item; m_contentLayout->insertWidget(m_contentLayout->count() - 1, item); return item; @@ -390,6 +387,9 @@ void DeviceSidebarWidget::updateSelection() for (auto item : m_recoveryItems) { item->setSelected(false); } + for (auto item : m_pendingItems) { + item->setSelected(false); + } // Set selection based on current selection if (m_currentSelection.type == DeviceSelection::Normal && @@ -398,15 +398,18 @@ void DeviceSidebarWidget::updateSelection() } else if (m_currentSelection.type == DeviceSelection::Recovery && m_recoveryItems.contains(m_currentSelection.ecid)) { m_recoveryItems[m_currentSelection.ecid]->setSelected(true); + } else if (m_currentSelection.type == DeviceSelection::Pending && + m_pendingItems.contains(m_currentSelection.udid)) { + m_pendingItems[m_currentSelection.udid]->setSelected(true); } } DevicePendingSidebarItem::DevicePendingSidebarItem(const QString &udid, QWidget *parent) - : QFrame(parent) + : QFrame(parent), m_udid(udid) { QHBoxLayout *layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + layout->setContentsMargins(10, 10, 10, 10); layout->setSpacing(1); QProcessIndicator *spinner = new QProcessIndicator(this); @@ -420,4 +423,25 @@ DevicePendingSidebarItem::DevicePendingSidebarItem(const QString &udid, layout->addWidget(spinner); setLayout(layout); + setSelected(false); +} + +void DevicePendingSidebarItem::setSelected(bool selected) +{ + m_selected = selected; + + if (selected) { + setStyleSheet(QString("DevicePendingSidebarItem { border: " + "2px solid %1; border-radius: 5px; }") + .arg(COLOR_BLUE.name())); + } else { + setStyleSheet("DevicePendingSidebarItem { border: " + "1px solid #e0e0e0; border-radius: 5px; }"); + } +} + +void DevicePendingSidebarItem::mousePressEvent(QMouseEvent *event) +{ + emit clicked(); + QFrame::mousePressEvent(event); } \ No newline at end of file diff --git a/src/devicesidebarwidget.h b/src/devicesidebarwidget.h index d54ac6c..1ff9a0e 100644 --- a/src/devicesidebarwidget.h +++ b/src/devicesidebarwidget.h @@ -82,9 +82,20 @@ class DevicePendingSidebarItem : public QFrame { Q_OBJECT public: - explicit DevicePendingSidebarItem(const QString &deviceName, + explicit DevicePendingSidebarItem(const QString &udid, QWidget *parent = nullptr); + void setSelected(bool selected); + bool isSelected() const { return m_selected; } + signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + QString m_udid; + bool m_selected = false; }; #endif // DEVICEPENDINGSIDEBARITEM_H diff --git a/src/iDescriptor.h b/src/iDescriptor.h index 30b2ca7..defb1cf 100644 --- a/src/iDescriptor.h +++ b/src/iDescriptor.h @@ -41,7 +41,8 @@ #define TOOL_NAME "iDescriptor" #define APP_LABEL "iDescriptor" -#define APP_COPYRIGHT "© 2025 Uncore. All rights reserved." +#define APP_COPYRIGHT \ + "© 2025 The iDescriptor Project contributors. See AUTHORS for details." #define AFC2_SERVICE_NAME "com.apple.afc2" #define RECOVERY_CLIENT_CONNECTION_TRIES 3 #define APPLE_VENDOR_ID 0x05ac @@ -49,6 +50,9 @@ #define SPONSORS_JSON_URL \ "https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/heads/" \ "main/sponsors.json" +#define DEVELOPER_DISK_IMAGE_JSON_URL \ + "https://raw.githubusercontent.com/iDescriptor/iDescriptor/refs/heads/" \ + "main/DeveloperDiskImages.json" // This is because afc_read_directory accepts "/var/mobile/Media" as "/" #define POSSIBLE_ROOT "../../../../" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 71ed468..6ccdcdc 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -299,8 +299,10 @@ MainWindow::MainWindow(QWidget *parent) true, false, true, - "AppImage is not updateable.New version is downloaded to " - "\"Downloads\".You can start using the new version by launching it " + "AppImages we ship are not updateable. New version is downloaded " + "to " + "\"Downloads\". You can start using the new version by launching " + "it " "from there. You can delete this AppImage version if you like.", "Update downloaded would you like to quit and open the new " "version?", @@ -337,8 +339,9 @@ void MainWindow::createMenus() QAction *aboutAct = new QAction("&About iDescriptor", this); connect(aboutAct, &QAction::triggered, this, [this]() { - QMessageBox::about(this, "iDescriptor", - "A free and open-source idevice management tool."); + QMessageBox::about( + this, "iDescriptor", + "A free, open-source, and cross-platform iDevice management tool."); }); actionsMenu->addAction(aboutAct); #endif diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index ea01cdd..5b7158d 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -179,6 +179,18 @@ void SettingsManager::setShowKeychainDialog(bool show) m_settings->sync(); } +QString SettingsManager::defaultJailbrokenRootPassword() const +{ + return m_settings->value("defaultJailbrokenRootPassword", "alpine") + .toString(); +} + +void SettingsManager::setDefaultJailbrokenRootPassword(const QString &password) +{ + m_settings->setValue("defaultJailbrokenRootPassword", password); + m_settings->sync(); +} + void SettingsManager::doIfEnabled(Setting setting, std::function action) { bool shouldExecute = false; @@ -222,6 +234,7 @@ void SettingsManager::resetToDefaults() setTheme("System Default"); setConnectionTimeout(30); setShowKeychainDialog(true); + setDefaultJailbrokenRootPassword("alpine"); } void SettingsManager::saveFavoritePlace(const QString &path, diff --git a/src/settingsmanager.h b/src/settingsmanager.h index 09bd320..f0e3208 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -89,6 +89,9 @@ public: bool showKeychainDialog() const; void setShowKeychainDialog(bool show); + QString defaultJailbrokenRootPassword() const; + void setDefaultJailbrokenRootPassword(const QString &password); + // Utility method for conditional execution void doIfEnabled(Setting setting, std::function action); diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp index 641cf30..f38002e 100644 --- a/src/settingswidget.cpp +++ b/src/settingswidget.cpp @@ -134,6 +134,38 @@ void SettingsWidget::setupUI() securityLayout->addWidget(m_useUnsecureBackend); scrollLayout->addWidget(securityGroup); + // === JAILBROKEN SETTINGS === + auto *jailbrokenGroup = new QGroupBox("Jailbroken"); + auto *jailbrokenLayout = new QVBoxLayout(jailbrokenGroup); + + // Default jailbroken root password + auto *passwordLayout = new QHBoxLayout(); + passwordLayout->addWidget(new QLabel("Default Jailbroken Root Password:")); + m_defaultJailbrokenRootPassword = new QLineEdit(); + m_defaultJailbrokenRootPassword->setEchoMode(QLineEdit::PasswordEchoOnEdit); + m_defaultJailbrokenRootPassword->setMaximumWidth(200); + m_defaultJailbrokenRootPassword->setToolTip( + "Default password used for SSH root authentication on jailbroken " + "devices: Default is 'alpine'."); + passwordLayout->addWidget(m_defaultJailbrokenRootPassword); + passwordLayout->addStretch(); + jailbrokenLayout->addLayout(passwordLayout); + + scrollLayout->addWidget(jailbrokenGroup); + + scrollLayout->addSpacing(30); + + // Add a footer Author & Version & app info & app description + auto *footerLabel = new QLabel( + QString( + "iDescriptor v%1\n" + "A free, open-source, and cross-platform iDevice management tool.\n" + "© 2025 See AUTHORS for details. Licensed under AGPLv3.") + .arg(APP_VERSION)); + footerLabel->setAlignment(Qt::AlignCenter); + footerLabel->setStyleSheet("color: gray; font-size: 10pt;"); + scrollLayout->addWidget(footerLabel); + // Add stretch to push everything to the top scrollLayout->addStretch(); @@ -188,6 +220,9 @@ void SettingsWidget::loadSettings() m_connectionTimeout->setValue(sm->connectionTimeout()); m_useUnsecureBackend->setChecked(sm->useUnsecureBackend()); + m_defaultJailbrokenRootPassword->setText( + sm->defaultJailbrokenRootPassword()); + // Disable apply button initially m_applyButton->setEnabled(false); } @@ -230,6 +265,9 @@ void SettingsWidget::connectSignals() onSettingChanged(); } }); + + connect(m_defaultJailbrokenRootPassword, &QLineEdit::textChanged, this, + &SettingsWidget::onSettingChanged); } void SettingsWidget::onBrowseButtonClicked() @@ -304,6 +342,8 @@ void SettingsWidget::saveSettings() sm->setTheme(m_themeCombo->currentText()); sm->setConnectionTimeout(m_connectionTimeout->value()); + sm->setDefaultJailbrokenRootPassword( + m_defaultJailbrokenRootPassword->text()); m_applyButton->setEnabled(false); } diff --git a/src/settingswidget.h b/src/settingswidget.h index d52b5c1..36869aa 100644 --- a/src/settingswidget.h +++ b/src/settingswidget.h @@ -63,6 +63,9 @@ private: // Device Connection QSpinBox *m_connectionTimeout; + // Jailbroken + QLineEdit *m_defaultJailbrokenRootPassword; + // Buttons QPushButton *m_checkUpdatesButton; QPushButton *m_resetButton; diff --git a/src/sshterminalwidget.cpp b/src/sshterminalwidget.cpp index 2e20821..f570916 100644 --- a/src/sshterminalwidget.cpp +++ b/src/sshterminalwidget.cpp @@ -19,6 +19,7 @@ #include "sshterminalwidget.h" #include "qprocessindicator.h" +#include "settingsmanager.h" #include #include #include @@ -384,8 +385,11 @@ void SSHTerminalWidget::startSSH(const QString &host, uint16_t port) qDebug() << "SSH connected successfully, attempting authentication..."; - // Authenticate with password - rc = ssh_userauth_password(m_sshSession, nullptr, "alpine"); + QString defaultPassword = + SettingsManager::sharedInstance()->defaultJailbrokenRootPassword(); + QByteArray passwordBytes = defaultPassword.toUtf8(); + rc = + ssh_userauth_password(m_sshSession, nullptr, passwordBytes.constData()); if (rc != SSH_AUTH_SUCCESS) { showError(QString("SSH authentication failed: %1") .arg(ssh_get_error(m_sshSession))); diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 762103f..2bb55d0 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -398,10 +398,6 @@ void ToolboxWidget::onCurrentDeviceChanged(const DeviceSelection &selection) m_currentDevice = AppContext::sharedInstance()->getDevice(selection.udid); } - } else { - // Handle recovery, pending, or no device selection - m_uuid.clear(); - m_currentDevice = nullptr; } }