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
This commit is contained in:
2026-04-29 12:03:57 +02:00
parent e2a5ddf9a0
commit 71f6699aa9
3 changed files with 39 additions and 20 deletions
+2
View File
@@ -0,0 +1,2 @@
*.o
irc
+5 -6
View File
@@ -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 - **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 levels** — isolated windows with independent 500-line scrollback:
- Window 1: Status (server messages, numerics, notices) - Window 1: Status and private messages
- Window 2: Private messages - Windows 29: Channels
- Windows 39: Channels
- **Status bar** — shows current window, channel, nick prefix (@/+), and channel modes - **Status bar** — shows current window, channel, nick prefix (@/+), and channel modes
- **UTF-8 terminal support** — full multi-byte input editing - **UTF-8 terminal support** — full multi-byte input editing
- **CTCP VERSION** reply with OS info - **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 ## 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 2. `/join #channel` — the channel is bound to that window
3. Press ESC+4, `/join #other` — second channel on window 4 3. Press ESC+3, `/join #other` — second channel on window 3
4. ESC+1 to check status, ESC+2 for private messages 4. ESC+1 to check status and private messages
Each window maintains its own scrollback. Switching redraws the full history. Each window maintains its own scrollback. Switching redraws the full history.
+31 -13
View File
@@ -21,12 +21,12 @@
#define BUF_SIZE 4096 #define BUF_SIZE 4096
#define IRC_MAX 512 #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_STATUS 0
#define WL_MSG 1 #define WL_MSG 0
#define WL_CHAN 2 #define WL_CHAN 1
#define WL_MAX 9 #define WL_MAX 9
#define MAX_CHAN_WINS 7 #define MAX_CHAN_WINS 8
#define SCROLLBACK 500 #define SCROLLBACK 500
static int current_level = WL_STATUS; static int current_level = WL_STATUS;
@@ -70,6 +70,7 @@ static const char *current_channel(void)
{ {
if (current_level >= WL_CHAN && current_level < WL_MAX) { if (current_level >= WL_CHAN && current_level < WL_MAX) {
int idx = current_level - WL_CHAN; int idx = current_level - WL_CHAN;
if (idx < MAX_CHAN_WINS)
return win_chans[idx].name; return win_chans[idx].name;
} }
return ""; return "";
@@ -101,16 +102,13 @@ static void draw_statusbar(void)
if (win_chans[idx].my_prefix) if (win_chans[idx].my_prefix)
snprintf(prefix_str, sizeof(prefix_str), "%c", snprintf(prefix_str, sizeof(prefix_str), "%c",
win_chans[idx].my_prefix); win_chans[idx].my_prefix);
} else if (current_level == WL_MSG) {
chan = "(messages)";
} else { } else {
chan = "(status)"; chan = "(status)";
} }
snprintf(bar, sizeof(bar), " [%d:%s] %s%s %s%s%s", snprintf(bar, sizeof(bar), " [%d:%s] %s%s %s%s%s",
current_level + 1, current_level + 1,
current_level == WL_STATUS ? "status" : current_level == WL_STATUS ? "status" : chan,
current_level == WL_MSG ? "msg" : chan,
prefix_str, nick, prefix_str, nick,
cmodes[0] ? "[" : "", cmodes, cmodes[0] ? "]" : ""); 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 input_pos)
{ {
size_t cpos_cols = display_cols(input_line, 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); fwrite(input_line, 1, input_len, stdout);
printf("\033[%d;%dH", term_rows, (int)(cpos_cols + 3)); printf("\033[%d;%dH", term_rows, (int)(cpos_cols + 3));
fflush(stdout); fflush(stdout);
@@ -797,9 +795,28 @@ int main(int argc, char *argv[])
printf("\033[%d;1H\033[KWanna quit? [y/N] ", printf("\033[%d;1H\033[KWanna quit? [y/N] ",
term_rows); term_rows);
fflush(stdout); fflush(stdout);
unsigned char ans; char qbuf[16];
ssize_t r = read(STDIN_FILENO, &ans, 1); size_t qpos = 0;
if (r > 0 && (ans == 'y' || ans == 'Y')) { 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"); irc_send_raw("QUIT :Leaving");
break; break;
} }
@@ -843,6 +860,7 @@ int main(int argc, char *argv[])
if (lvl < WL_MAX) { if (lvl < WL_MAX) {
current_level = lvl; current_level = lvl;
redraw_window(); redraw_window();
redraw_input(input_line, input_len, input_pos);
} }
continue; continue;
} }
@@ -857,7 +875,7 @@ int main(int argc, char *argv[])
handle_input(input_line); handle_input(input_line);
input_pos = 0; input_pos = 0;
input_len = 0; input_len = 0;
printf("\033[%d;1H> ", term_rows); printf("\033[%d;1H\033[32m>\033[0m ", term_rows);
fflush(stdout); fflush(stdout);
} else if (ch == 0x01) { } else if (ch == 0x01) {
/* Ctrl-A: beginning of line */ /* Ctrl-A: beginning of line */