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:
+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(); }
|
||||
|
||||
Reference in New Issue
Block a user