integrate ipatool fork and enhance download functionality

- Added support for downloading apps using the ipatool-go library.
- Updated AppDownloadBaseDialog to handle download progress and success notifications.
- Simplified startDownloadProcess method to accept bundleId directly.
- Introduced updateProgressBar slot for updating UI during downloads.
- Modified AppsWidget to utilize asynchronous search and download processes.
- Updated CMakeLists.txt to include new library and dependencies.
- Add gallerywidget.cpp and gallerywidget.h to be implemented later
This commit is contained in:
uncor3
2025-08-21 23:16:51 +00:00
parent 7d7b57ee43
commit 181a3365a2
13 changed files with 375 additions and 392 deletions
+1
View File
@@ -7,6 +7,7 @@
"/usr/include/qt/**",
"/usr/local/opt/qt/**",
"${workspaceFolder}/build/Desktop-Debug/iDescriptor_autogen/include",
"${workspaceFolder}/build/Desktop-Debug/src/lib/**",
"/usr/local/include/**",
"/usr/include/python3.13"
],
+159 -155
View File
@@ -1,156 +1,160 @@
{
"files.associations": {
".env*": "dotenv",
"stdio.h": "c",
"string.h": "c",
"stdlib.h": "c",
"_malloc.h": "c",
"*.mdx": "markdown",
"*.scss": "tailwindcss",
"*.x": "objective-c",
"libimobiledevice.h": "c",
"clay.h": "c",
"sdl_main.h": "c",
"sdl.h": "c",
"sdl_ttf.h": "c",
"sdl_image.h": "c",
"iostream": "cpp",
"fstream": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__locale": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__verbose_abort": "cpp",
"array": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"execution": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"locale": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"string_view": "cpp",
"tuple": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"vector": "cpp",
"algorithm": "cpp",
"__hash_table": "cpp",
"__node_handle": "cpp",
"__tree": "cpp",
"any": "cpp",
"charconv": "cpp",
"forward_list": "cpp",
"map": "cpp",
"optional": "cpp",
"span": "cpp",
"unordered_map": "cpp",
"valarray": "cpp",
"*.moc": "cpp",
"unistd.h": "c",
"shared_mutex": "c",
"mobile_image_mounter.h": "c",
"service.h": "c",
"property_list_service.h": "c",
"memory": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"condition_variable": "cpp",
"deque": "cpp",
"future": "cpp",
"latch": "cpp",
"list": "cpp",
"numbers": "cpp",
"print": "cpp",
"queue": "cpp",
"regex": "cpp",
"set": "cpp",
"source_location": "cpp",
"stack": "cpp",
"typeindex": "cpp",
"unordered_set": "cpp",
"codecvt": "cpp",
"atomic": "cpp",
"bit": "cpp",
"chrono": "cpp",
"compare": "cpp",
"concepts": "cpp",
"coroutine": "cpp",
"csignal": "cpp",
"exception": "cpp",
"expected": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"random": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"qvboxlayout": "cpp",
"qsvgrenderer": "cpp",
"printf.h": "c",
"sha.h": "c",
"qwidget": "cpp",
"qtabbar": "cpp",
"qhboxlayout": "cpp",
"qpushbutton": "cpp",
"qtabwidget": "cpp",
"qstring": "cpp",
"qdbusconnection": "cpp",
"qdbusmessage": "cpp",
"qmainwindow": "cpp",
"qlineedit": "cpp",
"qscrollarea": "cpp",
"qlabel": "cpp",
"qdebug": "cpp",
"qobject": "cpp",
"qspaceritem": "cpp",
"qmessagebox": "cpp",
"qtextstream": "cpp",
"qvariant": "cpp",
"qjsondocument": "cpp",
"qjsonobject": "cpp",
"qtimer": "cpp",
"qprogressbar": "cpp",
"qdialogbuttonbox": "cpp",
"qgridlayout": "cpp",
"qdir": "cpp",
"qstandardpaths": "cpp",
"qapplication": "cpp",
"qfileinfo": "cpp"
}
}
"files.associations": {
".env*": "dotenv",
"stdio.h": "c",
"string.h": "c",
"stdlib.h": "c",
"_malloc.h": "c",
"*.mdx": "markdown",
"*.scss": "tailwindcss",
"*.x": "objective-c",
"libimobiledevice.h": "c",
"clay.h": "c",
"sdl_main.h": "c",
"sdl.h": "c",
"sdl_ttf.h": "c",
"sdl_image.h": "c",
"iostream": "cpp",
"fstream": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__locale": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__verbose_abort": "cpp",
"array": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"execution": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"locale": "cpp",
"mutex": "cpp",
"new": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"string_view": "cpp",
"tuple": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"vector": "cpp",
"algorithm": "cpp",
"__hash_table": "cpp",
"__node_handle": "cpp",
"__tree": "cpp",
"any": "cpp",
"charconv": "cpp",
"forward_list": "cpp",
"map": "cpp",
"optional": "cpp",
"span": "cpp",
"unordered_map": "cpp",
"valarray": "cpp",
"*.moc": "cpp",
"unistd.h": "c",
"shared_mutex": "c",
"mobile_image_mounter.h": "c",
"service.h": "c",
"property_list_service.h": "c",
"memory": "cpp",
"cfenv": "cpp",
"cinttypes": "cpp",
"condition_variable": "cpp",
"deque": "cpp",
"future": "cpp",
"latch": "cpp",
"list": "cpp",
"numbers": "cpp",
"print": "cpp",
"queue": "cpp",
"regex": "cpp",
"set": "cpp",
"source_location": "cpp",
"stack": "cpp",
"typeindex": "cpp",
"unordered_set": "cpp",
"codecvt": "cpp",
"atomic": "cpp",
"bit": "cpp",
"chrono": "cpp",
"compare": "cpp",
"concepts": "cpp",
"coroutine": "cpp",
"csignal": "cpp",
"exception": "cpp",
"expected": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"random": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"format": "cpp",
"ranges": "cpp",
"semaphore": "cpp",
"stdfloat": "cpp",
"stop_token": "cpp",
"text_encoding": "cpp",
"thread": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"qvboxlayout": "cpp",
"qsvgrenderer": "cpp",
"printf.h": "c",
"sha.h": "c",
"qwidget": "cpp",
"qtabbar": "cpp",
"qhboxlayout": "cpp",
"qpushbutton": "cpp",
"qtabwidget": "cpp",
"qstring": "cpp",
"qdbusconnection": "cpp",
"qdbusmessage": "cpp",
"qmainwindow": "cpp",
"qlineedit": "cpp",
"qscrollarea": "cpp",
"qlabel": "cpp",
"qdebug": "cpp",
"qobject": "cpp",
"qspaceritem": "cpp",
"qmessagebox": "cpp",
"qtextstream": "cpp",
"qvariant": "cpp",
"qjsondocument": "cpp",
"qjsonobject": "cpp",
"qtimer": "cpp",
"qprogressbar": "cpp",
"qdialogbuttonbox": "cpp",
"qgridlayout": "cpp",
"qdir": "cpp",
"qstandardpaths": "cpp",
"qapplication": "cpp",
"qfileinfo": "cpp",
"qcoreapplication": "cpp",
"qdialog": "cpp",
"qcombobox": "cpp",
"qtconcurrent": "cpp"
}
}
+4
View File
@@ -120,6 +120,7 @@ file(GLOB PROJECT_SOURCES
)
add_subdirectory(src/core/airplay)
add_subdirectory(src/lib/ipatool-go)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
@@ -129,6 +130,7 @@ find_package(Qt6 REQUIRED COMPONENTS Quick)
find_package(Qt6 REQUIRED COMPONENTS Location)
find_package(Qt6 REQUIRED COMPONENTS Positioning)
find_package(Qt6 REQUIRED COMPONENTS QuickWidgets)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
qt_add_executable(iDescriptor
@@ -182,7 +184,9 @@ target_link_libraries(iDescriptor PRIVATE
PkgConfig::PLIST
PkgConfig::QRENCODE
airplay
ipatool-go
)
target_link_libraries(iDescriptor PRIVATE Qt6::Widgets)
# Add compile definition for source directory
target_compile_definitions(iDescriptor PRIVATE
+81 -130
View File
@@ -1,16 +1,39 @@
#include "appdownloadbasedialog.h"
#include "libipatool-go.h"
#include <QDesktopServices>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QFutureWatcher>
#include <QLabel>
#include <QMessageBox>
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
#include <QtConcurrent/QtConcurrent>
void downloadProgressCallback(long long current, long long total,
void *userData)
{
// Cast the user data back to the dialog instance.
AppDownloadBaseDialog *dialog =
static_cast<AppDownloadBaseDialog *>(userData);
if (dialog) {
int percentage = 0;
if (total > 0) {
percentage = static_cast<int>((current * 100) / total);
}
// Safely call the update method on the GUI thread.
QMetaObject::invokeMethod(dialog, "updateProgressBar",
Qt::QueuedConnection, Q_ARG(int, percentage));
}
}
void AppDownloadBaseDialog::updateProgressBar(int percentage)
{
if (m_progressBar) {
m_progressBar->setValue(percentage);
}
}
void AppDownloadBaseDialog::addProgressBar(int index)
{
@@ -43,146 +66,74 @@ AppDownloadBaseDialog::AppDownloadBaseDialog(const QString &appName,
m_actionButton = nullptr; // Derived classes set this
}
void AppDownloadBaseDialog::startDownloadProcess(const QStringList &args,
void AppDownloadBaseDialog::startDownloadProcess(const QString &bundleId,
const QString &outputDir,
int index)
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString timestamp =
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
QString logFilePath =
QDir::temp().filePath(QString("%1_%2.log").arg(m_appName, timestamp));
bool acquireLicense = true;
QFile *logFile = new QFile(logFilePath);
if (!logFile->open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(this, "Error",
"Failed to open log file for writing.");
if (bundleId.isEmpty()) {
QMessageBox::critical(this, "Error", "Bundle ID not provided.");
reject();
return;
}
logFile->close();
m_downloadProcess = new QProcess(this);
// m_downloadProcess->setProcessEnvironment(env);
// m_downloadProcess->setWorkingDirectory(workingDir);
m_downloadProcess->setStandardOutputFile(logFilePath, QIODevice::Append);
m_downloadProcess->setStandardErrorFile(logFilePath, QIODevice::Append);
m_downloadProcess->start("ipatool", args);
// TODO: handle errors
addProgressBar(index);
m_progressTimer = new QTimer(this);
connect(m_progressTimer, &QTimer::timeout, this,
[this, logFilePath, outputDir]() {
checkDownloadProgress(logFilePath, m_appName, outputDir);
});
connect(m_downloadProcess,
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this, logFilePath, outputDir](int exitCode,
QProcess::ExitStatus exitStatus) {
checkDownloadProgress(logFilePath, m_appName, outputDir);
m_progressTimer->stop();
m_progressTimer->deleteLater();
m_downloadProcess->deleteLater();
// m_progressBar->setValue(100);
// QMessageBox::information(
// this, "Download Complete",
// QString("Successfully downloaded
// %1.").arg(m_appName));
// accept();
// } else {
// QMessageBox::critical(
// this, "Download Failed",
// QString("Failed to download %1. Exit code: %2")
// .arg(m_appName)
// .arg(exitCode));
// reject();
// }
});
m_progressTimer->start(1000);
if (m_actionButton)
m_actionButton->setEnabled(false);
}
void AppDownloadBaseDialog::checkDownloadProgress(const QString &logFilePath,
const QString &appName,
const QString &outputDir)
{
QFile logFile(logFilePath);
if (!logFile.open(QIODevice::ReadOnly | QIODevice::Text))
return;
// // C-style callback function for progress updates from the Go library
// auto progressCallback = [this](long long current, long long total) {
QString fileContents = QString::fromUtf8(logFile.readAll());
logFile.close();
// // Use invokeMethod to call a slot on the GUI thread safely
// // QMetaObject::invokeMethod(this, "updateProgressBar",
// // Qt::QueuedConnection, Q_ARG(int,
// // percentage));
// updateProgressBar(percentage);
// };
int jsonStart = fileContents.indexOf('{');
qDebug() << "JSON Start:" << jsonStart;
if (jsonStart != -1) {
QString jsonString = fileContents.mid(jsonStart);
QJsonParseError parseError;
QJsonDocument doc =
QJsonDocument::fromJson(jsonString.toUtf8(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
qDebug() << "Parsed JSON successfully";
QJsonObject jsonObj = doc.object();
QString level = jsonObj.value("level").toString();
bool success = jsonObj.value("success").toBool();
QFuture<int> future =
QtConcurrent::run([bundleId, outputDir, acquireLicense, this]() {
// Call the Go function directly
return IpaToolDownloadApp(
bundleId.toUtf8().data(), outputDir.toUtf8().data(), "",
acquireLicense, downloadProgressCallback, this);
});
m_progressTimer->stop();
// if (m_actionButton)
// m_actionButton->setEnabled(true);
QFutureWatcher<int> *watcher = new QFutureWatcher<int>(this);
connect(watcher, &QFutureWatcher<int>::finished, this,
[this, watcher, outputDir]() {
int result = watcher->result();
watcher->deleteLater();
if (level == "error") {
QString errorMsg = jsonObj.contains("error")
? jsonObj.value("error").toString()
: "Unknown error";
QMessageBox::critical(
this, "Download Failed",
QString("Failed to download %1. Error: %2")
.arg(m_appName, errorMsg));
reject();
return;
} else if (level == "info" && success) {
m_progressBar->setValue(100);
// QMessageBox::information(
// this, "Download Successful",
// QString("Successfully downloaded %1. Would you like to "
// "open the directory?")
// .arg(m_appName));
if (QMessageBox::Yes ==
QMessageBox::question(
this, "Open Directory",
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",
QString("The directory %1 does not exist.")
.arg(outputDir));
} else {
QDesktopServices::openUrl(
QUrl::fromLocalFile(outputDir));
if (result == 0) { // Success
m_progressBar->setValue(100);
if (QMessageBox::Yes ==
QMessageBox::question(
this, "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",
QString("The directory %1 does not exist.")
.arg(outputDir));
} else {
QDesktopServices::openUrl(
QUrl::fromLocalFile(outputDir));
}
}
accept();
} else { // Failure
QMessageBox::critical(
this, "Download Failed",
QString("Failed to download %1. Error code: %2")
.arg(m_appName)
.arg(result));
reject();
}
accept();
return;
}
}
}
QRegularExpression re(R"(downloading\s+(\d+)%\s+\|)");
QRegularExpressionMatchIterator i = re.globalMatch(fileContents);
int percent = -1;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
percent = match.captured(1).toInt();
}
if (percent != -1) {
m_progressBar->setValue(percent);
}
});
watcher->setFuture(future);
}
+4 -1
View File
@@ -14,8 +14,11 @@ public:
explicit AppDownloadBaseDialog(const QString &appName,
QWidget *parent = nullptr);
public slots:
void updateProgressBar(int percentage);
protected:
void startDownloadProcess(const QStringList &args,
void startDownloadProcess(const QString &bundleId,
const QString &workingDir, int index);
void checkDownloadProgress(const QString &logFilePath,
const QString &appName,
+2 -11
View File
@@ -1,5 +1,6 @@
#include "appdownloaddialog.h"
#include "clickablelabel.h"
#include "libipatool-go.h"
#include <QDesktopServices>
#include <QFileDialog>
#include <QLabel>
@@ -82,15 +83,5 @@ void AppDownloadDialog::onDownloadClicked()
layout()->removeWidget(m_actionButton);
m_actionButton->deleteLater();
QStringList args = {"download",
"-b",
m_bundleId,
"-o",
m_outputDir,
"--purchase",
"--keychain-passphrase",
"iDescriptor",
"--format",
"json"};
startDownloadProcess(args, m_outputDir, buttonIndex);
startDownloadProcess(m_bundleId, m_outputDir, buttonIndex);
}
+12 -12
View File
@@ -112,22 +112,22 @@ void AppInstallDialog::onInstallClicked()
}
m_deviceCombo->setEnabled(false);
QString selectedDevice = m_deviceCombo->currentData().toString();
QStringList args = {"download",
"-b",
m_bundleId,
"-o",
"./",
"--purchase",
"--keychain-passphrase",
"iDescriptor",
"--format",
"json"};
// QStringList args = {"download",
// "-b",
// m_bundleId,
// "-o",
// "./",
// "--purchase",
// "--keychain-passphrase",
// "iDescriptor",
// "--format",
// "json"};
m_actionButton->setEnabled(false);
int buttonIndex = m_layout->indexOf(m_actionButton);
layout()->removeWidget(m_actionButton);
m_actionButton->deleteLater();
m_actionButton = nullptr; // Reset to avoid double deletion
startDownloadProcess(args, QDir::currentPath(), buttonIndex);
// TODO: need to implement the actual installation logic
// startDownloadProcess(args, QDir::currentPath(), buttonIndex);
}
+86 -77
View File
@@ -3,23 +3,35 @@
#include "appdownloadbasedialog.h"
#include "appdownloaddialog.h"
#include "appinstalldialog.h"
#include "libipatool-go.h"
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QComboBox>
#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFile>
#include <QFileDialog>
#include <QFrame>
#include <QFuture>
#include <QFutureWatcher>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QIcon>
#include <QImage>
#include <QInputDialog>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPainter>
#include <QPainterPath>
#include <QPixmap>
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
@@ -27,22 +39,32 @@
#include <QStyle>
#include <QTemporaryDir>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
#include <QAction>
#include <QBuffer>
#include <QDebug>
#include <QImage>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QPainter>
#include <QPainterPath>
#include <QPixmap>
#include <QUrl>
// 2FA callback for login
static char *getAuthCodeCallback()
{
static QByteArray buffer;
QString code;
QMetaObject::invokeMethod(
qApp,
[&]() {
bool ok;
code = QInputDialog::getText(
nullptr, "Two-Factor Authentication",
"Enter the 2FA code:", QLineEdit::Normal, QString(), &ok);
},
Qt::BlockingQueuedConnection);
if (code.isEmpty()) {
return nullptr;
}
buffer = code.toUtf8();
return buffer.data();
}
// Callback: void(QPixmap)
void fetchAppIconFromApple(const QString &bundleId,
@@ -165,7 +187,8 @@ QString LoginDialog::getPassword() const { return m_passwordEdit->text(); }
AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false)
{
m_searchProcess = new QProcess(this);
// m_searchProcess = new QProcess(this);
m_searchWatcher = new QFutureWatcher<QString>(this);
m_debounceTimer = new QTimer(this);
setupUI();
}
@@ -196,42 +219,20 @@ void AppsWidget::setupUI()
m_statusLabel->setStyleSheet("margin-right: 20px;");
// --- Status and Login Button ---
// int init_result = IpaToolInitialize();
// Example: Asynchronous QProcess with interactive input
QProcess process;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
process.setProcessEnvironment(env);
// process.
// TODO: keychain passphrase
process.start("ipatool", QStringList()
<< "auth" << "info" << "--non-interactive"
<< "--format" << "json"
<< "--keychain-passphrase" << "iDescriptor");
process.waitForFinished();
// connect(process, &QProcess::readyReadStandardOutput, [process]() {
// QString output = process->readAllStandardOutput();
// qDebug() << output;
// if (output.contains("Enter 2FA code")) {
// // Show dialog to user, get code, then:
// process->write("123456\n"); // Replace with actual code
// }
// });
// process->start("ipatool auth info --non-interactive --format json
// --keychain-passphrase \"iDescriptor\""); qDebug() << "IpaToolInitialize
// result:" << init_result;
if (process.exitCode() != 0) {
qDebug() << "IpaToolInitialize failed with error code:"
<< process.exitCode();
// TODO: need a singleton for IpaTool
int init_result = IpaToolInitialize();
if (init_result != 0) {
qDebug() << "IpaToolInitialize failed with error code:" << init_result;
m_statusLabel->setText("Failed to initialize");
} else {
qDebug() << "IpaToolInitialize succeeded";
QString jsonAccountInfo = process.readAllStandardOutput();
if (!jsonAccountInfo.isEmpty()) {
char *accountInfoCStr = IpaToolGetAccountInfo();
if (accountInfoCStr) {
QString jsonAccountInfo(accountInfoCStr);
free(accountInfoCStr);
qDebug() << "Account info JSON:" << jsonAccountInfo;
// Parse JSON with error handling
QJsonParseError parseError;
QJsonDocument doc = QJsonDocument::fromJson(
QByteArray(jsonAccountInfo.toUtf8()), &parseError);
@@ -256,8 +257,6 @@ void AppsWidget::setupUI()
qDebug() << "JSON parse error:" << parseError.errorString();
m_statusLabel->setText("Not signed in");
}
// free(jsonAccountInfo); // Free the allocated memory
} else {
m_statusLabel->setText("Not signed in");
}
@@ -326,8 +325,7 @@ void AppsWidget::setupUI()
m_debounceTimer->setSingleShot(true);
connect(m_debounceTimer, &QTimer::timeout, this,
&AppsWidget::performSearch);
connect(m_searchProcess,
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
connect(m_searchWatcher, &QFutureWatcher<QString>::finished, this,
&AppsWidget::onSearchFinished);
}
@@ -501,7 +499,7 @@ void AppsWidget::onDownloadIpaClicked(const QString &name,
void AppsWidget::onLoginClicked()
{
if (m_isLoggedIn) {
// Logout
IpaToolRevokeCredentials();
m_isLoggedIn = false;
m_loginButton->setText("Sign In");
m_statusLabel->setText("Not signed in");
@@ -511,10 +509,23 @@ void AppsWidget::onLoginClicked()
LoginDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
QString email = dialog.getEmail();
if (!email.isEmpty()) {
QString password = dialog.getPassword();
if (email.isEmpty() || password.isEmpty()) {
QMessageBox::warning(this, "Login Failed",
"Email and password cannot be empty.");
return;
}
int result = IpaToolLoginWithCallback(email.toUtf8().data(),
password.toUtf8().data(),
getAuthCodeCallback);
if (result == 0) {
m_isLoggedIn = true;
m_loginButton->setText("Sign Out");
m_statusLabel->setText("Signed in as " + email);
} else {
QMessageBox::warning(
this, "Login Failed",
"Login failed. Please check your credentials and 2FA code.");
}
}
}
@@ -536,9 +547,9 @@ void AppsWidget::onSearchTextChanged() { m_debounceTimer->start(300); }
void AppsWidget::performSearch()
{
if (m_searchProcess->state() == QProcess::Running) {
m_searchProcess->kill();
m_searchProcess->waitForFinished();
if (m_searchWatcher->isRunning()) {
m_searchWatcher->cancel();
m_searchWatcher->waitForFinished();
}
QString searchTerm = m_searchEdit->text().trimmed();
@@ -549,25 +560,23 @@ void AppsWidget::performSearch()
showStatusMessage(QString("Searching for \"%1\"...").arg(searchTerm));
QStringList args;
args << "search" << searchTerm << "--non-interactive"
<< "--keychain-passphrase"
<< "iDescriptor"
<< "--format"
<< "json";
m_searchProcess->start("ipatool", args);
auto searchFn = [searchTerm]() -> QString {
char *resultsCStr = IpaToolSearch(searchTerm.toUtf8().data(), 20);
if (!resultsCStr) {
return QString();
}
QString results(resultsCStr);
free(resultsCStr);
return results;
};
m_searchWatcher->setFuture(QtConcurrent::run(searchFn));
}
void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
void AppsWidget::onSearchFinished()
{
QString jsonOutput = m_searchProcess->readAllStandardOutput();
if (jsonOutput.isEmpty() && exitCode != 0) {
QString errorOutput = m_searchProcess->readAllStandardError();
qDebug() << "Search process failed:" << errorOutput;
showStatusMessage(
QString("Search failed: %1")
.arg(errorOutput.isEmpty() ? "Unknown error" : errorOutput));
QString jsonOutput = m_searchWatcher->result();
if (jsonOutput.isEmpty()) {
showStatusMessage("No apps found or search failed.");
return;
}
@@ -583,15 +592,15 @@ void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
}
QJsonObject rootObj = doc.object();
if (rootObj.value("level").toString() == "error") {
if (!rootObj.value("success").toBool()) {
QString errorMessage =
rootObj.value("error").toString("Unknown search error.");
showStatusMessage(QString("Search error: %1").arg(errorMessage));
return;
}
QJsonArray appsArray = rootObj.value("apps").toArray();
if (appsArray.isEmpty()) {
QJsonArray resultsArray = rootObj.value("results").toArray();
if (resultsArray.isEmpty()) {
showStatusMessage("No apps found.");
return;
}
@@ -606,10 +615,10 @@ void AppsWidget::onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus)
int col = 0;
const int maxCols = 3;
for (const QJsonValue &appValue : appsArray) {
for (const QJsonValue &appValue : resultsArray) {
QJsonObject appObj = appValue.toObject();
QString name = appObj.value("name").toString();
QString bundleId = appObj.value("bundleID").toString();
QString name = appObj.value("trackName").toString();
QString bundleId = appObj.value("bundleId").toString();
QString description = "Version: " + appObj.value("version").toString();
createAppCard(name, bundleId, description, "", gridLayout, row, col);
+3 -3
View File
@@ -5,11 +5,11 @@
#include <QDialog>
#include <QFile>
#include <QFrame>
#include <QFutureWatcher>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QProcess>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
@@ -43,7 +43,7 @@ private slots:
void onDownloadIpaClicked(const QString &name, const QString &bundleId);
void onSearchTextChanged();
void performSearch();
void onSearchFinished(int exitCode, QProcess::ExitStatus exitStatus);
void onSearchFinished();
private:
void setupUI();
@@ -63,7 +63,7 @@ private:
// Search
QLineEdit *m_searchEdit;
QTimer *m_debounceTimer;
QProcess *m_searchProcess;
QFutureWatcher<QString> *m_searchWatcher;
};
#endif // APPSWIDGET_H
+4 -3
View File
@@ -18,10 +18,11 @@ DeviceMenuWidget::DeviceMenuWidget(iDescriptorDevice *device, QWidget *parent)
tabWidget->addTab(new DeviceInfoWidget(device, this), "");
FileExplorerWidget *explorer = new FileExplorerWidget(device, this);
explorer->setMinimumHeight(300);
// FIXME:race condition with lockdownd_client_new_with_handshake
// FileExplorerWidget *explorer = new FileExplorerWidget(device, this);
// explorer->setMinimumHeight(300);
tabWidget->addTab(explorer, "");
// tabWidget->addTab(explorer, "");
setLayout(mainLayout);
}
+3
View File
@@ -0,0 +1,3 @@
#include "gallerywidget.h"
GalleryWidget::GalleryWidget(QWidget *parent) : QWidget{parent} {}
+15
View File
@@ -0,0 +1,15 @@
#ifndef GALLERYWIDGET_H
#define GALLERYWIDGET_H
#include <QWidget>
class GalleryWidget : public QWidget
{
Q_OBJECT
public:
explicit GalleryWidget(QWidget *parent = nullptr);
signals:
};
#endif // GALLERYWIDGET_H
+1
View File
@@ -0,0 +1 @@
/home/uncore/Desktop/clones/ipatool