mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-21 19:35:49 +08:00
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.
This commit is contained in:
@@ -172,4 +172,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: iDescriptor-AppImage
|
||||
path: iDescriptor-*.AppImage
|
||||
path: iDescriptor-*.AppImage.zip
|
||||
|
||||
+1
-1
Submodule lib/ipatool-go updated: 5affabd2b4...6872f99821
+1
-1
Submodule lib/zupdater updated: 95c6759249...3821cf82c8
@@ -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
|
||||
|
||||
+29
-23
@@ -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";
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPointer>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
@@ -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<AppDownloadBaseDialog> 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<int>((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();
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -43,11 +43,12 @@ public:
|
||||
void searchApps(
|
||||
const QString &searchTerm, int limit,
|
||||
std::function<void(bool success, const QString &results)> callback);
|
||||
void downloadApp(const QString &bundleId, const QString &outputDir,
|
||||
const QString &externalVersionId, bool acquireLicense,
|
||||
std::function<void(int result)> callback,
|
||||
std::function<void(long long current, long long total)>
|
||||
progressCallback = nullptr);
|
||||
void
|
||||
downloadApp(const QString &bundleId, const QString &outputPath,
|
||||
const QString &externalVersionId, bool acquireLicense,
|
||||
std::function<void(int)> completionCallback,
|
||||
std::function<void(long long, long long)> progressCallback);
|
||||
void cancelDownload(const QString &bundleId);
|
||||
|
||||
signals:
|
||||
void loginSuccessful(const QJsonObject &accountInfo);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<ImageInfo> 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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
+5
-1
@@ -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 "../../../../"
|
||||
|
||||
+7
-4
@@ -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
|
||||
|
||||
@@ -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<void()> 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,
|
||||
|
||||
@@ -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<void()> action);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@ private:
|
||||
// Device Connection
|
||||
QSpinBox *m_connectionTimeout;
|
||||
|
||||
// Jailbroken
|
||||
QLineEdit *m_defaultJailbrokenRootPassword;
|
||||
|
||||
// Buttons
|
||||
QPushButton *m_checkUpdatesButton;
|
||||
QPushButton *m_resetButton;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "sshterminalwidget.h"
|
||||
#include "qprocessindicator.h"
|
||||
#include "settingsmanager.h"
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
@@ -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)));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user