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:
+51
-34
@@ -4,19 +4,19 @@
|
||||
#include <sys/select.h>
|
||||
#include <signal.h>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
|
||||
SshTerminal::SshTerminal()
|
||||
: terminal_(nullptr), vte_box_(nullptr), session_(nullptr), channel_(nullptr),
|
||||
pty_master_(-1), pty_slave_(-1), running_(false), connected_(false), thread_(nullptr) {
|
||||
: terminal_(nullptr), vte_box_(nullptr), vte_pty_(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);
|
||||
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_);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ void SshTerminal::on_size_changed(VteTerminal* term, guint cols, guint rows, gpo
|
||||
}
|
||||
|
||||
void SshTerminal::cleanup() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (cleanup_done_) return;
|
||||
cleanup_done_ = true;
|
||||
|
||||
if (channel_) {
|
||||
ssh_channel_close(channel_);
|
||||
ssh_channel_free(channel_);
|
||||
@@ -44,19 +48,23 @@ void SshTerminal::cleanup() {
|
||||
ssh_free(session_);
|
||||
session_ = nullptr;
|
||||
}
|
||||
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
||||
if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; }
|
||||
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
gpointer SshTerminal::io_thread_func(gpointer data) {
|
||||
auto* self = static_cast<SshTerminal*>(data);
|
||||
int ssh_fd = ssh_get_fd(self->session_);
|
||||
if (ssh_fd < 0) {
|
||||
self->cleanup();
|
||||
|
||||
ssh_session sess = self->session_;
|
||||
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 {
|
||||
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;
|
||||
}, self);
|
||||
return nullptr;
|
||||
@@ -68,40 +76,44 @@ gpointer SshTerminal::io_thread_func(gpointer data) {
|
||||
|
||||
while (self->running_) {
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(self->pty_master_, &rfds);
|
||||
FD_SET(slave_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_usec = 50000;
|
||||
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 (FD_ISSET(self->pty_master_, &rfds)) {
|
||||
ssize_t n = read(self->pty_master_, buf, sizeof(buf));
|
||||
if (FD_ISSET(slave_fd, &rfds)) {
|
||||
ssize_t n = read(slave_fd, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
ssh_channel_write(self->channel_, buf, n);
|
||||
if (ssh_channel_write(chan, buf, n) < 0) break;
|
||||
} else if (n <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(ssh_fd, &rfds)) {
|
||||
if (ssh_channel_poll_timeout(self->channel_, 0, 0) > 0) {
|
||||
ssize_t n = ssh_channel_read_timeout(self->channel_, buf, sizeof(buf), 0, 0);
|
||||
int poll_result = ssh_channel_poll_timeout(chan, 50, 0);
|
||||
if (poll_result > 0) {
|
||||
ssize_t n = ssh_channel_read_timeout(chan, buf, sizeof(buf), 0, 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) {
|
||||
break;
|
||||
}
|
||||
} else if (ssh_channel_poll_timeout(self->channel_, 0, 0) < 0) {
|
||||
} else if (poll_result < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ssh_channel_is_eof(self->channel_)) break;
|
||||
if (ssh_channel_is_eof(chan)) break;
|
||||
}
|
||||
|
||||
self->connected_ = false;
|
||||
@@ -193,18 +205,19 @@ auth_done:
|
||||
return false;
|
||||
}
|
||||
|
||||
VtePty *pty = vte_terminal_get_pty(terminal_);
|
||||
if (pty) {
|
||||
int vte_fd = vte_pty_get_fd(pty);
|
||||
if (vte_fd >= 0) close(vte_fd);
|
||||
}
|
||||
vte_terminal_set_pty(terminal_, nullptr);
|
||||
|
||||
VtePty *new_pty = vte_pty_new_foreign_sync(pty_master_, nullptr, nullptr);
|
||||
if (new_pty) {
|
||||
vte_terminal_set_pty(terminal_, new_pty);
|
||||
/* VTE gets the master side of the pty. It reads terminal output from
|
||||
master and writes user input to master. Our IO thread uses the slave
|
||||
side to bridge between the pty and the SSH channel. */
|
||||
vte_pty_ = vte_pty_new_foreign_sync(pty_master_, nullptr, nullptr);
|
||||
if (vte_pty_) {
|
||||
vte_terminal_set_pty(terminal_, vte_pty_);
|
||||
} else {
|
||||
cleanup();
|
||||
if (on_status_change) on_status_change(false, "VTE pty setup failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup_done_ = false;
|
||||
connected_ = true;
|
||||
running_ = true;
|
||||
thread_ = g_thread_new("ssh_io", io_thread_func, this);
|
||||
@@ -216,13 +229,17 @@ auth_done:
|
||||
void SshTerminal::disconnect() {
|
||||
running_ = false;
|
||||
|
||||
if (pty_master_ >= 0) { close(pty_master_); pty_master_ = -1; }
|
||||
if (pty_slave_ >= 0) { close(pty_slave_); pty_slave_ = -1; }
|
||||
|
||||
if (thread_) {
|
||||
g_thread_join(thread_);
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user