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:
@@ -16,6 +16,7 @@
|
|||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
|
||||||
#include "charset.h"
|
#include "charset.h"
|
||||||
|
|
||||||
@@ -56,6 +57,8 @@ static size_t recv_len = 0;
|
|||||||
static char nick[64] = "kiro_user";
|
static char nick[64] = "kiro_user";
|
||||||
static int term_rows = 24, term_cols = 80;
|
static int term_rows = 24, term_cols = 80;
|
||||||
static struct termios orig_term;
|
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 */
|
/* Per-window scrollback buffer */
|
||||||
static struct {
|
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 translate_enabled = 1; /* master toggle for translation */
|
||||||
static int irc_colors = 1; /* display IRC colour codes as ANSI */
|
static int irc_colors = 1; /* display IRC colour codes as ANSI */
|
||||||
static char rent_msg[256] = "This space available for rent";
|
static char rent_msg[256] = "This space available for rent";
|
||||||
|
static int log_enabled = 0;
|
||||||
|
|
||||||
/* Track nicks who sent us private messages */
|
/* Track nicks who sent us private messages */
|
||||||
#define MAX_PM_NICKS 32
|
#define MAX_PM_NICKS 32
|
||||||
@@ -191,6 +195,8 @@ static void ai_config_load(void)
|
|||||||
irc_colors = atoi(eq);
|
irc_colors = atoi(eq);
|
||||||
else if (strcmp(line, "rent") == 0)
|
else if (strcmp(line, "rent") == 0)
|
||||||
snprintf(rent_msg, sizeof(rent_msg), "%s", eq);
|
snprintf(rent_msg, sizeof(rent_msg), "%s", eq);
|
||||||
|
else if (strcmp(line, "log") == 0)
|
||||||
|
log_enabled = (strcmp(eq, "true") == 0 || atoi(eq));
|
||||||
}
|
}
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
@@ -686,7 +692,7 @@ static void redraw_window(void)
|
|||||||
while (start_pos > 0 && rows_used < visible) {
|
while (start_pos > 0 && rows_used < visible) {
|
||||||
int li = start_pos - 1;
|
int li = start_pos - 1;
|
||||||
int buf_idx;
|
int buf_idx;
|
||||||
if (count <= SCROLLBACK)
|
if (count < SCROLLBACK)
|
||||||
buf_idx = li;
|
buf_idx = li;
|
||||||
else
|
else
|
||||||
buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK;
|
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++) {
|
for (int i = 0; i < show; i++) {
|
||||||
int li = start_pos + i;
|
int li = start_pos + i;
|
||||||
int buf_idx;
|
int buf_idx;
|
||||||
if (count <= SCROLLBACK)
|
if (count < SCROLLBACK)
|
||||||
buf_idx = li;
|
buf_idx = li;
|
||||||
else
|
else
|
||||||
buf_idx = (win_buf[current_level].head - count + li + SCROLLBACK) % SCROLLBACK;
|
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)
|
if (fd < 0)
|
||||||
die("connect");
|
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;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,48 +980,12 @@ static FILE *logfp = NULL;
|
|||||||
|
|
||||||
static void log_raw(const char *line, size_t len)
|
static void log_raw(const char *line, size_t len)
|
||||||
{
|
{
|
||||||
|
(void)len;
|
||||||
if (!logfp) return;
|
if (!logfp) return;
|
||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
struct tm *tm = localtime(&now);
|
struct tm *tm = localtime(&now);
|
||||||
fprintf(logfp, "[%02d:%02d:%02d] len=%zu charset=",
|
fprintf(logfp, "[%02d:%02d:%02d] %s\n",
|
||||||
tm->tm_hour, tm->tm_min, tm->tm_sec, len);
|
tm->tm_hour, tm->tm_min, tm->tm_sec, line);
|
||||||
|
|
||||||
/* 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");
|
|
||||||
fflush(logfp);
|
fflush(logfp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1557,6 +1537,11 @@ static void handle_input(char *line)
|
|||||||
exit(0);
|
exit(0);
|
||||||
} else if (strcasecmp(cmd, "raw") == 0 && args) {
|
} else if (strcasecmp(cmd, "raw") == 0 && args) {
|
||||||
irc_send_raw("%s", 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 {
|
} else {
|
||||||
wprintf(current_level, "Unknown command: /%s\n", cmd);
|
wprintf(current_level, "Unknown command: /%s\n", cmd);
|
||||||
}
|
}
|
||||||
@@ -1723,7 +1708,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
ai_config_load();
|
ai_config_load();
|
||||||
|
|
||||||
logfp = fopen("irc.log", "a");
|
logfp = log_enabled ? fopen("irc.log", "a") : NULL;
|
||||||
if (logfp)
|
if (logfp)
|
||||||
fprintf(logfp, "--- Session started ---\n");
|
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);
|
wprintf(WL_STATUS, "Connecting to %s:%s as %s...\n", host, port, nick);
|
||||||
|
|
||||||
sock_fd = irc_connect(host, port);
|
sock_fd = irc_connect(host, port);
|
||||||
|
last_recv = time(NULL);
|
||||||
|
|
||||||
wprintf(WL_STATUS, "Connected.\n");
|
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 */
|
/* Check translation results */
|
||||||
for (int ti = 0; ti < translate_count; ) {
|
for (int ti = 0; ti < translate_count; ) {
|
||||||
if (FD_ISSET(translate_pending[ti].fd, &fds)) {
|
if (FD_ISSET(translate_pending[ti].fd, &fds)) {
|
||||||
@@ -1989,6 +1989,8 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
recv_len += (size_t)n;
|
recv_len += (size_t)n;
|
||||||
recv_buf[recv_len] = '\0';
|
recv_buf[recv_len] = '\0';
|
||||||
|
last_recv = time(NULL);
|
||||||
|
ping_sent = 0;
|
||||||
process_recv();
|
process_recv();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user