diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 9602808..57a1346 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -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" ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 8e50a8d..80dd8f5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" - } -} \ No newline at end of file + "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" + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 98b22ec..7703b07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/appdownloadbasedialog.cpp b/src/appdownloadbasedialog.cpp index 5cffa52..84b80a4 100644 --- a/src/appdownloadbasedialog.cpp +++ b/src/appdownloadbasedialog.cpp @@ -1,16 +1,39 @@ #include "appdownloadbasedialog.h" +#include "libipatool-go.h" #include #include -#include -#include -#include +#include #include #include -#include #include #include -#include #include +#include + +void downloadProgressCallback(long long current, long long total, + void *userData) +{ + // Cast the user data back to the dialog instance. + AppDownloadBaseDialog *dialog = + static_cast(userData); + if (dialog) { + int percentage = 0; + if (total > 0) { + percentage = static_cast((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::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 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 *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::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); } \ No newline at end of file diff --git a/src/appdownloadbasedialog.h b/src/appdownloadbasedialog.h index 0d3bd36..85b7167 100644 --- a/src/appdownloadbasedialog.h +++ b/src/appdownloadbasedialog.h @@ -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, diff --git a/src/appdownloaddialog.cpp b/src/appdownloaddialog.cpp index 67deaf9..2fd02ba 100644 --- a/src/appdownloaddialog.cpp +++ b/src/appdownloaddialog.cpp @@ -1,5 +1,6 @@ #include "appdownloaddialog.h" #include "clickablelabel.h" +#include "libipatool-go.h" #include #include #include @@ -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); } diff --git a/src/appinstalldialog.cpp b/src/appinstalldialog.cpp index e2ab7e9..ac47671 100644 --- a/src/appinstalldialog.cpp +++ b/src/appinstalldialog.cpp @@ -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); } diff --git a/src/appswidget.cpp b/src/appswidget.cpp index 2079cc6..3593bdf 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -3,23 +3,35 @@ #include "appdownloadbasedialog.h" #include "appdownloaddialog.h" #include "appinstalldialog.h" +#include "libipatool-go.h" +#include #include +#include #include +#include #include #include #include #include #include +#include +#include #include #include #include +#include +#include +#include #include #include #include #include #include +#include +#include +#include +#include #include -#include #include #include #include @@ -27,22 +39,32 @@ #include #include #include +#include #include #include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// 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(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::of(&QProcess::finished), this, + connect(m_searchWatcher, &QFutureWatcher::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); diff --git a/src/appswidget.h b/src/appswidget.h index cd1fb27..137329c 100644 --- a/src/appswidget.h +++ b/src/appswidget.h @@ -5,11 +5,11 @@ #include #include #include +#include #include #include #include #include -#include #include #include #include @@ -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 *m_searchWatcher; }; #endif // APPSWIDGET_H diff --git a/src/devicemenuwidget.cpp b/src/devicemenuwidget.cpp index 43ba33d..f3aec93 100644 --- a/src/devicemenuwidget.cpp +++ b/src/devicemenuwidget.cpp @@ -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); } diff --git a/src/gallerywidget.cpp b/src/gallerywidget.cpp new file mode 100644 index 0000000..f003c8a --- /dev/null +++ b/src/gallerywidget.cpp @@ -0,0 +1,3 @@ +#include "gallerywidget.h" + +GalleryWidget::GalleryWidget(QWidget *parent) : QWidget{parent} {} diff --git a/src/gallerywidget.h b/src/gallerywidget.h new file mode 100644 index 0000000..853a6bc --- /dev/null +++ b/src/gallerywidget.h @@ -0,0 +1,15 @@ +#ifndef GALLERYWIDGET_H +#define GALLERYWIDGET_H + +#include + +class GalleryWidget : public QWidget +{ + Q_OBJECT +public: + explicit GalleryWidget(QWidget *parent = nullptr); + +signals: +}; + +#endif // GALLERYWIDGET_H diff --git a/src/lib/ipatool-go b/src/lib/ipatool-go new file mode 120000 index 0000000..bd1aec4 --- /dev/null +++ b/src/lib/ipatool-go @@ -0,0 +1 @@ +/home/uncore/Desktop/clones/ipatool \ No newline at end of file