fix: critical bugs + .desktop file + system install
Bug fixes: - Fixed PTY bridge: IO thread now uses pty_slave_ instead of pty_master_ (remote output was never reaching the terminal) - Fixed double-delete crash on tab close by removing page-removed callback - Fixed port being ignored in password dialog (always used port 22) - Added EINTR handling in select() for IO thread - Added mutex for thread-safe cleanup - Added try/catch for std::stoi in server config parsing - VtePty lifecycle now managed properly (no double-close) - Redundant ssh_channel_poll_timeout call removed New: - Added .desktop file for system-wide application menu entry - Makefile.am installs desktop file to $(datadir)/applications - Makefile.am installs icon to $(datadir)/icons/hicolor/256x256/apps
This commit is contained in:
+9
-1
@@ -1,2 +1,10 @@
|
|||||||
SUBDIRS = src
|
SUBDIRS = src
|
||||||
EXTRA_DIST = README.md
|
EXTRA_DIST = README.md desktop/x3-ssh.desktop icons/x3-ssh.png
|
||||||
|
|
||||||
|
# Install desktop file to system applications directory
|
||||||
|
applicationsdir = $(datadir)/applications
|
||||||
|
dist_applications_DATA = desktop/x3-ssh.desktop
|
||||||
|
|
||||||
|
# Install icon to system hicolor theme
|
||||||
|
iconsdir = $(datadir)/icons/hicolor/256x256/apps
|
||||||
|
dist_icons_DATA = icons/x3-ssh.png
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=x3-ssh
|
||||||
|
GenericName=SSH Client
|
||||||
|
Comment=A GTK3 SSH client with tabbed terminal, server management and key storage
|
||||||
|
Exec=x3-ssh
|
||||||
|
Icon=x3-ssh
|
||||||
|
Terminal=false
|
||||||
|
Categories=Network;RemoteAccess;SSH;
|
||||||
|
Keywords=ssh;remote;terminal;shell;secure;
|
||||||
|
StartupNotify=true
|
||||||
@@ -52,7 +52,7 @@ void ServerStore::load() {
|
|||||||
std::string key = trim(line.substr(0, eq));
|
std::string key = trim(line.substr(0, eq));
|
||||||
std::string val = trim(line.substr(eq + 1));
|
std::string val = trim(line.substr(eq + 1));
|
||||||
if (key == "host") current.host = val;
|
if (key == "host") current.host = val;
|
||||||
else if (key == "port") current.port = std::stoi(val);
|
else if (key == "port") { try { current.port = std::stoi(val); } catch (...) { current.port = 22; } }
|
||||||
else if (key == "user") current.user = val;
|
else if (key == "user") current.user = val;
|
||||||
else if (key == "keyfile") current.keyfile = val;
|
else if (key == "keyfile") current.keyfile = val;
|
||||||
else if (key == "use_key") current.use_key = (val == "true");
|
else if (key == "use_key") current.use_key = (val == "true");
|
||||||
|
|||||||
+32
-34
@@ -52,7 +52,10 @@ SshClientWindow::SshClientWindow()
|
|||||||
}
|
}
|
||||||
|
|
||||||
SshClientWindow::~SshClientWindow() {
|
SshClientWindow::~SshClientWindow() {
|
||||||
for (auto* t : terminals_) delete t;
|
for (auto* t : terminals_) {
|
||||||
|
t->disconnect();
|
||||||
|
delete t;
|
||||||
|
}
|
||||||
terminals_.clear();
|
terminals_.clear();
|
||||||
if (window_) gtk_widget_destroy(window_);
|
if (window_) gtk_widget_destroy(window_);
|
||||||
}
|
}
|
||||||
@@ -67,7 +70,7 @@ void SshClientWindow::build_ui() {
|
|||||||
|
|
||||||
const char* icon_paths[] = {
|
const char* icon_paths[] = {
|
||||||
"icons/x3-ssh.png",
|
"icons/x3-ssh.png",
|
||||||
"/usr/share/icons/x3-ssh.png",
|
"/usr/share/icons/hicolor/256x256/apps/x3-ssh.png",
|
||||||
"/usr/local/share/icons/x3-ssh.png",
|
"/usr/local/share/icons/x3-ssh.png",
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
@@ -133,7 +136,6 @@ void SshClientWindow::build_toolbar() {
|
|||||||
void SshClientWindow::build_notebook() {
|
void SshClientWindow::build_notebook() {
|
||||||
notebook_ = gtk_notebook_new();
|
notebook_ = gtk_notebook_new();
|
||||||
gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook_), TRUE);
|
gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook_), TRUE);
|
||||||
g_signal_connect(notebook_, "page-removed", G_CALLBACK(on_tab_close), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshClientWindow::build_statusbar() {
|
void SshClientWindow::build_statusbar() {
|
||||||
@@ -183,31 +185,40 @@ void SshClientWindow::close_current_tab() {
|
|||||||
void SshClientWindow::close_tab_at(int idx) {
|
void SshClientWindow::close_tab_at(int idx) {
|
||||||
GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook_), idx);
|
GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook_), idx);
|
||||||
if (!page) return;
|
if (!page) return;
|
||||||
|
|
||||||
|
/* Find and disconnect the terminal first, before removing from notebook */
|
||||||
|
SshTerminal* found = nullptr;
|
||||||
for (auto it = terminals_.begin(); it != terminals_.end(); ++it) {
|
for (auto it = terminals_.begin(); it != terminals_.end(); ++it) {
|
||||||
if ((*it)->widget() == page) {
|
if ((*it)->widget() == page) {
|
||||||
(*it)->disconnect();
|
found = *it;
|
||||||
delete *it;
|
|
||||||
terminals_.erase(it);
|
terminals_.erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
found->disconnect();
|
||||||
|
delete found;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now safe to remove page — no page-removed callback will cause issues */
|
||||||
gtk_notebook_remove_page(GTK_NOTEBOOK(notebook_), idx);
|
gtk_notebook_remove_page(GTK_NOTEBOOK(notebook_), idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshClientWindow::on_tab_close(GtkNotebook* nb, GtkWidget* page, guint idx, gpointer d) {
|
void SshClientWindow::disconnect() {
|
||||||
(void)nb; (void)idx;
|
int idx = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook_));
|
||||||
auto* self = static_cast<SshClientWindow*>(d);
|
if (idx < 0) return;
|
||||||
for (auto it = self->terminals_.begin(); it != self->terminals_.end(); ++it) {
|
GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook_), idx);
|
||||||
if ((*it)->widget() == page) {
|
for (auto* t : terminals_) {
|
||||||
(*it)->disconnect();
|
if (t->widget() == page) {
|
||||||
delete *it;
|
t->disconnect();
|
||||||
self->terminals_.erase(it);
|
update_status("Disconnected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── connect / disconnect ─────────────────────────────────── */
|
/* ─── connect ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
void SshClientWindow::connect_to_server(const std::string& host, int port,
|
void SshClientWindow::connect_to_server(const std::string& host, int port,
|
||||||
const std::string& user, const std::string& keyfile,
|
const std::string& user, const std::string& keyfile,
|
||||||
@@ -239,19 +250,6 @@ void SshClientWindow::connect_to_server(const std::string& host, int port,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshClientWindow::disconnect() {
|
|
||||||
int idx = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook_));
|
|
||||||
if (idx < 0) return;
|
|
||||||
GtkWidget* page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook_), idx);
|
|
||||||
for (auto* t : terminals_) {
|
|
||||||
if (t->widget() == page) {
|
|
||||||
t->disconnect();
|
|
||||||
update_status("Disconnected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ─── quick connect ───────────────────────────────────────── */
|
/* ─── quick connect ───────────────────────────────────────── */
|
||||||
|
|
||||||
void SshClientWindow::on_quick_connect(GtkWidget*, gpointer d) {
|
void SshClientWindow::on_quick_connect(GtkWidget*, gpointer d) {
|
||||||
@@ -273,17 +271,17 @@ void SshClientWindow::on_quick_connect(GtkWidget*, gpointer d) {
|
|||||||
self->connect_to_server(s.host, s.port, s.user, s.keyfile, "", s.name);
|
self->connect_to_server(s.host, s.port, s.user, s.keyfile, "", s.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self->show_password_dialog(s.host, s.user, s.keyfile, s.name);
|
self->show_password_dialog(s.host, s.port, s.user, s.keyfile, s.name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->show_password_dialog(host, u, "", host);
|
self->show_password_dialog(host, port, u, "", host);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── password dialog ─────────────────────────────────────── */
|
/* ─── password dialog ─────────────────────────────────────── */
|
||||||
|
|
||||||
void SshClientWindow::show_password_dialog(const std::string& host, const std::string& user,
|
void SshClientWindow::show_password_dialog(const std::string& host, int port,
|
||||||
const std::string& keyfile, const std::string& name) {
|
const std::string& user, const std::string& keyfile, const std::string& name) {
|
||||||
|
|
||||||
GtkWidget* dialog = gtk_dialog_new_with_buttons(
|
GtkWidget* dialog = gtk_dialog_new_with_buttons(
|
||||||
"SSH Authentication", GTK_WINDOW(window_),
|
"SSH Authentication", GTK_WINDOW(window_),
|
||||||
@@ -303,7 +301,7 @@ void SshClientWindow::show_password_dialog(const std::string& host, const std::s
|
|||||||
|
|
||||||
GtkWidget* info_label = gtk_label_new(nullptr);
|
GtkWidget* info_label = gtk_label_new(nullptr);
|
||||||
char markup[512];
|
char markup[512];
|
||||||
snprintf(markup, sizeof(markup), "<b>Connect to %s@%s</b>", user.c_str(), host.c_str());
|
snprintf(markup, sizeof(markup), "<b>Connect to %s@%s:%d</b>", user.c_str(), host.c_str(), port);
|
||||||
gtk_label_set_markup(GTK_LABEL(info_label), markup);
|
gtk_label_set_markup(GTK_LABEL(info_label), markup);
|
||||||
gtk_grid_attach(GTK_GRID(grid), info_label, 0, 0, 2, 1);
|
gtk_grid_attach(GTK_GRID(grid), info_label, 0, 0, 2, 1);
|
||||||
|
|
||||||
@@ -322,7 +320,7 @@ void SshClientWindow::show_password_dialog(const std::string& host, const std::s
|
|||||||
|
|
||||||
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
|
||||||
const char* password = gtk_entry_get_text(GTK_ENTRY(pass_entry));
|
const char* password = gtk_entry_get_text(GTK_ENTRY(pass_entry));
|
||||||
connect_to_server(host, 22, user, keyfile, password ? password : "", name);
|
connect_to_server(host, port, user, keyfile, password ? password : "", name);
|
||||||
}
|
}
|
||||||
gtk_widget_destroy(dialog);
|
gtk_widget_destroy(dialog);
|
||||||
}
|
}
|
||||||
@@ -662,7 +660,7 @@ void SshClientWindow::on_close_tab(GtkWidget*, gpointer d) {
|
|||||||
|
|
||||||
void SshClientWindow::on_connect(GtkWidget*, gpointer d) {
|
void SshClientWindow::on_connect(GtkWidget*, gpointer d) {
|
||||||
auto* self = static_cast<SshClientWindow*>(d);
|
auto* self = static_cast<SshClientWindow*>(d);
|
||||||
self->show_password_dialog("localhost", "root", "", "Local");
|
self->show_password_dialog("localhost", 22, "root", "", "Local");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SshClientWindow::on_disconnect(GtkWidget*, gpointer d) {
|
void SshClientWindow::on_disconnect(GtkWidget*, gpointer d) {
|
||||||
|
|||||||
+1
-2
@@ -30,7 +30,7 @@ private:
|
|||||||
|
|
||||||
void show_server_dialog(int edit_index = -1);
|
void show_server_dialog(int edit_index = -1);
|
||||||
void show_key_dialog();
|
void show_key_dialog();
|
||||||
void show_password_dialog(const std::string& host, const std::string& user,
|
void show_password_dialog(const std::string& host, int port, const std::string& user,
|
||||||
const std::string& keyfile, const std::string& name);
|
const std::string& keyfile, const std::string& name);
|
||||||
void update_server_combo();
|
void update_server_combo();
|
||||||
void update_status(const std::string& msg);
|
void update_status(const std::string& msg);
|
||||||
@@ -42,7 +42,6 @@ private:
|
|||||||
static void on_manage_servers(GtkWidget* w, gpointer d);
|
static void on_manage_servers(GtkWidget* w, gpointer d);
|
||||||
static void on_manage_keys(GtkWidget* w, gpointer d);
|
static void on_manage_keys(GtkWidget* w, gpointer d);
|
||||||
static void on_quick_connect(GtkWidget* w, gpointer d);
|
static void on_quick_connect(GtkWidget* w, gpointer d);
|
||||||
static void on_tab_close(GtkNotebook* nb, GtkWidget* page, guint idx, gpointer d);
|
|
||||||
|
|
||||||
GtkWidget* window_;
|
GtkWidget* window_;
|
||||||
GtkWidget* notebook_;
|
GtkWidget* notebook_;
|
||||||
|
|||||||
+51
-34
@@ -4,19 +4,19 @@
|
|||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
SshTerminal::SshTerminal()
|
SshTerminal::SshTerminal()
|
||||||
: terminal_(nullptr), vte_box_(nullptr), session_(nullptr), channel_(nullptr),
|
: terminal_(nullptr), vte_box_(nullptr), vte_pty_(nullptr),
|
||||||
pty_master_(-1), pty_slave_(-1), running_(false), connected_(false), thread_(nullptr) {
|
session_(nullptr), channel_(nullptr),
|
||||||
|
pty_master_(-1), pty_slave_(-1),
|
||||||
|
running_(false), connected_(false), cleanup_done_(true), thread_(nullptr) {
|
||||||
|
|
||||||
vte_box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
vte_box_ = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||||
terminal_ = VTE_TERMINAL(vte_terminal_new());
|
terminal_ = VTE_TERMINAL(vte_terminal_new());
|
||||||
gtk_box_pack_start(GTK_BOX(vte_box_), GTK_WIDGET(terminal_), TRUE, TRUE, 0);
|
gtk_box_pack_start(GTK_BOX(vte_box_), GTK_WIDGET(terminal_), TRUE, TRUE, 0);
|
||||||
gtk_widget_show_all(vte_box_);
|
gtk_widget_show_all(vte_box_);
|
||||||
|
|
||||||
VtePty *pty = vte_terminal_pty_new_sync(terminal_, VTE_PTY_DEFAULT, nullptr, nullptr);
|
|
||||||
(void)pty;
|
|
||||||
|
|
||||||
g_signal_connect(terminal_, "size-changed", G_CALLBACK(on_size_changed), this);
|
g_signal_connect(terminal_, "size-changed", G_CALLBACK(on_size_changed), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +34,10 @@ void SshTerminal::on_size_changed(VteTerminal* term, guint cols, guint rows, gpo
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SshTerminal::cleanup() {
|
void SshTerminal::cleanup() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
if (cleanup_done_) return;
|
||||||
|
cleanup_done_ = true;
|
||||||
|
|
||||||
if (channel_) {
|
if (channel_) {
|
||||||
ssh_channel_close(channel_);
|
ssh_channel_close(channel_);
|
||||||
ssh_channel_free(channel_);
|
ssh_channel_free(channel_);
|
||||||
@@ -44,19 +48,23 @@ void SshTerminal::cleanup() {
|
|||||||
ssh_free(session_);
|
ssh_free(session_);
|
||||||
session_ = nullptr;
|
session_ = nullptr;
|
||||||
}
|
}
|
||||||
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
|
||||||
if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; }
|
if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; }
|
||||||
|
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
||||||
connected_ = false;
|
connected_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
gpointer SshTerminal::io_thread_func(gpointer data) {
|
gpointer SshTerminal::io_thread_func(gpointer data) {
|
||||||
auto* self = static_cast<SshTerminal*>(data);
|
auto* self = static_cast<SshTerminal*>(data);
|
||||||
int ssh_fd = ssh_get_fd(self->session_);
|
|
||||||
if (ssh_fd < 0) {
|
ssh_session sess = self->session_;
|
||||||
self->cleanup();
|
ssh_channel chan = self->channel_;
|
||||||
|
int ssh_fd = ssh_get_fd(sess);
|
||||||
|
int slave_fd = self->pty_slave_;
|
||||||
|
|
||||||
|
if (ssh_fd < 0 || slave_fd < 0) {
|
||||||
g_idle_add([](gpointer d) -> gboolean {
|
g_idle_add([](gpointer d) -> gboolean {
|
||||||
auto* s = static_cast<SshTerminal*>(d);
|
auto* s = static_cast<SshTerminal*>(d);
|
||||||
if (s->on_status_change) s->on_status_change(false, "SSH get_fd failed");
|
if (s->on_status_change) s->on_status_change(false, "SSH fd unavailable");
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}, self);
|
}, self);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -68,40 +76,44 @@ gpointer SshTerminal::io_thread_func(gpointer data) {
|
|||||||
|
|
||||||
while (self->running_) {
|
while (self->running_) {
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
FD_SET(self->pty_master_, &rfds);
|
FD_SET(slave_fd, &rfds);
|
||||||
FD_SET(ssh_fd, &rfds);
|
FD_SET(ssh_fd, &rfds);
|
||||||
int maxfd = (self->pty_master_ > ssh_fd ? self->pty_master_ : ssh_fd) + 1;
|
int maxfd = (slave_fd > ssh_fd ? slave_fd : ssh_fd) + 1;
|
||||||
|
|
||||||
tv.tv_sec = 0;
|
tv.tv_sec = 0;
|
||||||
tv.tv_usec = 50000;
|
tv.tv_usec = 50000;
|
||||||
int ret = select(maxfd, &rfds, nullptr, nullptr, &tv);
|
int ret = select(maxfd, &rfds, nullptr, nullptr, &tv);
|
||||||
|
|
||||||
if (ret < 0) break;
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (ret == 0) continue;
|
if (ret == 0) continue;
|
||||||
|
|
||||||
if (FD_ISSET(self->pty_master_, &rfds)) {
|
if (FD_ISSET(slave_fd, &rfds)) {
|
||||||
ssize_t n = read(self->pty_master_, buf, sizeof(buf));
|
ssize_t n = read(slave_fd, buf, sizeof(buf));
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
ssh_channel_write(self->channel_, buf, n);
|
if (ssh_channel_write(chan, buf, n) < 0) break;
|
||||||
} else if (n <= 0) {
|
} else if (n <= 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FD_ISSET(ssh_fd, &rfds)) {
|
if (FD_ISSET(ssh_fd, &rfds)) {
|
||||||
if (ssh_channel_poll_timeout(self->channel_, 0, 0) > 0) {
|
int poll_result = ssh_channel_poll_timeout(chan, 50, 0);
|
||||||
ssize_t n = ssh_channel_read_timeout(self->channel_, buf, sizeof(buf), 0, 0);
|
if (poll_result > 0) {
|
||||||
|
ssize_t n = ssh_channel_read_timeout(chan, buf, sizeof(buf), 0, 0);
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
write(self->pty_master_, buf, n);
|
if (write(slave_fd, buf, n) < 0 && errno != EINTR) break;
|
||||||
} else if (n == SSH_EOF || n == SSH_ERROR) {
|
} else if (n == SSH_EOF || n == SSH_ERROR) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (ssh_channel_poll_timeout(self->channel_, 0, 0) < 0) {
|
} else if (poll_result < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ssh_channel_is_eof(self->channel_)) break;
|
if (ssh_channel_is_eof(chan)) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->connected_ = false;
|
self->connected_ = false;
|
||||||
@@ -193,18 +205,19 @@ auth_done:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
VtePty *pty = vte_terminal_get_pty(terminal_);
|
/* VTE gets the master side of the pty. It reads terminal output from
|
||||||
if (pty) {
|
master and writes user input to master. Our IO thread uses the slave
|
||||||
int vte_fd = vte_pty_get_fd(pty);
|
side to bridge between the pty and the SSH channel. */
|
||||||
if (vte_fd >= 0) close(vte_fd);
|
vte_pty_ = vte_pty_new_foreign_sync(pty_master_, nullptr, nullptr);
|
||||||
}
|
if (vte_pty_) {
|
||||||
vte_terminal_set_pty(terminal_, nullptr);
|
vte_terminal_set_pty(terminal_, vte_pty_);
|
||||||
|
} else {
|
||||||
VtePty *new_pty = vte_pty_new_foreign_sync(pty_master_, nullptr, nullptr);
|
cleanup();
|
||||||
if (new_pty) {
|
if (on_status_change) on_status_change(false, "VTE pty setup failed");
|
||||||
vte_terminal_set_pty(terminal_, new_pty);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanup_done_ = false;
|
||||||
connected_ = true;
|
connected_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
thread_ = g_thread_new("ssh_io", io_thread_func, this);
|
thread_ = g_thread_new("ssh_io", io_thread_func, this);
|
||||||
@@ -216,13 +229,17 @@ auth_done:
|
|||||||
void SshTerminal::disconnect() {
|
void SshTerminal::disconnect() {
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|
||||||
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
|
||||||
if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; }
|
|
||||||
|
|
||||||
if (thread_) {
|
if (thread_) {
|
||||||
g_thread_join(thread_);
|
g_thread_join(thread_);
|
||||||
thread_ = nullptr;
|
thread_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clean up VTE pty first (releases master fd ownership) */
|
||||||
|
if (vte_pty_) {
|
||||||
|
g_object_unref(vte_pty_);
|
||||||
|
vte_pty_ = nullptr;
|
||||||
|
vte_terminal_set_pty(terminal_, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <libssh/libssh.h>
|
#include <libssh/libssh.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <mutex>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
class SshTerminal {
|
class SshTerminal {
|
||||||
@@ -28,12 +29,15 @@ private:
|
|||||||
|
|
||||||
GtkWidget* vte_box_;
|
GtkWidget* vte_box_;
|
||||||
VteTerminal* terminal_;
|
VteTerminal* terminal_;
|
||||||
|
VtePty* vte_pty_;
|
||||||
ssh_session session_;
|
ssh_session session_;
|
||||||
ssh_channel channel_;
|
ssh_channel channel_;
|
||||||
int pty_master_;
|
int pty_master_;
|
||||||
int pty_slave_;
|
int pty_slave_;
|
||||||
std::atomic<bool> running_;
|
std::atomic<bool> running_;
|
||||||
std::atomic<bool> connected_;
|
std::atomic<bool> connected_;
|
||||||
|
std::atomic<bool> cleanup_done_;
|
||||||
GThread* thread_;
|
GThread* thread_;
|
||||||
|
std::mutex mutex_;
|
||||||
std::string server_name_;
|
std::string server_name_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user