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
This commit is contained in:
2026-05-18 08:40:12 +02:00
parent e11ba65538
commit f4324ef2b4
+44 -42
View File
@@ -16,6 +16,7 @@
#include <termios.h>
#include <pwd.h>
#include <signal.h>
#include <netinet/tcp.h>
#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();
}