From f4324ef2b47a51109acabd85158ca9fc32cf4c93 Mon Sep 17 00:00:00 2001 From: Anders Holck Date: Mon, 18 May 2026 08:40:12 +0200 Subject: [PATCH] Fix scrollback wrap bug, add /clear, add TCP keepalive - Fix off-by-one in redraw_window circular buffer indexing (<= to <) that caused stale content after 500 lines filled - Add /clear command to reset current window scrollback - Add TCP keepalive and application-level ping timeout to detect dead connections --- main.c | 86 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/main.c b/main.c index be1d215..cd3c6d4 100644 --- a/main.c +++ b/main.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "charset.h" @@ -56,6 +57,8 @@ static size_t recv_len = 0; static char nick[64] = "kiro_user"; static int term_rows = 24, term_cols = 80; static struct termios orig_term; +static time_t last_recv = 0; /* time of last data from server */ +static int ping_sent = 0; /* we sent a client PING, awaiting reply */ /* Per-window scrollback buffer */ static struct { @@ -81,6 +84,7 @@ static int translate_public = 0; /* /trans toggle: echo translations to channel static int translate_enabled = 1; /* master toggle for translation */ static int irc_colors = 1; /* display IRC colour codes as ANSI */ static char rent_msg[256] = "This space available for rent"; +static int log_enabled = 0; /* Track nicks who sent us private messages */ #define MAX_PM_NICKS 32 @@ -191,6 +195,8 @@ static void ai_config_load(void) irc_colors = atoi(eq); else if (strcmp(line, "rent") == 0) snprintf(rent_msg, sizeof(rent_msg), "%s", eq); + else if (strcmp(line, "log") == 0) + log_enabled = (strcmp(eq, "true") == 0 || atoi(eq)); } fclose(f); @@ -686,7 +692,7 @@ static void redraw_window(void) while (start_pos > 0 && rows_used < visible) { int li = start_pos - 1; int buf_idx; - if (count <= SCROLLBACK) + if (count < SCROLLBACK) buf_idx = li; else buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK; @@ -706,7 +712,7 @@ static void redraw_window(void) for (int i = 0; i < show; i++) { int li = start_pos + i; int buf_idx; - if (count <= SCROLLBACK) + if (count < SCROLLBACK) buf_idx = li; else buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK; @@ -821,6 +827,16 @@ static int irc_connect(const char *host, const char *port) if (fd < 0) die("connect"); + /* Enable TCP keepalive to detect dead connections */ + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)); + int idle = 60; + setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)); + int intvl = 15; + setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl)); + int cnt = 4; + setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt)); + return fd; } @@ -964,48 +980,12 @@ static FILE *logfp = NULL; static void log_raw(const char *line, size_t len) { + (void)len; if (!logfp) return; time_t now = time(NULL); struct tm *tm = localtime(&now); - fprintf(logfp, "[%02d:%02d:%02d] len=%zu charset=", - tm->tm_hour, tm->tm_min, tm->tm_sec, len); - - /* Detect charset */ - const unsigned char *u = (const unsigned char *)line; - int has_high = 0, valid_utf8 = 1; - for (size_t i = 0; i < len; i++) { - if (u[i] >= 0x80) { - has_high = 1; - if ((u[i] & 0xE0) == 0xC0) { - if (i+1 >= len || (u[i+1] & 0xC0) != 0x80) - valid_utf8 = 0; - else i += 1; - } else if ((u[i] & 0xF0) == 0xE0) { - if (i+2 >= len || (u[i+1] & 0xC0) != 0x80 || - (u[i+2] & 0xC0) != 0x80) - valid_utf8 = 0; - else i += 2; - } else if ((u[i] & 0xF8) == 0xF0) { - if (i+3 >= len || (u[i+1] & 0xC0) != 0x80 || - (u[i+2] & 0xC0) != 0x80 || (u[i+3] & 0xC0) != 0x80) - valid_utf8 = 0; - else i += 3; - } else { - valid_utf8 = 0; - } - } - } - if (!has_high) - fprintf(logfp, "ASCII"); - else if (valid_utf8) - fprintf(logfp, "UTF-8"); - else - fprintf(logfp, "ISO-8859-1"); - - fprintf(logfp, "\n text: %s\n hex: ", line); - for (size_t i = 0; i < len; i++) - fprintf(logfp, "%02X ", u[i]); - fprintf(logfp, "\n"); + fprintf(logfp, "[%02d:%02d:%02d] %s\n", + tm->tm_hour, tm->tm_min, tm->tm_sec, line); fflush(logfp); } @@ -1557,6 +1537,11 @@ static void handle_input(char *line) exit(0); } else if (strcasecmp(cmd, "raw") == 0 && args) { irc_send_raw("%s", args); + } else if (strcasecmp(cmd, "clear") == 0) { + win_buf[current_level].count = 0; + win_buf[current_level].head = 0; + scroll_offset = 0; + redraw_window(); } else { wprintf(current_level, "Unknown command: /%s\n", cmd); } @@ -1723,7 +1708,7 @@ int main(int argc, char *argv[]) ai_config_load(); - logfp = fopen("irc.log", "a"); + logfp = log_enabled ? fopen("irc.log", "a") : NULL; if (logfp) fprintf(logfp, "--- Session started ---\n"); @@ -1751,6 +1736,7 @@ int main(int argc, char *argv[]) wprintf(WL_STATUS, "Connecting to %s:%s as %s...\n", host, port, nick); sock_fd = irc_connect(host, port); + last_recv = time(NULL); wprintf(WL_STATUS, "Connected.\n"); @@ -1826,6 +1812,20 @@ int main(int argc, char *argv[]) } } + /* Detect dead connection: no data for 240s → send PING, + no reply after 60 more seconds → disconnect */ + if (last_recv > 0) { + time_t elapsed = time(NULL) - last_recv; + if (!ping_sent && elapsed >= 240) { + irc_send_raw("PING :keepalive"); + ping_sent = 1; + } else if (ping_sent && elapsed >= 300) { + wprintf(WL_STATUS, + "* No response from server (timeout)\n"); + break; + } + } + /* Check translation results */ for (int ti = 0; ti < translate_count; ) { if (FD_ISSET(translate_pending[ti].fd, &fds)) { @@ -1989,6 +1989,8 @@ int main(int argc, char *argv[]) } recv_len += (size_t)n; recv_buf[recv_len] = '\0'; + last_recv = time(NULL); + ping_sent = 0; process_recv(); }