Add /names, Ctrl-P/N scrollback, charset logger, terminal fixes

- /names #channel to list users
- Ctrl-P/Ctrl-N for page up/down in scrollback
- Charset logger (irc.log) with hex dump and detection
- chartest.c utility for inspecting key codes
- Fix window redraw to align lines at bottom
- Fix terminal cleanup via atexit (no more broken terminal)
- Green prompt
This commit is contained in:
2026-04-29 12:26:29 +02:00
parent 71f6699aa9
commit 12a9127af8
2 changed files with 168 additions and 33 deletions
+32
View File
@@ -0,0 +1,32 @@
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
int main(void)
{
struct termios orig, raw;
tcgetattr(STDIN_FILENO, &orig);
raw = orig;
raw.c_lflag &= ~(ICANON | ECHO);
raw.c_cc[VMIN] = 1;
tcsetattr(STDIN_FILENO, TCSANOW, &raw);
printf("Press keys (Ctrl-D to quit):\n");
for (;;) {
unsigned char c;
if (read(STDIN_FILENO, &c, 1) <= 0 || c == 0x04)
break;
printf(" dec=%3d hex=0x%02X oct=%03o", c, c, c);
if (c >= 32 && c < 127)
printf(" char='%c'", c);
else if (c >= 0xC0)
printf(" (UTF-8 lead byte, %d-byte seq)",
c < 0xE0 ? 2 : c < 0xF0 ? 3 : 4);
else if ((c & 0xC0) == 0x80)
printf(" (UTF-8 continuation)");
printf("\n");
}
tcsetattr(STDIN_FILENO, TCSANOW, &orig);
return 0;
}
+134 -31
View File
@@ -30,6 +30,7 @@
#define SCROLLBACK 500
static int current_level = WL_STATUS;
static int scroll_offset = 0; /* 0 = bottom (live), >0 = scrolled up */
static volatile sig_atomic_t got_sigint = 0;
static volatile sig_atomic_t got_sigwinch = 0;
@@ -50,6 +51,7 @@ static char recv_buf[BUF_SIZE];
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;
/* Per-window scrollback buffer */
static struct {
@@ -134,24 +136,42 @@ static void buf_store(int level, const char *line)
static void redraw_window(void)
{
get_term_size();
int visible = term_rows - 2; /* scroll region height */
int visible = term_rows - 2;
int count = win_buf[current_level].count;
int show = count < visible ? count : visible;
/* Clamp scroll offset */
int max_scroll = count - visible;
if (max_scroll < 0) max_scroll = 0;
if (scroll_offset > max_scroll) scroll_offset = max_scroll;
int show = count - scroll_offset;
if (show > visible) show = visible;
if (show < 0) show = 0;
/* Clear scroll region */
for (int i = 1; i <= term_rows - 2; i++)
for (int i = 1; i <= visible; i++)
printf("\033[%d;1H\033[K", i);
/* Print last 'show' lines */
int start_idx;
if (count < SCROLLBACK)
start_idx = count - show;
else
start_idx = (win_buf[current_level].head - show + SCROLLBACK) % SCROLLBACK;
/* Calculate start index in circular buffer */
int end_pos = count - scroll_offset; /* logical end position */
int start_pos = end_pos - show; /* logical start position */
int buf_start;
if (count <= SCROLLBACK)
buf_start = start_pos;
else
buf_start = (win_buf[current_level].head - count + start_pos + SCROLLBACK) % SCROLLBACK;
/* Print lines aligned to bottom of scroll region */
int first_row = visible - show + 1;
for (int i = 0; i < show; i++) {
int idx = (start_idx + i) % SCROLLBACK;
printf("\033[%d;1H%s", i + 1, win_buf[current_level].lines[idx]);
int idx;
if (count <= SCROLLBACK)
idx = buf_start + i;
else
idx = (buf_start + i) % SCROLLBACK;
printf("\033[%d;1H%s", first_row + i,
win_buf[current_level].lines[idx]);
}
draw_statusbar();
@@ -181,13 +201,12 @@ static void wprintf(int level, const char *fmt, ...)
/* Only display if this is the current window */
if (level == current_level) {
/*
* Move to last line of scroll region, issue newline to
* scroll up, then print the text on the new blank line.
*/
if (scroll_offset == 0) {
printf("\033[%d;1H\n\033[K%s", term_rows - 2, timestamped);
draw_statusbar();
}
/* If scrolled up, don't disturb the view */
}
}
static void die(const char *msg)
@@ -368,8 +387,60 @@ static void update_my_prefix(const char *chan, const char *modestr,
}
}
static FILE *logfp = NULL;
static void log_raw(const char *line, size_t 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");
fflush(logfp);
}
static void handle_line(char *line)
{
size_t rawlen = strlen(line);
log_raw(line, rawlen);
char converted[BUF_SIZE];
to_iso8859_1((unsigned char *)line, strlen(line),
@@ -529,31 +600,30 @@ static void handle_line(char *line)
}
}
} else if (strcmp(cmd, "353") == 0 && params) {
/* RPL_NAMREPLY: <nick> = <channel> :names...
* Check our prefix in the names list */
/* RPL_NAMREPLY: <nick> = <channel> :names... */
char *colon = strchr(params, ':');
if (colon) {
/* Find channel name before the colon */
char *p = params;
char *chan = NULL;
char *sp;
/* Skip our nick */
sp = strchr(p, ' ');
if (sp) p = sp + 1;
/* Skip = or @ or * */
sp = strchr(p, ' ');
if (sp) p = sp + 1;
/* Channel */
sp = strchr(p, ' ');
if (sp) { *sp = '\0'; chan = p; }
if (chan) {
char *names = colon + 1;
int lvl = chan_to_level(chan);
wprintf(lvl, "[%s] %s\n", chan, names);
int idx = chan_win_idx(chan);
if (idx >= 0) {
colon++;
/* Find our nick in the list */
char *names = colon;
char *tok = strtok(names, " ");
/* Check our prefix */
char namecopy[512];
snprintf(namecopy, sizeof(namecopy), "%s", names);
char *tok = strtok(namecopy, " ");
while (tok) {
const char *n = tok;
char pf = '\0';
@@ -643,6 +713,10 @@ static void handle_input(char *line)
irc_send_raw("WHOIS %s", args);
} else if (strcasecmp(cmd, "wii") == 0 && args) {
irc_send_raw("WHOIS %s %s", args, args);
} else if (strcasecmp(cmd, "names") == 0) {
const char *chan = args ? args : current_channel();
if (chan[0])
irc_send_raw("NAMES %s", chan);
} else if (strcasecmp(cmd, "quit") == 0) {
irc_send_raw("QUIT :%s", args ? args : "See you later");
close(sock_fd);
@@ -702,6 +776,15 @@ static void redraw_input(const char *input_line, size_t input_len,
fflush(stdout);
}
static void cleanup(void)
{
printf("\033[1;%dr", term_rows); /* reset scroll region */
printf("\033[%d;1H\033[K", term_rows - 1);
printf("\033[%d;1H\033[K\n", term_rows);
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
if (logfp) fclose(logfp);
}
static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s <nick> <server> [port]\n", prog);
@@ -719,8 +802,12 @@ int main(int argc, char *argv[])
memset(win_chans, 0, sizeof(win_chans));
logfp = fopen("irc.log", "a");
if (logfp)
fprintf(logfp, "--- Session started ---\n");
/* Set terminal to raw mode */
struct termios orig_term, raw_term;
struct termios raw_term;
tcgetattr(STDIN_FILENO, &orig_term);
raw_term = orig_term;
raw_term.c_lflag &= ~(ICANON | ECHO);
@@ -728,6 +815,8 @@ int main(int argc, char *argv[])
raw_term.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &raw_term);
atexit(cleanup);
signal(SIGINT, sigint_handler);
signal(SIGWINCH, sigwinch_handler);
@@ -859,6 +948,7 @@ int main(int argc, char *argv[])
int lvl = ch - '1';
if (lvl < WL_MAX) {
current_level = lvl;
scroll_offset = 0;
redraw_window();
redraw_input(input_line, input_len, input_pos);
}
@@ -913,6 +1003,24 @@ int main(int argc, char *argv[])
memcpy(yank_buf, input_line + input_pos, yank_len);
input_len = input_pos;
redraw_input(input_line, input_len, input_pos);
} else if (ch == 0x10) {
/* Ctrl-P: page up in scrollback */
int page = term_rows - 3;
if (page < 1) page = 1;
scroll_offset += page;
int max_scroll = win_buf[current_level].count - (term_rows - 2);
if (max_scroll < 0) max_scroll = 0;
if (scroll_offset > max_scroll) scroll_offset = max_scroll;
redraw_window();
redraw_input(input_line, input_len, input_pos);
} else if (ch == 0x0E) {
/* Ctrl-N: page down in scrollback */
int page = term_rows - 3;
if (page < 1) page = 1;
scroll_offset -= page;
if (scroll_offset < 0) scroll_offset = 0;
redraw_window();
redraw_input(input_line, input_len, input_pos);
} else if (ch == 127 || ch == 0x08) {
if (input_pos > 0) {
size_t clen = utf8_back(input_line, input_pos);
@@ -956,11 +1064,6 @@ int main(int argc, char *argv[])
}
}
/* Restore terminal */
printf("\033[1;%dr", term_rows); /* reset scroll region */
printf("\033[%d;1H\033[K", term_rows - 1); /* clear status bar */
printf("\033[%d;1H\033[K", term_rows); /* clear input line */
tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
close(sock_fd);
return 0;
}