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.
This commit is contained in:
uncor3
2025-10-03 06:45:34 -07:00
parent e25f194ee9
commit d6282762b1
14 changed files with 518 additions and 455 deletions
+26 -12
View File
@@ -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}"
+137 -138
View File
@@ -5,18 +5,11 @@
#include "appinstalldialog.h"
#include "appstoremanager.h"
#include "logindialog.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>
@@ -33,20 +26,16 @@
#include <QPixmap>
#include <QProgressBar>
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollArea>
#include <QStyle>
#include <QTemporaryDir>
#include <QTimer>
#include <QUrl>
#include <QVBoxLayout>
#include <QWidget>
#include <QtConcurrent/QtConcurrent>
// watch for login and logout events
AppsWidget::AppsWidget(QWidget *parent) : QWidget(parent), m_isLoggedIn(false)
{
// m_searchProcess = new QProcess(this);
m_searchWatcher = new QFutureWatcher<QString>(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<QString>::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<QGridLayout *>(m_contentWidget->layout());
if (!gridLayout)
@@ -221,20 +285,10 @@ void AppsWidget::clearAppGrid()
}
}
void AppsWidget::showStatusMessage(const QString &message)
{
clearAppGrid();
QGridLayout *gridLayout =
qobject_cast<QGridLayout *>(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<QGridLayout *>(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);
}
+17 -6
View File
@@ -2,11 +2,10 @@
#define APPSWIDGET_H
#include "appstoremanager.h"
#include "qprocessindicator.h"
#include <QComboBox>
#include <QDialog>
#include <QFile>
#include <QFrame>
#include <QFutureWatcher>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
@@ -15,6 +14,7 @@
#include <QPushButton>
#include <QRegularExpression>
#include <QScrollArea>
#include <QStackedWidget>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
@@ -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<QString> *m_searchWatcher;
};
#endif // APPSWIDGET_H
+53 -29
View File
@@ -9,6 +9,7 @@
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QGraphicsDropShadowEffect>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -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()) {
+9 -12
View File
@@ -2,19 +2,18 @@
#define DEVDISKIMAGESWIDGET_H
#include "iDescriptor.h"
#include <QComboBox>
#include <QLabel>
#include <QListWidget>
#include <QMap>
#include <QNetworkReply>
#include <QPair>
#include <QProgressBar>
#include <QPushButton>
#include <QStackedWidget>
#include <QStringList>
#include <QWidget>
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;
+6 -58
View File
@@ -11,6 +11,7 @@
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QList>
@@ -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<QPair<QString, QWidget *>> 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
+208 -132
View File
@@ -8,10 +8,12 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QProcess>
#include <QProcessEnvironment>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
#include <QWidget>
#include <libssh/libssh.h>
#include <qtermwidget6/qtermwidget.h>
#include <unistd.h>
@@ -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<int, QProcess::ExitStatus>::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;
}
}
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(); }
+10 -3
View File
@@ -5,7 +5,9 @@
#include <QLabel>
#include <QProcess>
#include <QPushButton>
#include <QTimer>
#include <QWidget>
#include <libssh/libssh.h>
#include <qtermwidget6/qtermwidget.h>
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;
+12 -28
View File
@@ -39,6 +39,7 @@
#include "fileexplorerwidget.h"
#include "jailbrokenwidget.h"
#include "recoverydeviceinfowidget.h"
#include "settingsmanager.h"
#include <QApplication>
#include <QMenu>
#include <QMenuBar>
@@ -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<QMetaObject::Connection>();
*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",
+18
View File
@@ -1,4 +1,5 @@
#include "settingsmanager.h"
#include "settingswidget.h"
#include <QDebug>
#include <QSettings>
@@ -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);
+3
View File
@@ -6,6 +6,7 @@
#include <QSettings>
#include <QString>
#include <QStringList>
#include <QDialog>
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;
+10 -23
View File
@@ -1,6 +1,7 @@
#include "settingswidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QFileDialog>
#include <QFrame>
#include <QGroupBox>
@@ -15,7 +16,7 @@
#include <QTimer>
#include <QVBoxLayout>
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
+7 -12
View File
@@ -1,16 +1,15 @@
#ifndef SETTINGSWIDGET_H
#define SETTINGSWIDGET_H
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QWidget>
// 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
+2 -2
View File
@@ -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);