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:
@@ -0,0 +1,2 @@
|
|||||||
|
*.o
|
||||||
|
irc
|
||||||
@@ -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 2–9: Channels
|
||||||
- Windows 3–9: 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.
|
||||||
|
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
Reference in New Issue
Block a user