diff --git a/src/jailbrokenwidget.cpp b/src/jailbrokenwidget.cpp index 130f03a..59c69f7 100644 --- a/src/jailbrokenwidget.cpp +++ b/src/jailbrokenwidget.cpp @@ -1,5 +1,6 @@ #include "jailbrokenwidget.h" #include "appcontext.h" +#include "sshterminalwidget.h" #ifdef __linux__ #include "core/services/avahi/avahi_service.h" @@ -16,18 +17,11 @@ #include #include #include -#include -#include -#include #include #include #include -#include #include #include -#include -#include -#include JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} { @@ -54,7 +48,7 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} connect(AppContext::sharedInstance(), &AppContext::deviceRemoved, this, &JailbrokenWidget::onWiredDeviceRemoved); -#ifdef __linux__ +#ifdef __linux____ m_wirelessProvider = new AvahiService(this); connect(m_wirelessProvider, &AvahiService::deviceAdded, this, &JailbrokenWidget::onWirelessDeviceAdded); @@ -78,21 +72,9 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} rightLayout->setSpacing(10); setupDeviceSelectionUI(rightLayout); - setupTerminal(); - rightLayout->addWidget(m_terminal, 1); 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); - // Start scanning for wireless devices m_wirelessProvider->startBrowsing(); @@ -100,27 +82,6 @@ JailbrokenWidget::JailbrokenWidget(QWidget *parent) : QWidget{parent} updateDeviceList(); } -void JailbrokenWidget::setupTerminal() -{ - m_terminal = new QTermWidget(0, this); - m_terminal->setMinimumHeight(400); - m_terminal->setScrollBarPosition(QTermWidget::ScrollBarRight); - m_terminal->setColorScheme("Linux"); - m_terminal->setContextMenuPolicy(Qt::CustomContextMenu); - connect(m_terminal, &QWidget::customContextMenuRequested, this, - [this](const QPoint &pos) { - QMenu menu(this); - QList actions = m_terminal->filterActions(pos); - if (!actions.isEmpty()) { - menu.addActions(actions); - menu.exec(m_terminal->mapToGlobal(pos)); - } - }); - m_terminal->startTerminalTeletype(); - m_terminal->hide(); - m_terminal->setStyleSheet("padding : 10px;"); -} - void JailbrokenWidget::setupDeviceSelectionUI(QVBoxLayout *layout) { // Create scroll area for device selection @@ -157,10 +118,10 @@ void JailbrokenWidget::setupDeviceSelectionUI(QVBoxLayout *layout) m_infoLabel = new QLabel("Select a device to connect"); layout->addWidget(m_infoLabel); - m_connectButton = new QPushButton("Connect SSH Terminal"); + m_connectButton = new QPushButton("Open SSH Terminal"); m_connectButton->setEnabled(false); connect(m_connectButton, &QPushButton::clicked, this, - &JailbrokenWidget::onConnectSSH); + &JailbrokenWidget::onOpenSSHTerminal); layout->addWidget(m_connectButton); } @@ -296,7 +257,8 @@ void JailbrokenWidget::onDeviceSelected(QAbstractButton *button) if (m_selectedWiredDevice->deviceInfo.jailbroken) { m_infoLabel->setText("Jailbroken device selected"); } else { - m_infoLabel->setText("Device selected (jailbreak status unknown)"); + m_infoLabel->setText( + "Device selected (detected as non-jailbroken)"); } } else if (deviceType == "wireless") { m_selectedDeviceType = DeviceType::Wireless; @@ -311,7 +273,7 @@ void JailbrokenWidget::onDeviceSelected(QAbstractButton *button) } m_connectButton->setEnabled(true); - m_connectButton->setText("Connect SSH Terminal"); + m_connectButton->setText("Open SSH Terminal"); } void JailbrokenWidget::resetSelection() @@ -330,333 +292,44 @@ void JailbrokenWidget::resetSelection() } } -void JailbrokenWidget::connectLibsshToTerminal() +void JailbrokenWidget::onOpenSSHTerminal() { - if (!m_terminal) - return; - - // Connect terminal input to SSH channel - connect(m_terminal, &QTermWidget::sendData, this, - [this](const char *data, int size) { - if (m_sshChannel && ssh_channel_is_open(m_sshChannel)) { - ssh_channel_write(m_sshChannel, data, size); - } - }); -} - -void JailbrokenWidget::deviceConnected(iDescriptorDevice *device) -{ - if (device->deviceInfo.jailbroken) { - m_infoLabel->setText("Jailbroken device connected"); - } else { - m_infoLabel->setText( - "Connected device is not detected as jailbroken. Continue anyway?"); - } - m_device = device; - m_connectButton->setEnabled(true); - m_connectButton->setText("Connect SSH Terminal"); -} - -void JailbrokenWidget::onConnectSSH() -{ - if (m_sshConnected) { - disconnectSSH(); - return; - } - if (m_selectedDeviceType == DeviceType::None) { m_infoLabel->setText("Please select a device first"); return; } + // Prepare connection info + ConnectionInfo connectionInfo; + if (m_selectedDeviceType == DeviceType::Wired) { - initWiredDevice(); - } else { - initWirelessDevice(); - } -} - -void JailbrokenWidget::initWiredDevice() -{ - if (m_isInitialized) - return; - m_isInitialized = true; - - if (!m_selectedWiredDevice) { - m_infoLabel->setText("No wired device selected"); - return; - } - - m_connectButton->setEnabled(false); - m_infoLabel->setText("Setting up SSH tunnel..."); - - // Start iproxy first for wired devices - 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) { - m_infoLabel->setText("Error: " + iproxyProcess->errorString()); - m_connectButton->setEnabled(true); - qDebug() << "iproxy error:" << error; - }); - - // Monitor iproxy output for readiness - connect(iproxyProcess, &QProcess::readyRead, this, [this]() { - QByteArray output = iproxyProcess->readAll(); - if (output.contains("waiting for connection")) { - // iproxy is ready, disconnect the signal to avoid multiple calls - disconnect(iproxyProcess, &QProcess::readyRead, this, nullptr); - startSSH("127.0.0.1", 3333); + if (!m_selectedWiredDevice) { + m_infoLabel->setText("No wired device selected"); + return; } - }); - QStringList args; - args << "-u" << m_selectedWiredDevice->udid.c_str() << "3333" << "22"; + connectionInfo.type = ConnectionType::Wired; + connectionInfo.deviceName = QString::fromStdString( + m_selectedWiredDevice->deviceInfo.deviceName); + connectionInfo.deviceUdid = + QString::fromStdString(m_selectedWiredDevice->udid); + connectionInfo.hostAddress = "127.0.0.1"; + connectionInfo.port = 22; - qDebug() << "Starting iproxy with args:" << args; - - iproxyProcess->start("iproxy", args); - - // Check if iproxy started successfully - if (!iproxyProcess->waitForStarted(5000)) { - m_infoLabel->setText("Failed to start iproxy"); - m_connectButton->setEnabled(true); - return; + } else if (m_selectedDeviceType == DeviceType::Wireless) { + connectionInfo.type = ConnectionType::Wireless; + connectionInfo.deviceName = m_selectedNetworkDevice.name; + connectionInfo.deviceUdid = ""; + connectionInfo.hostAddress = m_selectedNetworkDevice.address; + connectionInfo.port = m_selectedNetworkDevice.port; } + + // Create and show SSH terminal widget in a new window + SSHTerminalWidget *sshTerminal = new SSHTerminalWidget(connectionInfo); + sshTerminal->setAttribute(Qt::WA_DeleteOnClose); + sshTerminal->show(); + sshTerminal->raise(); + sshTerminal->activateWindow(); } -void JailbrokenWidget::initWirelessDevice() -{ - if (m_isInitialized) - return; - m_isInitialized = true; - - m_connectButton->setEnabled(false); - m_infoLabel->setText("Connecting to network device..."); - - // For wireless devices, connect directly without iproxy - startSSH(m_selectedNetworkDevice.address, m_selectedNetworkDevice.port); -} - -void JailbrokenWidget::startSSH(const QString &host, uint16_t port) -{ - if (m_sshConnected) - return; - - m_infoLabel->setText("Connecting to SSH server..."); - qDebug() << "Starting SSH connection to" << host << ":" << port; - - // 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 - QByteArray hostBytes = host.toUtf8(); - ssh_options_set(m_sshSession, SSH_OPTIONS_HOST, hostBytes.constData()); - int sshPort = static_cast(port); - ssh_options_set(m_sshSession, SSH_OPTIONS_PORT, &sshPort); - 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 - m_terminal->setFocus(); -} - -void JailbrokenWidget::checkSshData() -{ - if (!m_sshChannel || !ssh_channel_is_open(m_sshChannel)) - return; - - // 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); - } - } - - // 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(); - } -} - -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->kill(); - delete iproxyProcess; - iproxyProcess = nullptr; - } - - m_terminal->hide(); - m_infoLabel->setText("SSH disconnected"); - m_sshConnected = false; - m_isInitialized = false; - m_connectButton->setEnabled(false); -} - -// todo: crash at exit -JailbrokenWidget::~JailbrokenWidget() -{ - if (m_sshTimer) { - m_sshTimer->stop(); - } - - if (m_sshChannel) { - ssh_channel_close(m_sshChannel); - ssh_channel_free(m_sshChannel); - } - - if (m_sshSession) { - ssh_disconnect(m_sshSession); - ssh_free(m_sshSession); - } - - if (iproxyProcess) { - iproxyProcess->kill(); - } -} +JailbrokenWidget::~JailbrokenWidget() {} diff --git a/src/jailbrokenwidget.h b/src/jailbrokenwidget.h index 81cd12c..099c9b4 100644 --- a/src/jailbrokenwidget.h +++ b/src/jailbrokenwidget.h @@ -8,18 +8,14 @@ #endif #include "iDescriptor.h" +#include "sshterminalwidget.h" #include #include #include #include -#include #include -#include #include #include -#include - -class QTermWidget; enum class DeviceType { None, Wired, Wireless }; @@ -32,8 +28,7 @@ public: ~JailbrokenWidget(); private slots: - void onConnectSSH(); - void checkSshData(); + void onOpenSSHTerminal(); void onWiredDeviceAdded(iDescriptorDevice *device); void onWiredDeviceRemoved(const std::string &udid); void onWirelessDeviceAdded(const NetworkDevice &device); @@ -41,7 +36,6 @@ private slots: void onDeviceSelected(QAbstractButton *button); private: - void setupTerminal(); void setupDeviceSelectionUI(QVBoxLayout *layout); void updateDeviceList(); void clearDeviceButtons(); @@ -49,14 +43,6 @@ private: void addWirelessDevice(const NetworkDevice &device); void resetSelection(); - void initWiredDevice(); - void initWirelessDevice(); - void startSSH(const QString &host, uint16_t port); - void disconnectSSH(); - void connectLibsshToTerminal(); - void deviceConnected(iDescriptorDevice *device); - - QTermWidget *m_terminal; QLabel *m_infoLabel; QPushButton *m_connectButton; @@ -68,7 +54,7 @@ private: QVBoxLayout *m_wirelessDevicesLayout; QButtonGroup *m_deviceButtonGroup; -#ifdef Q_OS_LINUX +#ifdef __linux__ AvahiService *m_wirelessProvider = nullptr; #endif diff --git a/src/sshterminalwidget.cpp b/src/sshterminalwidget.cpp new file mode 100644 index 0000000..403aaa5 --- /dev/null +++ b/src/sshterminalwidget.cpp @@ -0,0 +1,499 @@ +#include "sshterminalwidget.h" +#include "qprocessindicator.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SSHTerminalWidget::SSHTerminalWidget(const ConnectionInfo& connectionInfo, QWidget *parent) + : QWidget(parent) + , m_connectionInfo(connectionInfo) + , m_sshSession(nullptr) + , m_sshChannel(nullptr) + , m_iproxyProcess(nullptr) + , m_sshConnected(false) + , m_isInitialized(false) + , m_currentState(TerminalState::Loading) +{ + setWindowTitle(QString("SSH Terminal - %1").arg(m_connectionInfo.deviceName)); + setMinimumSize(800, 600); + + setupUI(); + + // Initialize SSH + ssh_init(); + + // Setup timer for checking SSH data + m_sshTimer = new QTimer(this); + connect(m_sshTimer, &QTimer::timeout, this, &SSHTerminalWidget::checkSshData); + + // Start connection process + initializeConnection(); +} + +SSHTerminalWidget::~SSHTerminalWidget() +{ + cleanup(); +} + +void SSHTerminalWidget::setupUI() +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + + m_stackedWidget = new QStackedWidget(this); + mainLayout->addWidget(m_stackedWidget); + + setupLoadingState(); + setupErrorState(); + setupActionState(); + + setState(TerminalState::Loading); +} + +void SSHTerminalWidget::setupLoadingState() +{ + m_loadingWidget = new QWidget(); + QVBoxLayout *loadingLayout = new QVBoxLayout(m_loadingWidget); + loadingLayout->setAlignment(Qt::AlignCenter); + + // Process indicator + m_loadingIndicator = new QProcessIndicator(m_loadingWidget); + m_loadingIndicator->setType(QProcessIndicator::line_rotate); + m_loadingIndicator->setFixedSize(64, 64); + + // Loading label + m_loadingLabel = new QLabel("Connecting to SSH server..."); + m_loadingLabel->setAlignment(Qt::AlignCenter); + m_loadingLabel->setStyleSheet("QLabel { font-size: 14px; color: #666; margin-top: 20px; }"); + + loadingLayout->addWidget(m_loadingIndicator, 0, Qt::AlignCenter); + loadingLayout->addWidget(m_loadingLabel); + + m_stackedWidget->addWidget(m_loadingWidget); +} + +void SSHTerminalWidget::setupErrorState() +{ + m_errorWidget = new QWidget(); + QVBoxLayout *errorLayout = new QVBoxLayout(m_errorWidget); + errorLayout->setAlignment(Qt::AlignCenter); + errorLayout->setSpacing(20); + + // Error label + m_errorLabel = new QLabel(); + m_errorLabel->setAlignment(Qt::AlignCenter); + m_errorLabel->setWordWrap(true); + m_errorLabel->setStyleSheet("QLabel { font-size: 14px; color: #d32f2f; padding: 20px; }"); + + // Retry button + m_retryButton = new QPushButton("Retry Connection"); + m_retryButton->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 14px; }"); + connect(m_retryButton, &QPushButton::clicked, this, &SSHTerminalWidget::onRetryClicked); + + errorLayout->addWidget(m_errorLabel); + errorLayout->addWidget(m_retryButton, 0, Qt::AlignCenter); + + m_stackedWidget->addWidget(m_errorWidget); +} + +void SSHTerminalWidget::setupActionState() +{ + m_actionWidget = new QWidget(); + QVBoxLayout *actionLayout = new QVBoxLayout(m_actionWidget); + actionLayout->setContentsMargins(0, 0, 0, 0); + + // Terminal widget + m_terminal = new QTermWidget(0, m_actionWidget); + m_terminal->setScrollBarPosition(QTermWidget::ScrollBarRight); + m_terminal->setColorScheme("Linux"); + m_terminal->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(m_terminal, &QWidget::customContextMenuRequested, this, + [this](const QPoint &pos) { + QMenu menu(this); + QList actions = m_terminal->filterActions(pos); + if (!actions.isEmpty()) { + menu.addActions(actions); + menu.exec(m_terminal->mapToGlobal(pos)); + } + }); + + m_terminal->startTerminalTeletype(); + m_terminal->setStyleSheet("padding: 5px;"); + + actionLayout->addWidget(m_terminal); + + m_stackedWidget->addWidget(m_actionWidget); +} + +void SSHTerminalWidget::setState(TerminalState state) +{ + m_currentState = state; + + switch (state) { + case TerminalState::Loading: + m_stackedWidget->setCurrentWidget(m_loadingWidget); + m_loadingIndicator->start(); + break; + + case TerminalState::Error: + m_stackedWidget->setCurrentWidget(m_errorWidget); + m_loadingIndicator->stop(); + break; + + case TerminalState::Connected: + m_stackedWidget->setCurrentWidget(m_actionWidget); + m_loadingIndicator->stop(); + m_terminal->setFocus(); + break; + } +} + +void SSHTerminalWidget::showError(const QString& errorMessage) +{ + m_errorLabel->setText(errorMessage); + setState(TerminalState::Error); +} + +void SSHTerminalWidget::onRetryClicked() +{ + // Reset all state + cleanup(); + m_sshConnected = false; + m_isInitialized = false; + + // Reinitialize SSH + ssh_init(); + + // Setup timer again + m_sshTimer = new QTimer(this); + connect(m_sshTimer, &QTimer::timeout, this, &SSHTerminalWidget::checkSshData); + + // Update loading message and start connection + m_loadingLabel->setText("Connecting to SSH server..."); + setState(TerminalState::Loading); + initializeConnection(); +} + +void SSHTerminalWidget::initializeConnection() +{ + if (m_connectionInfo.type == ConnectionType::Wired) { + initWiredDevice(); + } else { + initWirelessDevice(); + } +} + +void SSHTerminalWidget::initWiredDevice() +{ + if (m_isInitialized) + return; + m_isInitialized = true; + + m_loadingLabel->setText("Setting up SSH tunnel..."); + + // Start iproxy for wired devices + m_iproxyProcess = new QProcess(this); + m_iproxyProcess->setProcessChannelMode(QProcess::MergedChannels); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("PATH", env.value("PATH") + ":/usr/local/bin:/opt/homebrew/bin"); + m_iproxyProcess->setProcessEnvironment(env); + + connect(m_iproxyProcess, &QProcess::errorOccurred, this, + [this](QProcess::ProcessError error) { + showError("Error starting iproxy: " + m_iproxyProcess->errorString()); + qDebug() << "iproxy error:" << error; + }); + + connect(m_iproxyProcess, &QProcess::finished, this, + [this](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "iproxy finished with exit code:" << exitCode; + if (!m_sshConnected) { + showError("iproxy process terminated unexpectedly"); + } + }); + + // Monitor iproxy output for readiness + connect(m_iproxyProcess, &QProcess::readyRead, this, [this]() { + QByteArray output = m_iproxyProcess->readAll(); + qDebug() << "iproxy output:" << output; + + if (output.contains("waiting for connection")) { + qDebug() << "iproxy is ready, starting SSH connection"; + disconnect(m_iproxyProcess, &QProcess::readyRead, this, nullptr); + startSSH(QHostAddress(QHostAddress::LocalHost).toString(), 3333); + } else if (output.contains("ERROR") || output.contains("failed")) { + showError("iproxy failed: " + QString::fromUtf8(output)); + } + }); + + // Add timeout timer as backup + QTimer *timeoutTimer = new QTimer(this); + timeoutTimer->setSingleShot(true); + connect(timeoutTimer, &QTimer::timeout, this, [this, timeoutTimer]() { + qDebug() << "iproxy timeout - assuming it's ready and attempting SSH connection"; + timeoutTimer->deleteLater(); + startSSH(QHostAddress(QHostAddress::LocalHost).toString(), 3333); + }); + + QStringList args; + args << "-u" << m_connectionInfo.deviceUdid << "3333" << "22"; + + qDebug() << "Starting iproxy with args:" << args; + + QString iproxyPath; + QStringList possiblePaths = {"/usr/local/bin/iproxy", + "/opt/homebrew/bin/iproxy", "/usr/bin/iproxy", + "iproxy"}; + + for (const QString &path : possiblePaths) { + if (QFile::exists(path) || path == "iproxy") { + iproxyPath = path; + break; + } + } + + if (iproxyPath.isEmpty()) { + showError("Error: iproxy not found. Please install libimobiledevice."); + return; + } + + qDebug() << "Using iproxy at:" << iproxyPath; + m_iproxyProcess->start(iproxyPath, args); + + if (!m_iproxyProcess->waitForStarted(5000)) { + showError("Failed to start iproxy process"); + timeoutTimer->deleteLater(); + return; + } + + qDebug() << "iproxy process started, waiting for readiness..."; + timeoutTimer->start(5000); +} + +void SSHTerminalWidget::initWirelessDevice() +{ + if (m_isInitialized) + return; + m_isInitialized = true; + + m_loadingLabel->setText("Connecting to network device..."); + + // For wireless devices, connect directly without iproxy + startSSH(m_connectionInfo.hostAddress, m_connectionInfo.port); +} + +void SSHTerminalWidget::startSSH(const QString &host, uint16_t port) +{ + qDebug() << "Starting SSH to" << host << "on port" << port; + + if (m_sshConnected) + return; + + m_loadingLabel->setText("Establishing SSH connection..."); + qDebug() << "Starting SSH connection to" << host << ":" << port; + + // Create SSH session + m_sshSession = ssh_new(); + if (!m_sshSession) { + showError("Error: Failed to create SSH session"); + return; + } + + // Configure SSH session + QByteArray hostBytes = host.toUtf8(); + ssh_options_set(m_sshSession, SSH_OPTIONS_HOST, hostBytes.constData()); + int sshPort = static_cast(port); + ssh_options_set(m_sshSession, SSH_OPTIONS_PORT, &sshPort); + 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)); + showError(errorMsg); + qDebug() << errorMsg; + ssh_free(m_sshSession); + m_sshSession = nullptr; + return; + } + + qDebug() << "SSH connected successfully, attempting authentication..."; + + // Authenticate with password + rc = ssh_userauth_password(m_sshSession, nullptr, "alpine"); + if (rc != SSH_AUTH_SUCCESS) { + showError(QString("SSH authentication failed: %1") + .arg(ssh_get_error(m_sshSession))); + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + return; + } + + // Create SSH channel + m_sshChannel = ssh_channel_new(m_sshSession); + if (!m_sshChannel) { + showError("Error: Failed to create SSH channel"); + ssh_disconnect(m_sshSession); + ssh_free(m_sshSession); + m_sshSession = nullptr; + return; + } + + // Open SSH channel + rc = ssh_channel_open_session(m_sshChannel); + if (rc != SSH_OK) { + showError(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; + return; + } + + // Request a PTY + rc = ssh_channel_request_pty(m_sshChannel); + if (rc != SSH_OK) { + showError("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; + return; + } + + // Start shell + rc = ssh_channel_request_shell(m_sshChannel); + if (rc != SSH_OK) { + showError("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; + return; + } + + // Connect terminal to SSH + connectLibsshToTerminal(); + + // Start timer to check for SSH data + m_sshTimer->start(50); // Check every 50ms + + m_sshConnected = true; + setState(TerminalState::Connected); + + qDebug() << "SSH terminal connected successfully"; +} + +void SSHTerminalWidget::connectLibsshToTerminal() +{ + if (!m_terminal) + return; + + // Connect terminal input to SSH channel + connect(m_terminal, &QTermWidget::sendData, this, + [this](const char *data, int size) { + if (m_sshChannel && ssh_channel_is_open(m_sshChannel)) { + ssh_channel_write(m_sshChannel, data, size); + } + }); +} + +void SSHTerminalWidget::checkSshData() +{ + if (!m_sshChannel || !ssh_channel_is_open(m_sshChannel)) + return; + + // 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); + } + } + + // 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(); + } +} + +void SSHTerminalWidget::disconnectSSH() +{ + cleanup(); + showError("SSH connection was closed by the remote host"); +} + +void SSHTerminalWidget::cleanup() +{ + if (m_sshTimer) { + m_sshTimer->stop(); + m_sshTimer->deleteLater(); + m_sshTimer = nullptr; + } + + 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 (m_iproxyProcess) { + m_iproxyProcess->kill(); + m_iproxyProcess->deleteLater(); + m_iproxyProcess = nullptr; + } + + m_sshConnected = false; +} diff --git a/src/sshterminalwidget.h b/src/sshterminalwidget.h new file mode 100644 index 0000000..367e7ef --- /dev/null +++ b/src/sshterminalwidget.h @@ -0,0 +1,91 @@ +#ifndef SSHTERMINALWIDGET_H +#define SSHTERMINALWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QTermWidget; +class QProcessIndicator; + +enum class ConnectionType { Wired, Wireless }; + +struct ConnectionInfo { + ConnectionType type; + QString deviceName; + QString deviceUdid; // For wired devices + QString hostAddress; // For wireless devices + uint16_t port; +}; + +enum class TerminalState { Loading, Error, Connected }; + +class SSHTerminalWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SSHTerminalWidget(const ConnectionInfo &connectionInfo, + QWidget *parent = nullptr); + ~SSHTerminalWidget(); + +private slots: + void onRetryClicked(); + void checkSshData(); + +private: + void setupUI(); + void setupLoadingState(); + void setupErrorState(); + void setupActionState(); + + void setState(TerminalState state); + void showError(const QString &errorMessage); + + void initializeConnection(); + void initWiredDevice(); + void initWirelessDevice(); + void startSSH(const QString &host, uint16_t port); + void disconnectSSH(); + void connectLibsshToTerminal(); + void cleanup(); + + // UI Components + QStackedWidget *m_stackedWidget; + + // Loading state + QWidget *m_loadingWidget; + QProcessIndicator *m_loadingIndicator; + QLabel *m_loadingLabel; + + // Error state + QWidget *m_errorWidget; + QLabel *m_errorLabel; + QPushButton *m_retryButton; + + // Action state (connected terminal) + QWidget *m_actionWidget; + QTermWidget *m_terminal; + + // Connection data + ConnectionInfo m_connectionInfo; + + // SSH components + ssh_session m_sshSession; + ssh_channel m_sshChannel; + QTimer *m_sshTimer; + QProcess *m_iproxyProcess; + + // State tracking + bool m_sshConnected; + bool m_isInitialized; + TerminalState m_currentState; +}; + +#endif // SSHTERMINALWIDGET_H \ No newline at end of file