Replaced the SSH library + IO thread + intermediate PTY bridge with vte_terminal_spawn_sync(). This eliminates the broken terminal behavior (backspace, Ctrl+C, copy/paste, bash completion) caused by the intermediate PTY with no process on the slave side. - SshTerminal now spawns 'ssh' as a VTE subprocess - Removed libssh dependency from terminal (still used for key validation) - Removed IO thread, manual PTY creation, and thread synchronization - VTE now handles all terminal I/O, line discipline, resize, etc. - Password saved: uses sshpass -e with SSHPASS env var if available - Password not saved: ssh prompts interactively in the terminal - Removed show_password_dialog (no longer needed) - Simplified connect flow in ssh_client.cpp
169 lines
4.5 KiB
C++
169 lines
4.5 KiB
C++
#include "ssh_terminal.h"
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <glib.h>
|
|
|
|
SshTerminal::SshTerminal()
|
|
: vte_box_(nullptr), terminal_(nullptr),
|
|
child_pid_(0), connected_(false) {
|
|
|
|
vte_box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
|
terminal_ = VTE_TERMINAL(vte_terminal_new());
|
|
gtk_box_pack_start(GTK_BOX(vte_box_), GTK_WIDGET(terminal_), TRUE, TRUE, 0);
|
|
gtk_widget_show_all(vte_box_);
|
|
|
|
g_signal_connect(terminal_, "child-exited", G_CALLBACK(on_child_exited), this);
|
|
}
|
|
|
|
SshTerminal::~SshTerminal() {
|
|
disconnect();
|
|
if (vte_box_) {
|
|
gtk_widget_destroy(vte_box_);
|
|
vte_box_ = nullptr;
|
|
}
|
|
}
|
|
|
|
void SshTerminal::on_child_exited(VteTerminal* terminal, int status, gpointer data) {
|
|
(void)terminal;
|
|
auto* self = static_cast<SshTerminal*>(data);
|
|
self->connected_ = false;
|
|
self->child_pid_ = 0;
|
|
|
|
if (self->on_status_change) {
|
|
char msg[128];
|
|
snprintf(msg, sizeof(msg), "Process exited with status %d", status);
|
|
self->on_status_change(false, msg);
|
|
}
|
|
}
|
|
|
|
bool SshTerminal::connect(const std::string& host, int port, const std::string& user,
|
|
const std::string& keyfile, const std::string& password,
|
|
std::string& out_error) {
|
|
if (connected_) disconnect();
|
|
|
|
g_message("Connecting to %s@%s:%d ...", user.c_str(), host.c_str(), port);
|
|
|
|
/* Build ssh command arguments */
|
|
std::vector<std::string> args;
|
|
|
|
/* Use sshpass if password is provided and sshpass is available */
|
|
bool use_sshpass = false;
|
|
if (!password.empty()) {
|
|
if (g_find_program_in_path("sshpass")) {
|
|
use_sshpass = true;
|
|
args.push_back("sshpass");
|
|
args.push_back("-e");
|
|
g_message("Using sshpass for auto-login");
|
|
} else {
|
|
g_message("sshpass not found, ssh will prompt for password");
|
|
}
|
|
}
|
|
|
|
args.push_back("ssh");
|
|
args.push_back("-p");
|
|
args.push_back(std::to_string(port));
|
|
args.push_back("-o");
|
|
args.push_back("StrictHostKeyChecking=no");
|
|
args.push_back("-o");
|
|
args.push_back("UserKnownHostsFile=/dev/null");
|
|
args.push_back("-o");
|
|
args.push_back("LogLevel=QUIET");
|
|
|
|
if (!keyfile.empty()) {
|
|
args.push_back("-i");
|
|
args.push_back(keyfile);
|
|
g_message("Using keyfile: %s", keyfile.c_str());
|
|
}
|
|
|
|
args.push_back(user + "@" + host);
|
|
|
|
/* Convert to char** for spawn */
|
|
std::vector<char*> c_args;
|
|
for (auto& arg : args) {
|
|
c_args.push_back(const_cast<char*>(arg.c_str()));
|
|
}
|
|
c_args.push_back(nullptr);
|
|
|
|
/* Set up environment with password if using sshpass */
|
|
gchar** envp = nullptr;
|
|
if (use_sshpass) {
|
|
GHashTable* env_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
/* Copy current environment */
|
|
for (char** ep = environ; *ep; ep++) {
|
|
gchar** parts = g_strsplit(*ep, "=", 2);
|
|
if (parts[0] && parts[1]) {
|
|
g_hash_table_insert(env_table, g_strdup(parts[0]), g_strdup(parts[1]));
|
|
}
|
|
g_strfreev(parts);
|
|
}
|
|
|
|
g_hash_table_insert(env_table, g_strdup("SSHPASS"), g_strdup(password.c_str()));
|
|
|
|
/* Convert hash table to envp array */
|
|
GHashTableIter iter;
|
|
gpointer key, value;
|
|
GArray* env_arr = g_array_new(TRUE, TRUE, sizeof(gchar*));
|
|
|
|
g_hash_table_iter_init(&iter, env_table);
|
|
while (g_hash_table_iter_next(&iter, &key, &value)) {
|
|
gchar* entry = g_strdup_printf("%s=%s", (const char*)key, (const char*)value);
|
|
g_array_append_val(env_arr, entry);
|
|
}
|
|
envp = (gchar**)g_array_free(env_arr, FALSE);
|
|
g_hash_table_destroy(env_table);
|
|
}
|
|
|
|
/* Spawn ssh process */
|
|
GError* error = nullptr;
|
|
GPid child_pid = 0;
|
|
|
|
g_message("Spawning: %s", args[0].c_str());
|
|
|
|
gboolean ok = vte_terminal_spawn_sync(
|
|
terminal_,
|
|
VTE_PTY_DEFAULT,
|
|
nullptr, /* working directory */
|
|
c_args.data(),
|
|
envp, /* environment */
|
|
(GSpawnFlags)(G_SPAWN_CHILD_INHERITS_STDIN),
|
|
nullptr, /* child_setup */
|
|
nullptr, /* child_setup_data */
|
|
&child_pid,
|
|
nullptr, /* cancellable */
|
|
&error
|
|
);
|
|
|
|
if (envp) {
|
|
for (gchar** p = envp; *p; p++) g_free(*p);
|
|
g_free(envp);
|
|
}
|
|
|
|
if (!ok) {
|
|
out_error = error ? error->message : "Failed to spawn SSH process";
|
|
g_warning("spawn failed: %s", out_error.c_str());
|
|
if (error) g_error_free(error);
|
|
return false;
|
|
}
|
|
|
|
child_pid_ = child_pid;
|
|
connected_ = true;
|
|
|
|
g_message("SSH process spawned (PID %d)", (int)child_pid);
|
|
|
|
if (on_status_change)
|
|
on_status_change(true, "Connected to " + user + "@" + host + ":" + std::to_string(port));
|
|
|
|
return true;
|
|
}
|
|
|
|
void SshTerminal::disconnect() {
|
|
if (child_pid_ > 0) {
|
|
g_message("Killing SSH process (PID %d)", (int)child_pid_);
|
|
kill(child_pid_, SIGHUP);
|
|
g_spawn_close_pid(child_pid_);
|
|
child_pid_ = 0;
|
|
}
|
|
connected_ = false;
|
|
}
|