mirror of
https://github.com/iDescriptor/iDescriptor.git
synced 2026-06-22 03:45:51 +08:00
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:
+137
-138
@@ -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
@@ -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
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user