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:
@@ -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,12 +201,11 @@ 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.
|
||||
*/
|
||||
printf("\033[%d;1H\n\033[K%s", term_rows - 2, timestamped);
|
||||
draw_statusbar();
|
||||
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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user