Fix: sidebar refresh after edit, crash on connect, live log, uninstall script

- Fix sidebar disappearing after edit: add gtk_widget_show_all after rebuild
- Fix crash on connect: flush pending GLib idle callbacks in disconnect()
- Fix single-click: explicitly set activate_on_single_click to FALSE
- Add live log output when started from terminal (stderr + ~/.config/sshclient/x3-ssh.log)
- Add logging to SSH connect, auth, and key operations
- Add uninstall.sh script to remove system-wide installation
This commit is contained in:
x3
2026-09-07 15:50:11 +00:00
parent 6e719c3236
commit fe7050c018
4 changed files with 169 additions and 1 deletions
+98
View File
@@ -1,11 +1,109 @@
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include "ssh_client.h" #include "ssh_client.h"
static FILE* log_file_ = nullptr;
static bool log_to_terminal_ = false;
static void log_message(const char* level, const char* fmt, va_list args) {
char timestamp[32];
time_t now = time(nullptr);
strftime(timestamp, sizeof(timestamp), "%H:%M:%S", localtime(&now));
char msg[2048];
vsnprintf(msg, sizeof(msg), fmt, args);
if (log_to_terminal_) {
fprintf(stderr, "[%s] [%s] %s\n", timestamp, level, msg);
fflush(stderr);
}
if (log_file_) {
fprintf(log_file_, "[%s] [%s] %s\n", timestamp, level, msg);
fflush(log_file_);
}
}
static void log_info(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
log_message("INFO", fmt, args);
va_end(args);
}
static void log_error(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
log_message("ERROR", fmt, args);
va_end(args);
}
static void log_debug(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
log_message("DEBUG", fmt, args);
va_end(args);
}
static void g_log_handler(const gchar* log_domain, GLogLevelFlags log_level,
const gchar* message, gpointer user_data) {
(void)log_domain; (void)user_data;
switch (log_level & G_LOG_LEVEL_MASK) {
case G_LOG_LEVEL_ERROR: log_error("%s", message); break;
case G_LOG_LEVEL_CRITICAL: log_error("CRITICAL: %s", message); break;
case G_LOG_LEVEL_WARNING: log_error("WARNING: %s", message); break;
case G_LOG_LEVEL_MESSAGE: log_info("%s", message); break;
case G_LOG_LEVEL_INFO: log_info("%s", message); break;
case G_LOG_LEVEL_DEBUG: log_debug("%s", message); break;
default: log_info("%s", message); break;
}
}
static void cleanup_log() {
if (log_file_) {
log_info("x3-ssh shutting down");
fclose(log_file_);
log_file_ = nullptr;
}
}
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
/* Detect if stderr is a terminal (not redirected) */
log_to_terminal_ = isatty(STDERR_FILENO);
/* Open log file */
const char* home = getenv("HOME");
if (home) {
std::string log_dir = std::string(home) + "/.config/sshclient";
std::string mkdir_cmd = "mkdir -p \"" + log_dir + "\"";
(void)system(mkdir_cmd.c_str());
std::string log_path = log_dir + "/x3-ssh.log";
log_file_ = fopen(log_path.c_str(), "a");
}
log_info("x3-ssh starting (PID %d)", getpid());
log_info("Terminal logging: %s", log_to_terminal_ ? "enabled" : "disabled");
log_info("Log file: %s", log_file_ ? "open" : "failed to open");
/* Set up GLib log handler */
g_log_set_handler("x3-ssh",
(GLogLevelFlags)(G_LOG_LEVEL_MASK),
g_log_handler, nullptr);
g_log_set_handler(nullptr,
(GLogLevelFlags)(G_LOG_LEVEL_MASK),
g_log_handler, nullptr);
gtk_init(&argc, &argv); gtk_init(&argc, &argv);
log_info("GTK initialized");
log_info("User: %s", getenv("USER") ? getenv("USER") : "unknown");
SshClientWindow window; SshClientWindow window;
log_info("Main window created");
window.run(); window.run();
cleanup_log();
return 0; return 0;
} }
+7
View File
@@ -43,6 +43,7 @@ static void refresh_server_list_widget(GtkWidget* listbox, const std::vector<Ser
gtk_list_box_insert(GTK_LIST_BOX(listbox), row_box, -1); gtk_list_box_insert(GTK_LIST_BOX(listbox), row_box, -1);
} }
gtk_widget_show_all(listbox);
} }
static void refresh_key_list_widget(GtkWidget* listbox, const std::vector<SshKey>& keys) { static void refresh_key_list_widget(GtkWidget* listbox, const std::vector<SshKey>& keys) {
@@ -199,6 +200,7 @@ void SshClientWindow::build_ui() {
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
server_listbox_ = gtk_list_box_new(); server_listbox_ = gtk_list_box_new();
gtk_list_box_set_selection_mode(GTK_LIST_BOX(server_listbox_), GTK_SELECTION_SINGLE); gtk_list_box_set_selection_mode(GTK_LIST_BOX(server_listbox_), GTK_SELECTION_SINGLE);
gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(server_listbox_), FALSE);
g_signal_connect(server_listbox_, "row-activated", G_CALLBACK(on_server_row_activated), this); g_signal_connect(server_listbox_, "row-activated", G_CALLBACK(on_server_row_activated), this);
g_signal_connect(server_listbox_, "button-press-event", G_CALLBACK(on_server_row_button), this); g_signal_connect(server_listbox_, "button-press-event", G_CALLBACK(on_server_row_button), this);
gtk_container_add(GTK_CONTAINER(scrolled), server_listbox_); gtk_container_add(GTK_CONTAINER(scrolled), server_listbox_);
@@ -315,6 +317,11 @@ 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,
const std::string& password, const std::string& name) { const std::string& password, const std::string& name) {
g_message("connect_to_server: %s@%s:%d (key=%s, saved_pw=%s)",
user.c_str(), host.c_str(), port,
keyfile.empty() ? "none" : keyfile.c_str(),
!password.empty() ? "yes" : "no");
SshTerminal* term = new SshTerminal(); SshTerminal* term = new SshTerminal();
std::string tab_name = name.empty() ? (user + "@" + host) : name; std::string tab_name = name.empty() ? (user + "@" + host) : name;
term->set_server_name(tab_name); term->set_server_name(tab_name);
+11 -1
View File
@@ -141,8 +141,10 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string&
pty_slave_ = -1; pty_slave_ = -1;
vte_pty_ = nullptr; vte_pty_ = nullptr;
g_message("Connecting to %s:%d as %s ...", host.c_str(), port, user.c_str());
session_ = ssh_new(); session_ = ssh_new();
if (!session_) return false; if (!session_) { g_warning("ssh_new() failed"); return false; }
ssh_options_set(session_, SSH_OPTIONS_HOST, host.c_str()); ssh_options_set(session_, SSH_OPTIONS_HOST, host.c_str());
unsigned int p = port; unsigned int p = port;
@@ -153,6 +155,7 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string&
if (ssh_connect(session_) != SSH_OK) { if (ssh_connect(session_) != SSH_OK) {
std::string err = ssh_get_error(session_); std::string err = ssh_get_error(session_);
g_warning("ssh_connect failed: %s", err.c_str());
/* Manually free since cleanup_done_ is still true */ /* Manually free since cleanup_done_ is still true */
ssh_free(session_); ssh_free(session_);
session_ = nullptr; session_ = nullptr;
@@ -177,6 +180,7 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string&
int rc = ssh_userauth_password(session_, nullptr, password.c_str()); int rc = ssh_userauth_password(session_, nullptr, password.c_str());
if (rc != SSH_AUTH_SUCCESS) { if (rc != SSH_AUTH_SUCCESS) {
std::string err = ssh_get_error(session_); std::string err = ssh_get_error(session_);
g_warning("Auth failed: %s", err.c_str());
ssh_disconnect(session_); ssh_disconnect(session_);
ssh_free(session_); ssh_free(session_);
session_ = nullptr; session_ = nullptr;
@@ -256,6 +260,7 @@ auth_done:
cleanup_done_ = false; cleanup_done_ = false;
connected_ = true; connected_ = true;
running_ = true; running_ = true;
g_message("SSH connection established, starting IO thread");
thread_ = g_thread_new("ssh_io", io_thread_func, this); thread_ = g_thread_new("ssh_io", io_thread_func, this);
if (on_status_change) on_status_change(true, "Connected to " + user + "@" + host); if (on_status_change) on_status_change(true, "Connected to " + user + "@" + host);
@@ -270,6 +275,11 @@ void SshTerminal::disconnect() {
thread_ = nullptr; thread_ = nullptr;
} }
/* Flush any pending idle callbacks from the IO thread
that might still reference this object */
while (g_main_context_pending(NULL))
g_main_context_iteration(NULL, FALSE);
if (vte_pty_) { if (vte_pty_) {
g_object_unref(vte_pty_); g_object_unref(vte_pty_);
vte_pty_ = nullptr; vte_pty_ = nullptr;
Executable
+53
View File
@@ -0,0 +1,53 @@
#!/bin/bash
# x3-ssh uninstaller
# Run as root (sudo) to remove system-wide installation
set -e
BIN="/usr/local/bin/x3-ssh"
DESKTOP="/usr/share/applications/x3-ssh.desktop"
ICON_HIERARCHY="/usr/share/icons/hicolor/256x256/apps/x3-ssh.png"
ICON_SCALABLE="/usr/share/icons/hicolor/scalable/apps/x3-ssh.svg"
ICON_LEGACY="/usr/local/share/icons/x3-ssh.png"
echo "x3-ssh uninstaller"
echo "=================="
echo ""
if [ "$EUID" -ne 0 ]; then
echo "Error: Run this script as root (sudo ./uninstall.sh)"
exit 1
fi
removed=0
remove_file() {
if [ -f "$1" ]; then
rm -f "$1"
echo " Removed: $1"
removed=$((removed + 1))
fi
}
echo "Removing binary..."
remove_file "$BIN"
echo "Removing desktop file..."
remove_file "$DESKTOP"
echo "Removing icons..."
remove_file "$ICON_HIERARCHY"
remove_file "$ICON_SCALABLE"
remove_file "$ICON_LEGACY"
echo ""
if [ $removed -gt 0 ]; then
echo "Done. $removed file(s) removed."
else
echo "Nothing to remove - x3-ssh is not installed."
fi
echo ""
echo "Note: User config was NOT removed."
echo " Config dir: ~/.config/sshclient/"
echo " To remove config: rm -rf ~/.config/sshclient/"