From d6282762b1130714d2a46d8c3a48044b8fac10d4 Mon Sep 17 00:00:00 2001 From: uncor3 Date: Fri, 3 Oct 2025 06:45:34 -0700 Subject: [PATCH] implement ssh connection - Updated AppWidget to utilize QStackedWidget for better UI management, including loading and error states. - Removed unnecessary includes and improved the organization of private methods in AppWidget. - Enhanced DevDiskImagesWidget UI by adding a settings button and improving layout with shadows. - Refactored DeviceInfoWidget to use QGroupBox for better visual grouping of device information. - Replaced QProcess with libssh for SSH connections in JailbrokenWidget, improving reliability and performance. - Added a timer to check SSH data and handle input/output more effectively. - Improved SettingsManager to manage settings dialog display and lifecycle. - Refactored SettingsWidget to be a QDialog for better user experience and removed unnecessary buttons. - Adjusted layout margins across various widgets for a cleaner UI. --- CMakeLists.txt | 38 ++-- src/appswidget.cpp | 275 +++++++++++++++-------------- src/appswidget.h | 23 ++- src/devdiskimageswidget.cpp | 82 ++++++--- src/devdiskimageswidget.h | 21 +-- src/deviceinfowidget.cpp | 64 +------ src/jailbrokenwidget.cpp | 340 ++++++++++++++++++++++-------------- src/jailbrokenwidget.h | 13 +- src/mainwindow.cpp | 40 ++--- src/settingsmanager.cpp | 18 ++ src/settingsmanager.h | 3 + src/settingswidget.cpp | 33 ++-- src/settingswidget.h | 19 +- src/toolboxwidget.cpp | 4 +- 14 files changed, 518 insertions(+), 455 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 012d836..3a382fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,19 @@ else() ) endif() +# Add libssh for SSH connections +find_library(SSH_LIBRARY + NAMES ssh + PATHS ${CUSTOM_LIB_PATH} /usr/lib /usr/lib/x86_64-linux-gnu + REQUIRED +) + +# Apple-specific crypto libraries for SSH +if(APPLE) + find_library(SECURITY_FRAMEWORK Security REQUIRED) + find_library(COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED) +endif() + # Remove frida support for now # find_library(FRIDA_LIBRARY # NAMES frida-core @@ -105,12 +118,6 @@ endif() # REQUIRED # ) -# find_library(SSH_LIBRARY -# NAMES ssh -# PATHS /usr/lib /usr/lib/x86_64-linux-gnu -# REQUIRED -# ) - # find_library(ZIP_LIBRARY # NAMES zip # PATHS /usr/lib /usr/lib/x86_64-linux-gnu @@ -145,10 +152,9 @@ src/*.ui resources.qrc ) -if(MACOS) +if(APPLE) list(APPEND PROJECT_SOURCES - src/platform/macos/*.mm - src/platform/macos/*.h + src/platform/macos.mm ) endif() @@ -195,10 +201,10 @@ target_link_libraries(iDescriptor PRIVATE # ${PLIST_LIBRARY} ${TATSU_LIBRARY} ${IRECOVERY_LIBRARY} - # ${SSL_LIBRARY} - # ${CRYPTO_LIBRARY} + ${SSL_LIBRARY} + ${CRYPTO_LIBRARY} + ${SSH_LIBRARY} # ${FRIDA_LIBRARY} - # ${SSH_LIBRARY} # ${ZIP_LIBRARY} PkgConfig::PUGIXML PkgConfig::USB @@ -209,6 +215,14 @@ target_link_libraries(iDescriptor PRIVATE ipatool-go ) +# Add Apple-specific frameworks for SSH +if(APPLE) + target_link_libraries(iDescriptor PRIVATE + ${SECURITY_FRAMEWORK} + ${COREFOUNDATION_FRAMEWORK} + ) +endif() + # Add compile definition for source directory target_compile_definitions(iDescriptor PRIVATE SOURCE_DIR="${CMAKE_SOURCE_DIR}" diff --git a/src/appswidget.cpp b/src/appswidget.cpp index fe894fd..4660da7 100644 --- a/src/appswidget.cpp +++ b/src/appswidget.cpp @@ -5,18 +5,11 @@ #include "appinstalldialog.h" #include "appstoremanager.h" #include "logindialog.h" -#include #include -#include #include #include -#include -#include #include #include -#include -#include -#include #include #include #include @@ -33,20 +26,16 @@ #include #include #include -#include #include #include -#include #include -#include #include #include #include + // watch for login and logout events AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false) { - // m_searchProcess = new QProcess(this); - m_searchWatcher = new QFutureWatcher(this); m_debounceTimer = new QTimer(this); setupUI(); } @@ -60,7 +49,7 @@ void AppsWidget::setupUI() // Header with login QWidget *headerWidget = new QWidget(); headerWidget->setFixedHeight(60); - headerWidget->setStyleSheet("border-bottom: 1px solid #dee2e6;"); + headerWidget->setStyleSheet("border-bottom: 1px solid #363d32;"); QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget); headerLayout->setContentsMargins(20, 10, 20, 10); @@ -73,12 +62,22 @@ void AppsWidget::setupUI() m_statusLabel = new QLabel("Not signed in"); m_statusLabel->setStyleSheet("margin-right: 20px;"); + m_loginButton = new QPushButton(); + m_searchEdit = new QLineEdit(); + m_searchEdit->setMaximumWidth(400); + m_searchEdit->setStyleSheet("QLineEdit { " + " padding: 8px; " + " border: 1px solid #ccc; " + " border-radius: 4px; " + " font-size: 14px; " + "}"); + // --- Status and Login Button --- m_manager = AppStoreManager::sharedInstance(); if (!m_manager) { qDebug() << "AppStoreManager failed to initialize"; m_statusLabel->setText("Failed to initialize"); - m_loginButton = new QPushButton("Failed to initialize"); + m_loginButton->setText("Failed to initialize"); m_loginButton->setEnabled(false); m_loginButton->setStyleSheet( "background-color: #ccc; color: #666; border: none; border-radius: " @@ -91,17 +90,6 @@ void AppsWidget::setupUI() mainLayout->addWidget(headerWidget); - m_searchEdit = new QLineEdit(); - m_searchEdit->setPlaceholderText(m_isLoggedIn ? "Search for apps..." - : "Sign in to search"); - m_searchEdit->setMaximumWidth(400); - m_searchEdit->setStyleSheet("QLineEdit { " - " padding: 8px; " - " border: 1px solid #ccc; " - " border-radius: 4px; " - " font-size: 14px; " - "}"); - QAction *searchAction = m_searchEdit->addAction( this->style()->standardIcon(QStyle::SP_FileDialogContentsView), QLineEdit::TrailingPosition); @@ -117,23 +105,16 @@ void AppsWidget::setupUI() headerLayout->addWidget(m_statusLabel); headerLayout->addWidget(m_loginButton); - // Scroll area for apps - m_scrollArea = new QScrollArea(); - m_scrollArea->setWidgetResizable(true); - m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_scrollArea->setStyleSheet( - "QScrollArea { background: transparent; border: none; }"); - m_scrollArea->viewport()->setStyleSheet("background: transparent;"); + // Stacked widget for different pages + m_stackedWidget = new QStackedWidget(); + setupDefaultAppsPage(); + setupLoadingPage(); + setupErrorPage(); - m_contentWidget = new QWidget(); - QGridLayout *gridLayout = new QGridLayout(m_contentWidget); - gridLayout->setContentsMargins(20, 20, 20, 20); - gridLayout->setSpacing(20); + mainLayout->addWidget(m_stackedWidget); - populateDefaultApps(); - - m_scrollArea->setWidget(m_contentWidget); - mainLayout->addWidget(m_scrollArea); + // Show default apps initially + showDefaultApps(); // Connections connect(m_loginButton, &QPushButton::clicked, this, &AppsWidget::onLoginClicked); @@ -142,8 +123,6 @@ void AppsWidget::setupUI() m_debounceTimer->setSingleShot(true); connect(m_debounceTimer, &QTimer::timeout, this, &AppsWidget::performSearch); - connect(m_searchWatcher, &QFutureWatcher::finished, this, - &AppsWidget::onSearchFinished); connect(m_manager, &AppStoreManager::loginSuccessful, this, &AppsWidget::onAppStoreInitialized); connect(m_manager, &AppStoreManager::loggedOut, this, @@ -152,8 +131,6 @@ void AppsWidget::setupUI() void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo) { - qDebug() << "AppStoreManager initialized successfully"; - if (accountInfo.contains("success") && accountInfo.value("success").toBool()) { if (accountInfo.contains("email")) { @@ -167,16 +144,103 @@ void AppsWidget::onAppStoreInitialized(const QJsonObject &accountInfo) m_statusLabel->setText("Not signed in"); } - m_loginButton = new QPushButton(m_isLoggedIn ? "Sign Out" : "Sign In"); + m_loginButton->setText(m_isLoggedIn ? "Sign Out" : "Sign In"); m_loginButton->setStyleSheet( "background-color: #007AFF; color: white; border: none; " "border-radius: " "4px; padding: 8px 16px; font-size: 14px;"); + m_searchEdit->setPlaceholderText(m_isLoggedIn ? "Search for apps..." + : "Sign in to search"); +} + +void AppsWidget::setupDefaultAppsPage() +{ + m_defaultAppsPage = new QWidget(); + + // Scroll area for apps + m_scrollArea = new QScrollArea(); + m_scrollArea->setWidgetResizable(true); + m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_scrollArea->setStyleSheet( + "QScrollArea { background: transparent; border: none; }"); + m_scrollArea->viewport()->setStyleSheet("background: transparent;"); + + m_contentWidget = new QWidget(); + QGridLayout *gridLayout = new QGridLayout(m_contentWidget); + gridLayout->setContentsMargins(20, 20, 20, 20); + gridLayout->setSpacing(20); + + m_scrollArea->setWidget(m_contentWidget); + + QVBoxLayout *pageLayout = new QVBoxLayout(m_defaultAppsPage); + pageLayout->setContentsMargins(0, 0, 0, 0); + pageLayout->addWidget(m_scrollArea); + + m_stackedWidget->addWidget(m_defaultAppsPage); +} + +void AppsWidget::setupLoadingPage() +{ + m_loadingPage = new QWidget(); + + QVBoxLayout *loadingLayout = new QVBoxLayout(m_loadingPage); + loadingLayout->setAlignment(Qt::AlignCenter); + + m_loadingIndicator = new QProcessIndicator(); + m_loadingIndicator->setType(QProcessIndicator::line_rotate); + m_loadingIndicator->setFixedSize(64, 32); + + m_loadingLabel = new QLabel("Loading..."); + m_loadingLabel->setAlignment(Qt::AlignCenter); + m_loadingLabel->setStyleSheet( + "font-size: 16px; color: #666; margin-top: 20px;"); + + loadingLayout->addWidget(m_loadingIndicator, 0, Qt::AlignCenter); + loadingLayout->addWidget(m_loadingLabel, 0, Qt::AlignCenter); + + m_stackedWidget->addWidget(m_loadingPage); +} + +void AppsWidget::setupErrorPage() +{ + m_errorPage = new QWidget(); + + QVBoxLayout *errorLayout = new QVBoxLayout(m_errorPage); + errorLayout->setAlignment(Qt::AlignCenter); + + m_errorLabel = new QLabel("Error occurred"); + m_errorLabel->setAlignment(Qt::AlignCenter); + m_errorLabel->setWordWrap(true); + m_errorLabel->setStyleSheet("font-size: 16px; color: #666;"); + + errorLayout->addWidget(m_errorLabel, 0, Qt::AlignCenter); + + m_stackedWidget->addWidget(m_errorPage); +} + +void AppsWidget::showDefaultApps() +{ + clearAppGrid(); + populateDefaultApps(); + m_stackedWidget->setCurrentWidget(m_defaultAppsPage); +} + +void AppsWidget::showLoading(const QString &message) +{ + m_loadingLabel->setText(message); + m_loadingIndicator->start(); + m_stackedWidget->setCurrentWidget(m_loadingPage); +} + +void AppsWidget::showError(const QString &message) +{ + m_loadingIndicator->stop(); + m_errorLabel->setText(message); + m_stackedWidget->setCurrentWidget(m_errorPage); } void AppsWidget::populateDefaultApps() { - clearAppGrid(); QGridLayout *gridLayout = qobject_cast(m_contentWidget->layout()); if (!gridLayout) @@ -221,20 +285,10 @@ void AppsWidget::clearAppGrid() } } -void AppsWidget::showStatusMessage(const QString &message) -{ - clearAppGrid(); - QGridLayout *gridLayout = - qobject_cast(m_contentWidget->layout()); - if (!gridLayout) - return; - - QLabel *statusLabel = new QLabel(message); - statusLabel->setAlignment(Qt::AlignCenter); - statusLabel->setWordWrap(true); - statusLabel->setStyleSheet("font-size: 16px; color: #666;"); - gridLayout->addWidget(statusLabel, 0, 0, 1, -1, Qt::AlignCenter); -} +// void AppsWidget::showStatusMessage(const QString &message) +// { +// showError(message); +// } void AppsWidget::createAppCard(const QString &name, const QString &bundleId, const QString &description, @@ -384,104 +438,48 @@ void AppsWidget::onSearchTextChanged() { m_debounceTimer->start(300); } void AppsWidget::performSearch() { - if (m_searchWatcher->isRunning()) { - m_searchWatcher->cancel(); - m_searchWatcher->waitForFinished(); - } QString searchTerm = m_searchEdit->text().trimmed(); if (searchTerm.isEmpty()) { - populateDefaultApps(); + showDefaultApps(); return; } - showStatusMessage(QString("Searching for \"%1\"...").arg(searchTerm)); + showLoading(QString("Searching for \"%1\"...").arg(searchTerm)); AppStoreManager *manager = AppStoreManager::sharedInstance(); if (!manager) { - showStatusMessage("Failed to initialize App Store manager."); + showError("Failed to initialize App Store manager."); return; } - manager->searchApps( - searchTerm, 20, [this](bool success, const QString &results) { - if (!success || results.isEmpty()) { - showStatusMessage("No apps found or search failed."); - return; - } - - QJsonParseError parseError; - QJsonDocument doc = - QJsonDocument::fromJson(results.toUtf8(), &parseError); - - if (parseError.error != QJsonParseError::NoError) { - qDebug() << "JSON parse error:" << parseError.errorString() - << " on output: " << results; - showStatusMessage("Failed to parse search results."); - return; - } - - QJsonObject rootObj = doc.object(); - if (!rootObj.value("success").toBool()) { - QString errorMessage = - rootObj.value("error").toString("Unknown search error."); - showStatusMessage( - QString("Search error: %1").arg(errorMessage)); - return; - } - - QJsonArray resultsArray = rootObj.value("results").toArray(); - if (resultsArray.isEmpty()) { - showStatusMessage("No apps found."); - return; - } - - clearAppGrid(); - QGridLayout *gridLayout = - qobject_cast(m_contentWidget->layout()); - if (!gridLayout) - return; - - int row = 0; - int col = 0; - const int maxCols = 3; - - for (const QJsonValue &appValue : resultsArray) { - QJsonObject appObj = appValue.toObject(); - 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); - - col++; - if (col >= maxCols) { - col = 0; - row++; - } - } - gridLayout->setRowStretch(gridLayout->rowCount(), 1); - }); + manager->searchApps(searchTerm, 20, + [this](bool success, const QString &results) { + onSearchFinished(success, results); + }); } -void AppsWidget::onSearchFinished() +void AppsWidget::onSearchFinished(bool success, const QString &results) { - QString jsonOutput = m_searchWatcher->result(); - if (jsonOutput.isEmpty()) { - showStatusMessage("No apps found or search failed."); + // FIXME: cancel fetch instead of just ignoring results + QString searchTerm = m_searchEdit->text().trimmed(); + if (searchTerm.isEmpty()) { + showDefaultApps(); + return; + } + + if (!success || results.isEmpty()) { + showError("No apps found or search failed."); return; } QJsonParseError parseError; - QJsonDocument doc = - QJsonDocument::fromJson(jsonOutput.toUtf8(), &parseError); + QJsonDocument doc = QJsonDocument::fromJson(results.toUtf8(), &parseError); if (parseError.error != QJsonParseError::NoError) { qDebug() << "JSON parse error:" << parseError.errorString() - << " on output: " << jsonOutput; - showStatusMessage("Failed to parse search results."); + << " on output: " << results; + showError("Failed to parse search results."); return; } @@ -489,13 +487,13 @@ void AppsWidget::onSearchFinished() if (!rootObj.value("success").toBool()) { QString errorMessage = rootObj.value("error").toString("Unknown search error."); - showStatusMessage(QString("Search error: %1").arg(errorMessage)); + showError(QString("Search error: %1").arg(errorMessage)); return; } QJsonArray resultsArray = rootObj.value("results").toArray(); if (resultsArray.isEmpty()) { - showStatusMessage("No apps found."); + showError("No apps found."); return; } @@ -524,4 +522,5 @@ void AppsWidget::onSearchFinished() } } gridLayout->setRowStretch(gridLayout->rowCount(), 1); + m_stackedWidget->setCurrentWidget(m_defaultAppsPage); } diff --git a/src/appswidget.h b/src/appswidget.h index 45018a5..6f5dd27 100644 --- a/src/appswidget.h +++ b/src/appswidget.h @@ -2,11 +2,10 @@ #define APPSWIDGET_H #include "appstoremanager.h" +#include "qprocessindicator.h" #include #include #include -#include -#include #include #include #include @@ -15,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +31,7 @@ private slots: void onDownloadIpaClicked(const QString &name, const QString &bundleId); void onSearchTextChanged(); void performSearch(); - void onSearchFinished(); + void onSearchFinished(bool success, const QString &results); void onAppStoreInitialized(const QJsonObject &accountInfo); private: @@ -39,10 +39,22 @@ private: void createAppCard(const QString &name, const QString &bundleId, const QString &description, const QString &iconPath, QGridLayout *gridLayout, int row, int col); - void populateDefaultApps(); + void setupDefaultAppsPage(); + void setupLoadingPage(); + void setupErrorPage(); + void showDefaultApps(); + void showLoading(const QString &message = "Loading..."); + void showError(const QString &message); void clearAppGrid(); - void showStatusMessage(const QString &message); + void populateDefaultApps(); + QStackedWidget *m_stackedWidget; + QWidget *m_defaultAppsPage; + QWidget *m_loadingPage; + QWidget *m_errorPage; + QProcessIndicator *m_loadingIndicator; + QLabel *m_loadingLabel; + QLabel *m_errorLabel; QScrollArea *m_scrollArea; QWidget *m_contentWidget; QPushButton *m_loginButton; @@ -53,7 +65,6 @@ private: // Search QLineEdit *m_searchEdit; QTimer *m_debounceTimer; - QFutureWatcher *m_searchWatcher; }; #endif // APPSWIDGET_H diff --git a/src/devdiskimageswidget.cpp b/src/devdiskimageswidget.cpp index 5909d47..f014ae5 100644 --- a/src/devdiskimageswidget.cpp +++ b/src/devdiskimageswidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -52,18 +53,9 @@ DevDiskImagesWidget::DevDiskImagesWidget(iDescriptorDevice *device, void DevDiskImagesWidget::setupUi() { + setWindowTitle("Developer Disk Images - iDescriptor"); auto *layout = new QVBoxLayout(this); - - auto *pathLayout = new QHBoxLayout(); - pathLayout->addWidget(new QLabel("Download Path:")); - m_downloadPathEdit = new QLineEdit(); - m_downloadPathEdit->setReadOnly(true); - pathLayout->addWidget(m_downloadPathEdit); - auto *changeDirButton = new QPushButton("Change..."); - connect(changeDirButton, &QPushButton::clicked, this, - &DevDiskImagesWidget::changeDownloadDirectory); - pathLayout->addWidget(changeDirButton); - layout->addLayout(pathLayout); + layout->setContentsMargins(0, 0, 0, 0); auto *mountLayout = new QHBoxLayout(); mountLayout->addWidget(new QLabel("Device:")); @@ -75,10 +67,55 @@ void DevDiskImagesWidget::setupUi() &DevDiskImagesWidget::onMountButtonClicked); connect(m_check_mountedButton, &QPushButton::clicked, this, &DevDiskImagesWidget::checkMountedImage); + mountLayout->setContentsMargins(10, 10, 10, 10); mountLayout->addWidget(m_mountButton); mountLayout->addWidget(m_check_mountedButton); layout->addLayout(mountLayout); + auto *pathLayout = new QHBoxLayout(); + // main path/info row (no shadow) + auto *pathWidget = new QWidget(); + pathWidget->setLayout(pathLayout); + pathLayout->addWidget( + new QLabel("You can change the download path from settings :")); + QPushButton *openSettingsButton = new QPushButton("Open Settings"); + pathLayout->addWidget(openSettingsButton); + connect(openSettingsButton, &QPushButton::clicked, this, [this]() { + SettingsManager::sharedInstance()->showSettingsDialog(); + }); + pathLayout->setContentsMargins(10, 10, 10, 10); + layout->addWidget(pathWidget); + + // thin centered bottom line + shadow (shadow only applied to this line) + QWidget *lineContainer = new QWidget(); + QHBoxLayout *lineLayout = new QHBoxLayout(lineContainer); + lineLayout->setContentsMargins(0, 0, 0, 0); // adjust centering / width + lineLayout->setSpacing(0); + + QWidget *innerLine = new QWidget(); + innerLine->setFixedHeight(2); // thickness of the visible border + innerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + innerLine->setStyleSheet("background-color: #363d32;"); + innerLine->setLayout(new QHBoxLayout()); + innerLine->layout()->setContentsMargins(0, 0, 0, 0); + innerLine->layout()->setSpacing(0); + + // apply shadow only to the thin line so shadow appears only under bottom + QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); + shadow->setBlurRadius(30); + shadow->setColor(QColor(0, 0, 0, 30)); + shadow->setOffset(0, 6); + innerLine->setGraphicsEffect(shadow); + + // If you want the line to be shorter than full width, give it a max width: + // innerLine->setMaximumWidth( int(width * 0.8) ); // or manage in + // resizeEvent + + lineLayout->addStretch(); + lineLayout->addWidget(innerLine); + lineLayout->addStretch(); + layout->addWidget(lineContainer); + m_stackedWidget = new QStackedWidget(this); layout->addWidget(m_stackedWidget); @@ -87,12 +124,12 @@ void DevDiskImagesWidget::setupUi() m_stackedWidget->addWidget(m_statusLabel); m_imageListWidget = new QListWidget(this); - m_stackedWidget->addWidget(m_imageListWidget); + m_imageListWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_imageListWidget->setStyleSheet( + "QListWidget { background: transparent; border: none; }"); + m_imageListWidget->viewport()->setStyleSheet("background: transparent;"); - // m_downloadPath = - // QDir(QCoreApplication::applicationDirPath()).filePath("devdiskimages"); - m_downloadPathEdit->setText( - SettingsManager::sharedInstance()->devdiskimgpath()); + m_stackedWidget->addWidget(m_imageListWidget); displayImages(); if (DevDiskManager::sharedInstance()->isImageListReady()) { @@ -518,19 +555,6 @@ void DevDiskImagesWidget::mountImage(const QString &version) } } -void DevDiskImagesWidget::changeDownloadDirectory() -{ - // TODO: logic moved to settings manager - // QString dir = QFileDialog::getExistingDirectory( - // this, "Select Download Directory", m_downloadPath, - // QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - // if (!dir.isEmpty() && dir != m_downloadPath) { - // m_downloadPath = dir; - // m_downloadPathEdit->setText(m_downloadPath); - // displayImages(); - // } -} - void DevDiskImagesWidget::closeEvent(QCloseEvent *event) { if (!m_activeDownloads.isEmpty()) { diff --git a/src/devdiskimageswidget.h b/src/devdiskimageswidget.h index 6f9cc5d..2119a7b 100644 --- a/src/devdiskimageswidget.h +++ b/src/devdiskimageswidget.h @@ -2,19 +2,18 @@ #define DEVDISKIMAGESWIDGET_H #include "iDescriptor.h" +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include -class QNetworkAccessManager; -class QNetworkReply; -class QListWidget; -class QStackedWidget; -class QLabel; -class QLineEdit; -class QPushButton; -class QProgressBar; -class QComboBox; - class DevDiskImagesWidget : public QWidget { Q_OBJECT @@ -27,7 +26,6 @@ private slots: void onDownloadButtonClicked(); void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void onFileDownloadFinished(); - void changeDownloadDirectory(); void updateDeviceList(); void onMountButtonClicked(); void onImageListFetched(bool success, @@ -62,7 +60,6 @@ private: QLabel *m_statusLabel; QLabel *m_initialStatusLabel; QWidget *m_errorWidget; - QLineEdit *m_downloadPathEdit; QComboBox *m_deviceComboBox; QPushButton *m_mountButton; QPushButton *m_check_mountedButton; diff --git a/src/deviceinfowidget.cpp b/src/deviceinfowidget.cpp index a20496d..3c33c87 100644 --- a/src/deviceinfowidget.cpp +++ b/src/deviceinfowidget.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -57,15 +58,8 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) // infoLayout->setSpacing(10); // Header - QWidget *headerWidget = new QWidget(); - headerWidget->setObjectName("headerWidget"); - headerWidget->setStyleSheet("QWidget#headerWidget { " - " border: 1px solid #ccc; " - " border-radius: 6px; " - "}"); - + QGroupBox *headerWidget = new QGroupBox(); QHBoxLayout *headerLayout = new QHBoxLayout(headerWidget); - headerLayout->setContentsMargins(10, 10, 10, 10); headerLayout->setSpacing(15); QLabel *devProductType = @@ -132,59 +126,14 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) // Add maximum stretch between header and grid infoLayout->addStretch(); - // --- Neumorphic Grid Widget --- - - // 1. Create a container for the shadows - QWidget *shadowContainer = new QWidget(); - // The container must be transparent to not hide the main window background - shadowContainer->setStyleSheet("background: transparent;"); - // Use a layout to make the gridWidget fill the container - QVBoxLayout *shadowLayout = new QVBoxLayout(shadowContainer); - shadowLayout->setContentsMargins(15, 15, 15, - 15); // Margins to make space for shadows - - // 2. Create the dark (bottom-right) shadow and apply to the container - QGraphicsDropShadowEffect *darkShadow = new QGraphicsDropShadowEffect(); - darkShadow->setBlurRadius(30); - darkShadow->setColor(QColor(0, 0, 0, 70)); // Dark, semi-transparent color - darkShadow->setOffset(0, 0); - shadowContainer->setGraphicsEffect(darkShadow); - - // 3. Create the grid widget (the main content) - QWidget *gridWidget = new QWidget(); - gridWidget->setObjectName("infoGrid"); - - QPalette palette = qApp->palette(); - QColor background = palette.color(QPalette::Window); - - gridWidget->setStyleSheet("QWidget#infoGrid {" - " background-color: " + - background.name() + - ";" - // " background-color: #161d37;" - // " border: 1px solid #29356b;" - " border-radius: 8px;" - "}"); - - // 4. Create the light (top-left) shadow and apply to the grid widget - QGraphicsDropShadowEffect *lightShadow = new QGraphicsDropShadowEffect(); - lightShadow->setBlurRadius(30); - lightShadow->setColor( - QColor(255, 255, 255, 40)); // Light, semi-transparent color - lightShadow->setOffset(0, 0); - gridWidget->setGraphicsEffect(lightShadow); - - // Add gridWidget to the container's layout - shadowLayout->addWidget(gridWidget); - - QGridLayout *gridLayout = - new QGridLayout(gridWidget); // Set layout on gridWidget + QGroupBox *gridContainer = new QGroupBox("Device Information"); + QGridLayout *gridLayout = new QGridLayout(); // Set layout on gridWidget gridLayout->setSpacing(8); gridLayout->setColumnStretch(1, 1); // Allow value column to stretch gridLayout->setColumnStretch( 3, 1); // Allow value column for right side to stretch gridLayout->setContentsMargins(17, 17, 17, 17); - gridWidget->setLayout(gridLayout); + gridContainer->setLayout(gridLayout); QList> infoItems; auto createValueLabel = [](const QString &text) { @@ -292,8 +241,7 @@ DeviceInfoWidget::DeviceInfoWidget(iDescriptorDevice *device, QWidget *parent) } } - infoLayout->addWidget( - shadowContainer); // Add the container to the main layout + infoLayout->addWidget(gridContainer); // infoLayout->addStretch(); // Pushes footer to the bottom // Footer diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index be161ad..5e4e875 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -8,10 +8,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -30,9 +32,9 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} graphicsView->setRenderHint(QPainter::Antialiasing); graphicsView->setMinimumWidth(200); graphicsView->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding); - graphicsView->setStyleSheet("background: transparent; border: none;"); + graphicsView->setStyleSheet("background:transparent; border: none;"); - mainLayout->addWidget(graphicsView, 1); // Stretch factor 1 + mainLayout->addWidget(graphicsView, 1); connect(AppContext::sharedInstance(), &AppContext::deviceAdded, this, [this](iDescriptorDevice *device) { deviceConnected(device); }); @@ -56,53 +58,43 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} rightLayout->addWidget(m_connectButton); setupTerminal(); - rightLayout->addWidget(m_terminal, 1); // Give terminal most of the space + rightLayout->addWidget(m_terminal, 1); - mainLayout->addWidget(rightContainer, 3); // Stretch factor 3 + mainLayout->addWidget(rightContainer, 3); + + // Initialize SSH + ssh_init(); + m_sshSession = nullptr; + m_sshChannel = nullptr; + + // Setup timer for checking SSH data + m_sshTimer = new QTimer(this); + connect(m_sshTimer, &QTimer::timeout, this, + &JailbrokenWidget::checkSshData); } void JailbrokenWidget::setupTerminal() { - m_terminal = new QTermWidget(0, this); // 0 = use default shell + m_terminal = new QTermWidget(0, this); m_terminal->setMinimumHeight(400); m_terminal->setScrollBarPosition(QTermWidget::ScrollBarRight); - - // Set terminal colors and font m_terminal->setColorScheme("DarkPastels"); - - // Initially hide the terminal + m_terminal->startTerminalTeletype(); m_terminal->hide(); } -void JailbrokenWidget::connectTerminalToProcess() +void JailbrokenWidget::connectLibsshToTerminal() { - if (!m_terminal || !sshProcess) + if (!m_terminal) return; - // Connect terminal input to SSH process stdin + // Connect terminal input to SSH channel connect(m_terminal, &QTermWidget::sendData, this, [this](const char *data, int size) { - if (sshProcess && sshProcess->state() == QProcess::Running) { - sshProcess->write(data, size); + if (m_sshChannel && ssh_channel_is_open(m_sshChannel)) { + ssh_channel_write(m_sshChannel, data, size); } }); - - // Connect SSH process stdout/stderr to terminal - connect(sshProcess, &QProcess::readyReadStandardOutput, this, [this]() { - QByteArray data = sshProcess->readAllStandardOutput(); - if (m_terminal && !data.isEmpty()) { - // Write directly to the terminal's PTY - write(m_terminal->getPtySlaveFd(), data.data(), data.size()); - } - }); - - connect(sshProcess, &QProcess::readyReadStandardError, this, [this]() { - QByteArray data = sshProcess->readAllStandardError(); - if (m_terminal && !data.isEmpty()) { - // Write directly to the terminal's PTY - write(m_terminal->getPtySlaveFd(), data.data(), data.size()); - } - }); } void JailbrokenWidget::deviceConnected(iDescriptorDevice *device) @@ -118,22 +110,7 @@ void JailbrokenWidget::deviceConnected(iDescriptorDevice *device) void JailbrokenWidget::onConnectSSH() { if (m_sshConnected) { - // Disconnect SSH - if (sshProcess) { - sshProcess->terminate(); - sshProcess->waitForFinished(3000); - sshProcess = nullptr; - } - if (iproxyProcess) { - iproxyProcess->terminate(); - iproxyProcess->waitForFinished(3000); - iproxyProcess = nullptr; - } - m_terminal->hide(); - m_connectButton->setText("Connect SSH Terminal"); - m_infoLabel->setText("SSH disconnected"); - m_sshConnected = false; - m_isInitialized = false; + disconnectSSH(); return; } @@ -157,6 +134,11 @@ void JailbrokenWidget::initWidget() // Start iproxy first iproxyProcess = new QProcess(this); iproxyProcess->setProcessChannelMode(QProcess::MergedChannels); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + // Add common directories where iproxy might be installed + env.insert("PATH", env.value("PATH") + ":/usr/local/bin:/opt/homebrew/bin"); + + iproxyProcess->setProcessEnvironment(env); connect(iproxyProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { @@ -165,22 +147,20 @@ void JailbrokenWidget::initWidget() qDebug() << "iproxy error:" << error; }); - // connect(iproxyProcess, &QProcess::readyReadStandardOutput, this, [this]() - // { - // QByteArray output = iproxyProcess->readAllStandardOutput(); - // qDebug() << "iproxy output:" << output; - - // // Once iproxy is running, start SSH terminal after a short delay - // if (!m_sshConnected) { - // QTimer::singleShot(2000, this, &JailbrokenWidget::startSSH); - // } - // }); - - QTimer::singleShot(2000, this, &JailbrokenWidget::startSSH); + QTimer::singleShot( + 3000, this, + &JailbrokenWidget::startSSH); // Increased delay to 3 seconds iproxyProcess->start("iproxy", QStringList() << "-u" << m_device->udid.c_str() << "3333" << "22"); + + // Check if iproxy started successfully + if (!iproxyProcess->waitForStarted(5000)) { + m_infoLabel->setText("Failed to start iproxy"); + m_connectButton->setEnabled(true); + return; + } } void JailbrokenWidget::startSSH() @@ -188,98 +168,194 @@ void JailbrokenWidget::startSSH() if (m_sshConnected) return; - m_infoLabel->setText("Starting SSH terminal..."); + m_infoLabel->setText("Connecting to SSH server..."); + qDebug() << "Starting SSH connection to localhost:3333"; - // Show the terminal and start SSH session - m_terminal->show(); - - // Start the terminal with an empty shell first - m_terminal->startTerminalTeletype(); - - // Create SSH process - sshProcess = new QProcess(this); - sshProcess->setProcessChannelMode(QProcess::MergedChannels); - - // Check if sshpass is available for automatic password entry - QProcess *checkSshpass = new QProcess(this); - checkSshpass->start("which", QStringList() << "sshpass"); - checkSshpass->waitForFinished(1000); - - QStringList sshArgs; - QString sshProgram; - - if (checkSshpass->exitCode() == 0) { - // Use sshpass for automatic login - sshProgram = "sshpass"; - sshArgs << "-p" << "alpine" - << "ssh" - << "-t" // Force pseudo-terminal allocation - << "-o" << "StrictHostKeyChecking=no" - << "-o" << "UserKnownHostsFile=/dev/null" - << "-p" << "3333" - << "root@localhost"; - m_infoLabel->setText( - "SSH terminal connected on port 3333 (using sshpass)"); - } else { - // Use regular SSH (user will need to enter password manually) - sshProgram = "ssh"; - sshArgs << "-t" // Force pseudo-terminal allocation - << "-o" << "StrictHostKeyChecking=no" - << "-o" << "UserKnownHostsFile=/dev/null" - << "-p" << "3333" - << "root@localhost"; - m_infoLabel->setText("SSH terminal connected on port 3333 (enter " - "'alpine' when prompted)"); - } - - checkSshpass->deleteLater(); - - // Connect terminal to SSH process - connectTerminalToProcess(); - - // Handle SSH process finish - connect(sshProcess, - QOverload::of(&QProcess::finished), this, - &JailbrokenWidget::onSshProcessFinished); - - // Start SSH process - sshProcess->start(sshProgram, sshArgs); - - if (!sshProcess->waitForStarted(3000)) { - m_infoLabel->setText("Failed to start SSH process"); - m_terminal->hide(); + // Create SSH session + m_sshSession = ssh_new(); + if (!m_sshSession) { + m_infoLabel->setText("Error: Failed to create SSH session"); + m_connectButton->setEnabled(true); return; } + // Configure SSH session + ssh_options_set(m_sshSession, SSH_OPTIONS_HOST, "localhost"); + int port = 3333; + ssh_options_set(m_sshSession, SSH_OPTIONS_PORT, &port); + ssh_options_set(m_sshSession, SSH_OPTIONS_USER, "root"); + + // Disable strict host key checking + int stricthostcheck = 0; + ssh_options_set(m_sshSession, SSH_OPTIONS_STRICTHOSTKEYCHECK, + &stricthostcheck); + + // Set log level for debugging + int log_level = SSH_LOG_PROTOCOL; + ssh_options_set(m_sshSession, SSH_OPTIONS_LOG_VERBOSITY, &log_level); + + qDebug() << "SSH session configured, attempting connection..."; + + // Connect to SSH server + int rc = ssh_connect(m_sshSession); + qDebug() << "SSH connect result:" << rc << "SSH_OK:" << SSH_OK; + if (rc != SSH_OK) { + QString errorMsg = QString("SSH connection failed: %1") + .arg(ssh_get_error(m_sshSession)); + m_infoLabel->setText(errorMsg); + qDebug() << errorMsg; + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + qDebug() << "SSH connected successfully, attempting authentication..."; + + // Authenticate with password + rc = ssh_userauth_password(m_sshSession, nullptr, "alpine"); + if (rc != SSH_AUTH_SUCCESS) { + m_infoLabel->setText(QString("SSH authentication failed: %1") + .arg(ssh_get_error(m_sshSession))); + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + // Create SSH channel + m_sshChannel = ssh_channel_new(m_sshSession); + if (!m_sshChannel) { + m_infoLabel->setText("Error: Failed to create SSH channel"); + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + // Open SSH channel + rc = ssh_channel_open_session(m_sshChannel); + if (rc != SSH_OK) { + m_infoLabel->setText(QString("Failed to open SSH channel: %1") + .arg(ssh_get_error(m_sshSession))); + ssh_channel_free(m_sshChannel); + m_sshChannel = nullptr; + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + // Request a PTY + rc = ssh_channel_request_pty(m_sshChannel); + if (rc != SSH_OK) { + m_infoLabel->setText("Failed to request PTY"); + ssh_channel_close(m_sshChannel); + ssh_channel_free(m_sshChannel); + m_sshChannel = nullptr; + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + // Start shell + rc = ssh_channel_request_shell(m_sshChannel); + if (rc != SSH_OK) { + m_infoLabel->setText("Failed to start shell"); + ssh_channel_close(m_sshChannel); + ssh_channel_free(m_sshChannel); + m_sshChannel = nullptr; + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + m_connectButton->setEnabled(true); + return; + } + + // Show terminal and connect to libssh + m_terminal->show(); + connectLibsshToTerminal(); + + // Start timer to check for SSH data + m_sshTimer->start(50); // Check every 50ms + m_sshConnected = true; m_connectButton->setEnabled(true); m_connectButton->setText("Disconnect SSH"); + m_infoLabel->setText("SSH terminal connected"); - // Set focus to terminal so user can type immediately + // Set focus to terminal m_terminal->setFocus(); } -void JailbrokenWidget::onSshProcessFinished(int exitCode, - QProcess::ExitStatus exitStatus) +void JailbrokenWidget::checkSshData() { - Q_UNUSED(exitCode) - Q_UNUSED(exitStatus) + if (!m_sshChannel || !ssh_channel_is_open(m_sshChannel)) + return; - m_infoLabel->setText("SSH connection closed"); - m_sshConnected = false; - m_connectButton->setText("Connect SSH Terminal"); - m_terminal->hide(); + // Check if SSH channel has data to read + if (ssh_channel_poll(m_sshChannel, 0) > 0) { + char buffer[4096]; + int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer, + sizeof(buffer), 0); + if (nbytes > 0) { + // Write data to terminal's PTY + write(m_terminal->getPtySlaveFd(), buffer, nbytes); + } + } - if (sshProcess) { - sshProcess->deleteLater(); - sshProcess = nullptr; + // Check for stderr data + if (ssh_channel_poll(m_sshChannel, 1) > 0) { + char buffer[4096]; + int nbytes = ssh_channel_read_nonblocking(m_sshChannel, buffer, + sizeof(buffer), 1); + if (nbytes > 0) { + // Write stderr data to terminal's PTY + write(m_terminal->getPtySlaveFd(), buffer, nbytes); + } + } + + // Check if channel is closed + if (ssh_channel_is_eof(m_sshChannel)) { + disconnectSSH(); } } -JailbrokenWidget::~JailbrokenWidget() +void JailbrokenWidget::disconnectSSH() { + if (m_sshTimer) { + m_sshTimer->stop(); + } + + if (m_sshChannel) { + ssh_channel_close(m_sshChannel); + ssh_channel_free(m_sshChannel); + m_sshChannel = nullptr; + } + + if (m_sshSession) { + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + } + if (iproxyProcess) { iproxyProcess->terminate(); iproxyProcess->waitForFinished(3000); + iproxyProcess = nullptr; } -} \ No newline at end of file + + m_terminal->hide(); + m_connectButton->setText("Connect SSH Terminal"); + m_infoLabel->setText("SSH disconnected"); + m_sshConnected = false; + m_isInitialized = false; + m_connectButton->setEnabled(true); +} + +JailbrokenWidget::~JailbrokenWidget() { disconnectSSH(); } diff --git a/src/jailbrokenwidget.h b/src/jailbrokenwidget.h index b966921..18e4963 100644 --- a/src/jailbrokenwidget.h +++ b/src/jailbrokenwidget.h @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include class JailbrokenWidget : public QWidget @@ -21,16 +23,21 @@ private slots: void deviceConnected(iDescriptorDevice *device); void onConnectSSH(); void startSSH(); - void onSshProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void checkSshData(); private: void setupTerminal(); - void connectTerminalToProcess(); + void connectLibsshToTerminal(); + void disconnectSSH(); QLabel *m_infoLabel; iDescriptorDevice *m_device = nullptr; QProcess *iproxyProcess = nullptr; - QProcess *sshProcess = nullptr; + + // SSH session variables + ssh_session m_sshSession; + ssh_channel m_sshChannel; + QTimer *m_sshTimer; // Terminal widgets QTermWidget *m_terminal; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fda02e5..2aef7a7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -39,6 +39,7 @@ #include "fileexplorerwidget.h" #include "jailbrokenwidget.h" #include "recoverydeviceinfowidget.h" +#include "settingsmanager.h" #include #include #include @@ -132,6 +133,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); + // setStyleSheet("background-color: white; color: black;"); // Create custom tab widget m_customTabWidget = new CustomTabWidget(this); m_customTabWidget->setAttribute(Qt::WA_ContentsMarginsRespectsSafeArea, @@ -178,16 +180,14 @@ MainWindow::MainWindow(QWidget *parent) m_customTabWidget->addTab(jailbrokenWidget, "Jailbroken"); m_customTabWidget->finalizeStyles(); - // todo: is this ok ? - auto connection = std::make_shared(); - *connection = - connect(m_customTabWidget, &CustomTabWidget::currentChanged, this, - [this, jailbrokenWidget, connection](int index) { - if (index == 3) { // Jailbroken tab - jailbrokenWidget->initWidget(); - QObject::disconnect(*connection); - } - }); + connect( + m_customTabWidget, &CustomTabWidget::currentChanged, this, + [this, jailbrokenWidget](int index) { + if (index == 3) { // Jailbroken tab + jailbrokenWidget->initWidget(); + } + }, + Qt::SingleShotConnection); // settings button QPushButton *settingsButton = new QPushButton(); @@ -197,31 +197,16 @@ MainWindow::MainWindow(QWidget *parent) settingsButton->setCursor(Qt::PointingHandCursor); settingsButton->setFixedSize(24, 24); connect(settingsButton, &QPushButton::clicked, this, [this]() { - QDialog settingsDialog(this); - settingsDialog.setWindowTitle("Settings"); - settingsDialog.setModal(true); - settingsDialog.resize(400, 300); - QVBoxLayout *layout = new QVBoxLayout(&settingsDialog); - SettingsWidget *settingsWidget = new SettingsWidget(&settingsDialog); - layout->addWidget(settingsWidget); - settingsDialog.setLayout(layout); - settingsDialog.exec(); + SettingsManager::sharedInstance()->showSettingsDialog(); }); + m_connectedDeviceCountLabel = new QLabel("iDescriptor: no devices"); m_connectedDeviceCountLabel->setContentsMargins(5, 0, 5, 0); m_connectedDeviceCountLabel->setStyleSheet( "QLabel:hover { background-color : #13131319; }"); ui->statusbar->addWidget(m_connectedDeviceCountLabel); - ui->statusbar->setContentsMargins(0, 0, 0, 0); - - // QWidget *statusSpacer = new QWidget(); - // statusSpacer->setSizePolicy(QSizePolicy::Expanding, - // QSizePolicy::Preferred); - // statusSpacer->setAttribute(Qt::WA_TransparentForMouseEvents); - // ui->statusbar->addWidget(statusSpacer); - ui->statusbar->addPermanentWidget(settingsButton); #ifdef Q_OS_LINUX @@ -264,7 +249,6 @@ void MainWindow::createMenus() #ifdef Q_OS_MAC QMenu *actionsMenu = menuBar()->addMenu("&Actions"); - // Add a custom "About" action for your app QAction *aboutAct = new QAction("&About iDescriptor", this); connect(aboutAct, &QAction::triggered, this, [=]() { QMessageBox::about(this, "About iDescriptor", diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index 4148d69..61633e2 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -1,4 +1,5 @@ #include "settingsmanager.h" +#include "settingswidget.h" #include #include @@ -10,6 +11,23 @@ SettingsManager *SettingsManager::sharedInstance() return &instance; } +void SettingsManager::showSettingsDialog() +{ + if (m_dialog) { + m_dialog->raise(); + m_dialog->activateWindow(); + return; + } + + m_dialog = new SettingsWidget(); + m_dialog->setWindowTitle("Settings - iDescriptor"); + m_dialog->setModal(true); + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + connect(m_dialog, &QObject::destroyed, [this]() { m_dialog = nullptr; }); + + m_dialog->show(); +} + SettingsManager::SettingsManager(QObject *parent) : QObject{parent} { m_settings = new QSettings(this); diff --git a/src/settingsmanager.h b/src/settingsmanager.h index f11a77e..cb49525 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -6,6 +6,7 @@ #include #include #include +#include class SettingsManager : public QObject { @@ -25,11 +26,13 @@ public: bool isFavoritePlace(const QString &path) const; QString getFavoritePlaceAlias(const QString &path) const; void clearFavoritePlaces(); + void showSettingsDialog(); signals: void favoritePlacesChanged(); private: + QDialog *m_dialog; explicit SettingsManager(QObject *parent = nullptr); QSettings *m_settings; diff --git a/src/settingswidget.cpp b/src/settingswidget.cpp index 0d2d338..a0ea0b0 100644 --- a/src/settingswidget.cpp +++ b/src/settingswidget.cpp @@ -1,6 +1,7 @@ #include "settingswidget.h" #include #include +#include #include #include #include @@ -15,7 +16,7 @@ #include #include -SettingsWidget::SettingsWidget(QWidget *parent) : QWidget{parent} +SettingsWidget::SettingsWidget(QWidget *parent) : QDialog{parent} { setupUI(); loadSettings(); @@ -25,7 +26,7 @@ SettingsWidget::SettingsWidget(QWidget *parent) : QWidget{parent} void SettingsWidget::setupUI() { auto *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(20, 20, 20, 20); + mainLayout->setContentsMargins(0, 0, 0, 0); mainLayout->setSpacing(15); // Create scroll area for the settings @@ -42,6 +43,7 @@ void SettingsWidget::setupUI() downloadLayout->addWidget(new QLabel("Download Path:")); m_downloadPathEdit = new QLineEdit(); m_downloadPathEdit->setReadOnly(true); + m_downloadPathEdit->setMaximumWidth(300); downloadLayout->addWidget(m_downloadPathEdit); auto *browseButton = new QPushButton("Browse..."); downloadLayout->addWidget(browseButton); @@ -165,25 +167,22 @@ void SettingsWidget::setupUI() scrollArea->setWidget(scrollWidget); scrollArea->setWidgetResizable(true); scrollArea->setFrameStyle(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - mainLayout->addWidget(scrollArea); - - // === BOTTOM BUTTONS === + // == BUTTONS === auto *buttonLayout = new QHBoxLayout(); m_checkUpdatesButton = new QPushButton("Check for Updates"); - m_resetButton = new QPushButton("Reset to Defaults"); + m_resetButton = new QPushButton("Reset Settings"); m_applyButton = new QPushButton("Apply"); - m_okButton = new QPushButton("OK"); - m_cancelButton = new QPushButton("Cancel"); buttonLayout->addWidget(m_checkUpdatesButton); - buttonLayout->addStretch(); + // buttonLayout->addStretch(); buttonLayout->addWidget(m_resetButton); buttonLayout->addWidget(m_applyButton); - buttonLayout->addWidget(m_okButton); - buttonLayout->addWidget(m_cancelButton); + buttonLayout->setContentsMargins(10, 10, 10, 10); + mainLayout->addWidget(scrollArea); mainLayout->addLayout(buttonLayout); // Connect button signals @@ -193,10 +192,6 @@ void SettingsWidget::setupUI() &SettingsWidget::onResetToDefaultsClicked); connect(m_applyButton, &QPushButton::clicked, this, &SettingsWidget::onApplyClicked); - connect(m_okButton, &QPushButton::clicked, this, - &SettingsWidget::onOkClicked); - connect(m_cancelButton, &QPushButton::clicked, this, - &SettingsWidget::onCancelClicked); } void SettingsWidget::loadSettings() @@ -285,14 +280,6 @@ void SettingsWidget::onApplyClicked() QMessageBox::information(this, "Settings", "Settings have been applied."); } -void SettingsWidget::onOkClicked() -{ - saveSettings(); - close(); -} - -void SettingsWidget::onCancelClicked() { close(); } - void SettingsWidget::onSettingChanged() { // Enable apply button when settings change diff --git a/src/settingswidget.h b/src/settingswidget.h index 54b06fb..84cb13b 100644 --- a/src/settingswidget.h +++ b/src/settingswidget.h @@ -1,16 +1,15 @@ #ifndef SETTINGSWIDGET_H #define SETTINGSWIDGET_H +#include +#include +#include +#include +#include +#include #include -// Forward declarations -class QLineEdit; -class QCheckBox; -class QComboBox; -class QSpinBox; -class QPushButton; - -class SettingsWidget : public QWidget +class SettingsWidget : public QDialog { Q_OBJECT @@ -22,8 +21,6 @@ private slots: void onCheckUpdatesClicked(); void onResetToDefaultsClicked(); void onApplyClicked(); - void onOkClicked(); - void onCancelClicked(); void onSettingChanged(); private: @@ -63,8 +60,6 @@ private: QPushButton *m_checkUpdatesButton; QPushButton *m_resetButton; QPushButton *m_applyButton; - QPushButton *m_okButton; - QPushButton *m_cancelButton; }; #endif // SETTINGSWIDGET_H diff --git a/src/toolboxwidget.cpp b/src/toolboxwidget.cpp index 29a5694..c65e578 100644 --- a/src/toolboxwidget.cpp +++ b/src/toolboxwidget.cpp @@ -82,7 +82,7 @@ ToolboxWidget::ToolboxWidget(QWidget *parent) : QWidget{parent} void ToolboxWidget::setupUI() { QVBoxLayout *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(5, 5, 5, 5); + mainLayout->setContentsMargins(0, 0, 0, 0); // Device selection section QHBoxLayout *deviceLayout = new QHBoxLayout(); @@ -92,7 +92,7 @@ void ToolboxWidget::setupUI() deviceLayout->addWidget(m_deviceLabel); deviceLayout->addWidget(m_deviceCombo); - deviceLayout->setContentsMargins(0, 0, 0, 0); + deviceLayout->setContentsMargins(15, 5, 15, 5); deviceLayout->addStretch(); mainLayout->addLayout(deviceLayout);