From fe7050c01863420c6854d59fbd56e20bed68cd77 Mon Sep 17 00:00:00 2001 From: x3 Date: Mon, 7 Sep 2026 15:50:11 +0000 Subject: [PATCH] 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 --- src/main.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++ src/ssh_client.cpp | 7 ++++ src/ssh_terminal.cpp | 12 +++++- uninstall.sh | 53 ++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100755 uninstall.sh diff --git a/src/main.cpp b/src/main.cpp index 301e326..1b9f3c6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,109 @@ #include +#include +#include +#include #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[]) { + /* 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); + log_info("GTK initialized"); + log_info("User: %s", getenv("USER") ? getenv("USER") : "unknown"); + SshClientWindow window; + log_info("Main window created"); + window.run(); + cleanup_log(); return 0; } diff --git a/src/ssh_client.cpp b/src/ssh_client.cpp index 20aa4ad..22695c8 100644 --- a/src/ssh_client.cpp +++ b/src/ssh_client.cpp @@ -43,6 +43,7 @@ static void refresh_server_list_widget(GtkWidget* listbox, const std::vector& keys) { @@ -199,6 +200,7 @@ void SshClientWindow::build_ui() { gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); server_listbox_ = gtk_list_box_new(); 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_, "button-press-event", G_CALLBACK(on_server_row_button), this); 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& 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(); std::string tab_name = name.empty() ? (user + "@" + host) : name; term->set_server_name(tab_name); diff --git a/src/ssh_terminal.cpp b/src/ssh_terminal.cpp index 6527f05..6955d43 100644 --- a/src/ssh_terminal.cpp +++ b/src/ssh_terminal.cpp @@ -141,8 +141,10 @@ bool SshTerminal::connect(const std::string& host, int port, const std::string& pty_slave_ = -1; vte_pty_ = nullptr; + g_message("Connecting to %s:%d as %s ...", host.c_str(), port, user.c_str()); + 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()); 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) { std::string err = ssh_get_error(session_); + g_warning("ssh_connect failed: %s", err.c_str()); /* Manually free since cleanup_done_ is still true */ ssh_free(session_); 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()); if (rc != SSH_AUTH_SUCCESS) { std::string err = ssh_get_error(session_); + g_warning("Auth failed: %s", err.c_str()); ssh_disconnect(session_); ssh_free(session_); session_ = nullptr; @@ -256,6 +260,7 @@ auth_done: cleanup_done_ = false; connected_ = true; running_ = true; + g_message("SSH connection established, starting IO thread"); thread_ = g_thread_new("ssh_io", io_thread_func, this); if (on_status_change) on_status_change(true, "Connected to " + user + "@" + host); @@ -270,6 +275,11 @@ void SshTerminal::disconnect() { 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_) { g_object_unref(vte_pty_); vte_pty_ = nullptr; diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..344a9d2 --- /dev/null +++ b/uninstall.sh @@ -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/"