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:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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/"
|
||||||
Reference in New Issue
Block a user