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:
+32
@@ -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;
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
#define SCROLLBACK 500
|
#define SCROLLBACK 500
|
||||||
|
|
||||||
static int current_level = WL_STATUS;
|
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_sigint = 0;
|
||||||
static volatile sig_atomic_t got_sigwinch = 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 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;
|
||||||
|
|
||||||
/* Per-window scrollback buffer */
|
/* Per-window scrollback buffer */
|
||||||
static struct {
|
static struct {
|
||||||
@@ -134,24 +136,42 @@ static void buf_store(int level, const char *line)
|
|||||||
static void redraw_window(void)
|
static void redraw_window(void)
|
||||||
{
|
{
|
||||||
get_term_size();
|
get_term_size();
|
||||||
int visible = term_rows - 2; /* scroll region height */
|
int visible = term_rows - 2;
|
||||||
int count = win_buf[current_level].count;
|
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 */
|
/* 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);
|
printf("\033[%d;1H\033[K", i);
|
||||||
|
|
||||||
/* Print last 'show' lines */
|
/* Calculate start index in circular buffer */
|
||||||
int start_idx;
|
int end_pos = count - scroll_offset; /* logical end position */
|
||||||
if (count < SCROLLBACK)
|
int start_pos = end_pos - show; /* logical start position */
|
||||||
start_idx = count - show;
|
|
||||||
else
|
|
||||||
start_idx = (win_buf[current_level].head - show + SCROLLBACK) % SCROLLBACK;
|
|
||||||
|
|
||||||
|
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++) {
|
for (int i = 0; i < show; i++) {
|
||||||
int idx = (start_idx + i) % SCROLLBACK;
|
int idx;
|
||||||
printf("\033[%d;1H%s", i + 1, win_buf[current_level].lines[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();
|
draw_statusbar();
|
||||||
@@ -181,13 +201,12 @@ static void wprintf(int level, const char *fmt, ...)
|
|||||||
|
|
||||||
/* Only display if this is the current window */
|
/* Only display if this is the current window */
|
||||||
if (level == current_level) {
|
if (level == current_level) {
|
||||||
/*
|
if (scroll_offset == 0) {
|
||||||
* Move to last line of scroll region, issue newline to
|
|
||||||
* scroll up, then print the text on the new blank line.
|
|
||||||
*/
|
|
||||||
printf("\033[%d;1H\n\033[K%s", term_rows - 2, timestamped);
|
printf("\033[%d;1H\n\033[K%s", term_rows - 2, timestamped);
|
||||||
draw_statusbar();
|
draw_statusbar();
|
||||||
}
|
}
|
||||||
|
/* If scrolled up, don't disturb the view */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void die(const char *msg)
|
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)
|
static void handle_line(char *line)
|
||||||
{
|
{
|
||||||
|
size_t rawlen = strlen(line);
|
||||||
|
log_raw(line, rawlen);
|
||||||
|
|
||||||
char converted[BUF_SIZE];
|
char converted[BUF_SIZE];
|
||||||
|
|
||||||
to_iso8859_1((unsigned char *)line, strlen(line),
|
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) {
|
} else if (strcmp(cmd, "353") == 0 && params) {
|
||||||
/* RPL_NAMREPLY: <nick> = <channel> :names...
|
/* RPL_NAMREPLY: <nick> = <channel> :names... */
|
||||||
* Check our prefix in the names list */
|
|
||||||
char *colon = strchr(params, ':');
|
char *colon = strchr(params, ':');
|
||||||
if (colon) {
|
if (colon) {
|
||||||
/* Find channel name before the colon */
|
|
||||||
char *p = params;
|
char *p = params;
|
||||||
char *chan = NULL;
|
char *chan = NULL;
|
||||||
char *sp;
|
char *sp;
|
||||||
/* Skip our nick */
|
|
||||||
sp = strchr(p, ' ');
|
sp = strchr(p, ' ');
|
||||||
if (sp) p = sp + 1;
|
if (sp) p = sp + 1;
|
||||||
/* Skip = or @ or * */
|
|
||||||
sp = strchr(p, ' ');
|
sp = strchr(p, ' ');
|
||||||
if (sp) p = sp + 1;
|
if (sp) p = sp + 1;
|
||||||
/* Channel */
|
|
||||||
sp = strchr(p, ' ');
|
sp = strchr(p, ' ');
|
||||||
if (sp) { *sp = '\0'; chan = p; }
|
if (sp) { *sp = '\0'; chan = p; }
|
||||||
|
|
||||||
if (chan) {
|
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);
|
int idx = chan_win_idx(chan);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
colon++;
|
/* Check our prefix */
|
||||||
/* Find our nick in the list */
|
char namecopy[512];
|
||||||
char *names = colon;
|
snprintf(namecopy, sizeof(namecopy), "%s", names);
|
||||||
char *tok = strtok(names, " ");
|
char *tok = strtok(namecopy, " ");
|
||||||
while (tok) {
|
while (tok) {
|
||||||
const char *n = tok;
|
const char *n = tok;
|
||||||
char pf = '\0';
|
char pf = '\0';
|
||||||
@@ -643,6 +713,10 @@ static void handle_input(char *line)
|
|||||||
irc_send_raw("WHOIS %s", args);
|
irc_send_raw("WHOIS %s", args);
|
||||||
} else if (strcasecmp(cmd, "wii") == 0 && args) {
|
} else if (strcasecmp(cmd, "wii") == 0 && args) {
|
||||||
irc_send_raw("WHOIS %s %s", args, 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) {
|
} else if (strcasecmp(cmd, "quit") == 0) {
|
||||||
irc_send_raw("QUIT :%s", args ? args : "See you later");
|
irc_send_raw("QUIT :%s", args ? args : "See you later");
|
||||||
close(sock_fd);
|
close(sock_fd);
|
||||||
@@ -702,6 +776,15 @@ static void redraw_input(const char *input_line, size_t input_len,
|
|||||||
fflush(stdout);
|
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)
|
static void usage(const char *prog)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Usage: %s <nick> <server> [port]\n", 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));
|
memset(win_chans, 0, sizeof(win_chans));
|
||||||
|
|
||||||
|
logfp = fopen("irc.log", "a");
|
||||||
|
if (logfp)
|
||||||
|
fprintf(logfp, "--- Session started ---\n");
|
||||||
|
|
||||||
/* Set terminal to raw mode */
|
/* Set terminal to raw mode */
|
||||||
struct termios orig_term, raw_term;
|
struct termios raw_term;
|
||||||
tcgetattr(STDIN_FILENO, &orig_term);
|
tcgetattr(STDIN_FILENO, &orig_term);
|
||||||
raw_term = orig_term;
|
raw_term = orig_term;
|
||||||
raw_term.c_lflag &= ~(ICANON | ECHO);
|
raw_term.c_lflag &= ~(ICANON | ECHO);
|
||||||
@@ -728,6 +815,8 @@ int main(int argc, char *argv[])
|
|||||||
raw_term.c_cc[VTIME] = 0;
|
raw_term.c_cc[VTIME] = 0;
|
||||||
tcsetattr(STDIN_FILENO, TCSANOW, &raw_term);
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw_term);
|
||||||
|
|
||||||
|
atexit(cleanup);
|
||||||
|
|
||||||
signal(SIGINT, sigint_handler);
|
signal(SIGINT, sigint_handler);
|
||||||
signal(SIGWINCH, sigwinch_handler);
|
signal(SIGWINCH, sigwinch_handler);
|
||||||
|
|
||||||
@@ -859,6 +948,7 @@ int main(int argc, char *argv[])
|
|||||||
int lvl = ch - '1';
|
int lvl = ch - '1';
|
||||||
if (lvl < WL_MAX) {
|
if (lvl < WL_MAX) {
|
||||||
current_level = lvl;
|
current_level = lvl;
|
||||||
|
scroll_offset = 0;
|
||||||
redraw_window();
|
redraw_window();
|
||||||
redraw_input(input_line, input_len, input_pos);
|
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);
|
memcpy(yank_buf, input_line + input_pos, yank_len);
|
||||||
input_len = input_pos;
|
input_len = input_pos;
|
||||||
redraw_input(input_line, 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) {
|
} else if (ch == 127 || ch == 0x08) {
|
||||||
if (input_pos > 0) {
|
if (input_pos > 0) {
|
||||||
size_t clen = utf8_back(input_line, input_pos);
|
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);
|
close(sock_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user