From 71f6699aa9462655ecd16a7e62947af1c4170f01 Mon Sep 17 00:00:00 2001 From: Anders Holck Date: Wed, 29 Apr 2026 12:03:57 +0200 Subject: [PATCH] Add window levels, status bar, UTF-8 input, charset conversion - Isolated window levels with 500-line scrollback per window - Window 1: status + private messages - Windows 2-9: channels - Status bar showing window, channel, nick prefix, channel modes - Automatic charset conversion (UTF-8/UTF-16 -> ISO-8859-1 on wire) - UTF-8 aware input editing with Ctrl-A/E/U/K/Y - CTCP VERSION reply with real OS info from uname() - Real name from passwd GECOS field - SIGWINCH handling for terminal resize - Ctrl-C quit confirmation (y + Enter) - /whois, /wii, /mode, /quit with default reason - Green prompt, timestamps on all messages --- .gitignore | 2 ++ README.md | 11 +++++------ main.c | 46 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a35eec5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +irc diff --git a/README.md b/README.md index 1250779..3dca16b 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,8 @@ A lightweight terminal IRC client written in C with automatic charset conversion - **Automatic charset conversion** — detects UTF-8, UTF-16 (BOM), and ISO-8859-1 input; always sends ISO-8859-1 on the wire - **Window levels** — isolated windows with independent 500-line scrollback: - - Window 1: Status (server messages, numerics, notices) - - Window 2: Private messages - - Windows 3–9: Channels + - Window 1: Status and private messages + - Windows 2–9: Channels - **Status bar** — shows current window, channel, nick prefix (@/+), and channel modes - **UTF-8 terminal support** — full multi-byte input editing - **CTCP VERSION** reply with OS info @@ -63,10 +62,10 @@ Typing text without a `/` prefix sends to the channel on the current window. ## Window Workflow -1. Press ESC+3 to switch to window 3 +1. Press ESC+2 to switch to window 2 2. `/join #channel` — the channel is bound to that window -3. Press ESC+4, `/join #other` — second channel on window 4 -4. ESC+1 to check status, ESC+2 for private messages +3. Press ESC+3, `/join #other` — second channel on window 3 +4. ESC+1 to check status and private messages Each window maintains its own scrollback. Switching redraws the full history. diff --git a/main.c b/main.c index e26f032..5af2cda 100644 --- a/main.c +++ b/main.c @@ -21,12 +21,12 @@ #define BUF_SIZE 4096 #define IRC_MAX 512 -/* Window levels: 0=status, 1=msg, 2-8=channels */ +/* Window levels: 0=status+msg, 1-7=channels */ #define WL_STATUS 0 -#define WL_MSG 1 -#define WL_CHAN 2 +#define WL_MSG 0 +#define WL_CHAN 1 #define WL_MAX 9 -#define MAX_CHAN_WINS 7 +#define MAX_CHAN_WINS 8 #define SCROLLBACK 500 static int current_level = WL_STATUS; @@ -70,7 +70,8 @@ static const char *current_channel(void) { if (current_level >= WL_CHAN && current_level < WL_MAX) { int idx = current_level - WL_CHAN; - return win_chans[idx].name; + if (idx < MAX_CHAN_WINS) + return win_chans[idx].name; } return ""; } @@ -101,16 +102,13 @@ static void draw_statusbar(void) if (win_chans[idx].my_prefix) snprintf(prefix_str, sizeof(prefix_str), "%c", win_chans[idx].my_prefix); - } else if (current_level == WL_MSG) { - chan = "(messages)"; } else { chan = "(status)"; } snprintf(bar, sizeof(bar), " [%d:%s] %s%s %s%s%s", current_level + 1, - current_level == WL_STATUS ? "status" : - current_level == WL_MSG ? "msg" : chan, + current_level == WL_STATUS ? "status" : chan, prefix_str, nick, cmodes[0] ? "[" : "", cmodes, cmodes[0] ? "]" : ""); @@ -698,7 +696,7 @@ static void redraw_input(const char *input_line, size_t input_len, size_t input_pos) { size_t cpos_cols = display_cols(input_line, input_pos); - printf("\033[%d;1H\033[K> ", term_rows); + printf("\033[%d;1H\033[K\033[32m>\033[0m ", term_rows); fwrite(input_line, 1, input_len, stdout); printf("\033[%d;%dH", term_rows, (int)(cpos_cols + 3)); fflush(stdout); @@ -797,9 +795,28 @@ int main(int argc, char *argv[]) printf("\033[%d;1H\033[KWanna quit? [y/N] ", term_rows); fflush(stdout); - unsigned char ans; - ssize_t r = read(STDIN_FILENO, &ans, 1); - if (r > 0 && (ans == 'y' || ans == 'Y')) { + char qbuf[16]; + size_t qpos = 0; + int quit = 0; + for (;;) { + unsigned char qch; + ssize_t r = read(STDIN_FILENO, &qch, 1); + if (r <= 0) break; + if (qch == '\r' || qch == '\n') { + quit = (qpos > 0 && + (qbuf[0] == 'y' || qbuf[0] == 'Y')); + break; + } else if ((qch == 127 || qch == 0x08) && qpos > 0) { + qpos--; + printf("\b \b"); + fflush(stdout); + } else if (qch >= 32 && qpos < sizeof(qbuf) - 1) { + qbuf[qpos++] = qch; + ssize_t w = write(STDOUT_FILENO, &qch, 1); + (void)w; + } + } + if (quit) { irc_send_raw("QUIT :Leaving"); break; } @@ -843,6 +860,7 @@ int main(int argc, char *argv[]) if (lvl < WL_MAX) { current_level = lvl; redraw_window(); + redraw_input(input_line, input_len, input_pos); } continue; } @@ -857,7 +875,7 @@ int main(int argc, char *argv[]) handle_input(input_line); input_pos = 0; input_len = 0; - printf("\033[%d;1H> ", term_rows); + printf("\033[%d;1H\033[32m>\033[0m ", term_rows); fflush(stdout); } else if (ch == 0x01) { /* Ctrl-A: beginning of line */