first commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -pedantic -O2
|
||||||
|
LDFLAGS =
|
||||||
|
TARGET = irc
|
||||||
|
SRCS = main.c charset.c
|
||||||
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJS) $(TARGET)
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Mirk — Holck's IRC Client
|
||||||
|
|
||||||
|
A lightweight terminal IRC client written in C with automatic charset conversion and ircII-style window levels.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **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
|
||||||
|
- **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
|
||||||
|
- **SIGWINCH** handling (terminal resize)
|
||||||
|
- **Ident** — works with system identd on port 113
|
||||||
|
- **Real name** from passwd GECOS field
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires only a C compiler and POSIX headers. No external dependencies.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
./irc <nick> <server> [port]
|
||||||
|
```
|
||||||
|
|
||||||
|
Port defaults to 6667.
|
||||||
|
|
||||||
|
## Key Bindings
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|-----|--------|
|
||||||
|
| ESC+1–9 | Switch window |
|
||||||
|
| Ctrl-A | Beginning of line |
|
||||||
|
| Ctrl-E | End of line |
|
||||||
|
| Ctrl-U | Kill to beginning (yank buffer) |
|
||||||
|
| Ctrl-K | Kill to end (yank buffer) |
|
||||||
|
| Ctrl-Y | Yank (paste) |
|
||||||
|
| Ctrl-D | Quit (EOF) |
|
||||||
|
| Ctrl-C | Quit prompt (Y/N, default N) |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/join #channel` | Join channel (assigned to current window) |
|
||||||
|
| `/part [#channel]` | Part channel (defaults to current) |
|
||||||
|
| `/msg <target> <text>` | Send private message |
|
||||||
|
| `/nick <newnick>` | Change nickname |
|
||||||
|
| `/mode <target> <modes>` | Set mode |
|
||||||
|
| `/whois <nick>` | WHOIS query |
|
||||||
|
| `/wii <nick>` | Extended WHOIS (queries remote server) |
|
||||||
|
| `/quit [reason]` | Quit (default: "See you later") |
|
||||||
|
| `/raw <line>` | Send raw IRC command |
|
||||||
|
|
||||||
|
Typing text without a `/` prefix sends to the channel on the current window.
|
||||||
|
|
||||||
|
## Window Workflow
|
||||||
|
|
||||||
|
1. Press ESC+3 to switch to window 3
|
||||||
|
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
|
||||||
|
|
||||||
|
Each window maintains its own scrollback. Switching redraws the full history.
|
||||||
|
|
||||||
|
## CTCP VERSION Reply
|
||||||
|
|
||||||
|
```
|
||||||
|
Holck's Mirk, OS: Linux 6.x.x x86_64 :: This space available for rent
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Public domain.
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
#include "charset.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static int is_utf16_le(const unsigned char *in, size_t len)
|
||||||
|
{
|
||||||
|
return len >= 2 && in[0] == 0xFF && in[1] == 0xFE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_utf16_be(const unsigned char *in, size_t len)
|
||||||
|
{
|
||||||
|
return len >= 2 && in[0] == 0xFE && in[1] == 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_utf8(const unsigned char *in, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
int has_multibyte = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (in[i] < 0x80)
|
||||||
|
continue;
|
||||||
|
has_multibyte = 1;
|
||||||
|
if ((in[i] & 0xE0) == 0xC0) {
|
||||||
|
if (i + 1 >= len || (in[i+1] & 0xC0) != 0x80)
|
||||||
|
return 0;
|
||||||
|
i += 1;
|
||||||
|
} else if ((in[i] & 0xF0) == 0xE0) {
|
||||||
|
if (i + 2 >= len || (in[i+1] & 0xC0) != 0x80 ||
|
||||||
|
(in[i+2] & 0xC0) != 0x80)
|
||||||
|
return 0;
|
||||||
|
i += 2;
|
||||||
|
} else if ((in[i] & 0xF8) == 0xF0) {
|
||||||
|
if (i + 3 >= len || (in[i+1] & 0xC0) != 0x80 ||
|
||||||
|
(in[i+2] & 0xC0) != 0x80 || (in[i+3] & 0xC0) != 0x80)
|
||||||
|
return 0;
|
||||||
|
i += 3;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return has_multibyte;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int utf8_to_codepoint(const unsigned char *in, size_t remaining,
|
||||||
|
unsigned int *cp)
|
||||||
|
{
|
||||||
|
if (in[0] < 0x80) {
|
||||||
|
*cp = in[0];
|
||||||
|
return 1;
|
||||||
|
} else if ((in[0] & 0xE0) == 0xC0) {
|
||||||
|
if (remaining < 2) return 0;
|
||||||
|
*cp = ((in[0] & 0x1F) << 6) | (in[1] & 0x3F);
|
||||||
|
return 2;
|
||||||
|
} else if ((in[0] & 0xF0) == 0xE0) {
|
||||||
|
if (remaining < 3) return 0;
|
||||||
|
*cp = ((in[0] & 0x0F) << 12) | ((in[1] & 0x3F) << 6) |
|
||||||
|
(in[2] & 0x3F);
|
||||||
|
return 3;
|
||||||
|
} else if ((in[0] & 0xF8) == 0xF0) {
|
||||||
|
if (remaining < 4) return 0;
|
||||||
|
*cp = ((in[0] & 0x07) << 18) | ((in[1] & 0x3F) << 12) |
|
||||||
|
((in[2] & 0x3F) << 6) | (in[3] & 0x3F);
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int utf16le_to_iso8859_1(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
size_t i, o = 0;
|
||||||
|
|
||||||
|
for (i = 2; i + 1 < in_len && o + 1 < out_size; i += 2) {
|
||||||
|
unsigned int cp = in[i] | (in[i+1] << 8);
|
||||||
|
out[o++] = cp <= 0xFF ? (char)cp : '?';
|
||||||
|
}
|
||||||
|
out[o] = '\0';
|
||||||
|
return (int)o;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int utf16be_to_iso8859_1(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
size_t i, o = 0;
|
||||||
|
|
||||||
|
for (i = 2; i + 1 < in_len && o + 1 < out_size; i += 2) {
|
||||||
|
unsigned int cp = (in[i] << 8) | in[i+1];
|
||||||
|
out[o++] = cp <= 0xFF ? (char)cp : '?';
|
||||||
|
}
|
||||||
|
out[o] = '\0';
|
||||||
|
return (int)o;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int utf8_to_iso8859_1(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
size_t i = 0, o = 0;
|
||||||
|
|
||||||
|
while (i < in_len && o + 1 < out_size) {
|
||||||
|
unsigned int cp;
|
||||||
|
int consumed = utf8_to_codepoint(in + i, in_len - i, &cp);
|
||||||
|
if (consumed == 0) {
|
||||||
|
out[o++] = '?';
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[o++] = cp <= 0xFF ? (char)cp : '?';
|
||||||
|
i += consumed;
|
||||||
|
}
|
||||||
|
out[o] = '\0';
|
||||||
|
return (int)o;
|
||||||
|
}
|
||||||
|
|
||||||
|
int to_iso8859_1(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_size)
|
||||||
|
{
|
||||||
|
if (!in || !out || out_size == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (in_len == 0) {
|
||||||
|
out[0] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_utf16_le(in, in_len))
|
||||||
|
return utf16le_to_iso8859_1(in, in_len, out, out_size);
|
||||||
|
|
||||||
|
if (is_utf16_be(in, in_len))
|
||||||
|
return utf16be_to_iso8859_1(in, in_len, out, out_size);
|
||||||
|
|
||||||
|
if (is_utf8(in, in_len))
|
||||||
|
return utf8_to_iso8859_1(in, in_len, out, out_size);
|
||||||
|
|
||||||
|
/* Already ISO-8859-1 / ASCII — passthrough */
|
||||||
|
size_t copy = in_len < out_size - 1 ? in_len : out_size - 1;
|
||||||
|
memcpy(out, in, copy);
|
||||||
|
out[copy] = '\0';
|
||||||
|
return (int)copy;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef CHARSET_H
|
||||||
|
#define CHARSET_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert input (UTF-8, UTF-16 with BOM, or ISO-8859-1) to ISO-8859-1.
|
||||||
|
* Unmappable characters (> U+00FF) are replaced with '?'.
|
||||||
|
* Returns number of bytes written (excluding null terminator).
|
||||||
|
* Output is always null-terminated.
|
||||||
|
*/
|
||||||
|
int to_iso8859_1(const unsigned char *in, size_t in_len,
|
||||||
|
char *out, size_t out_size);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,948 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include "charset.h"
|
||||||
|
|
||||||
|
#define BUF_SIZE 4096
|
||||||
|
#define IRC_MAX 512
|
||||||
|
|
||||||
|
/* Window levels: 0=status, 1=msg, 2-8=channels */
|
||||||
|
#define WL_STATUS 0
|
||||||
|
#define WL_MSG 1
|
||||||
|
#define WL_CHAN 2
|
||||||
|
#define WL_MAX 9
|
||||||
|
#define MAX_CHAN_WINS 7
|
||||||
|
#define SCROLLBACK 500
|
||||||
|
|
||||||
|
static int current_level = WL_STATUS;
|
||||||
|
static volatile sig_atomic_t got_sigint = 0;
|
||||||
|
static volatile sig_atomic_t got_sigwinch = 0;
|
||||||
|
|
||||||
|
static void sigint_handler(int sig)
|
||||||
|
{
|
||||||
|
(void)sig;
|
||||||
|
got_sigint = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sigwinch_handler(int sig)
|
||||||
|
{
|
||||||
|
(void)sig;
|
||||||
|
got_sigwinch = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sock_fd = -1;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/* Per-window scrollback buffer */
|
||||||
|
static struct {
|
||||||
|
char lines[SCROLLBACK][512];
|
||||||
|
int count; /* total lines stored (up to SCROLLBACK) */
|
||||||
|
int head; /* next write position (circular) */
|
||||||
|
} win_buf[WL_MAX];
|
||||||
|
|
||||||
|
/* Per-channel window state */
|
||||||
|
static struct {
|
||||||
|
char name[128]; /* channel name */
|
||||||
|
char modes[64]; /* channel modes (e.g. "+nt") */
|
||||||
|
char my_prefix; /* '@', '+', or '\0' */
|
||||||
|
} win_chans[MAX_CHAN_WINS];
|
||||||
|
|
||||||
|
/* Get the active channel for current window, or "" */
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_term_size(void)
|
||||||
|
{
|
||||||
|
struct winsize ws;
|
||||||
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
|
||||||
|
term_rows = ws.ws_row;
|
||||||
|
term_cols = ws.ws_col;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_statusbar(void)
|
||||||
|
{
|
||||||
|
get_term_size();
|
||||||
|
char bar[512];
|
||||||
|
const char *chan = "";
|
||||||
|
char prefix_str[4] = "";
|
||||||
|
const char *cmodes = "";
|
||||||
|
|
||||||
|
if (current_level >= WL_CHAN && current_level < WL_MAX) {
|
||||||
|
int idx = current_level - WL_CHAN;
|
||||||
|
chan = win_chans[idx].name;
|
||||||
|
cmodes = win_chans[idx].modes;
|
||||||
|
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,
|
||||||
|
prefix_str, nick,
|
||||||
|
cmodes[0] ? "[" : "", cmodes, cmodes[0] ? "]" : "");
|
||||||
|
|
||||||
|
/* Status bar on second-to-last row */
|
||||||
|
printf("\033[%d;1H\033[7m%-*.*s\033[0m",
|
||||||
|
term_rows - 1, term_cols, term_cols, bar);
|
||||||
|
/* Move cursor to input line */
|
||||||
|
printf("\033[%d;1H", term_rows);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Store a line in a window's scrollback */
|
||||||
|
static void buf_store(int level, const char *line)
|
||||||
|
{
|
||||||
|
if (level < 0 || level >= WL_MAX) return;
|
||||||
|
snprintf(win_buf[level].lines[win_buf[level].head], 512, "%s", line);
|
||||||
|
win_buf[level].head = (win_buf[level].head + 1) % SCROLLBACK;
|
||||||
|
if (win_buf[level].count < SCROLLBACK)
|
||||||
|
win_buf[level].count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Redraw the current window's scrollback */
|
||||||
|
static void redraw_window(void)
|
||||||
|
{
|
||||||
|
get_term_size();
|
||||||
|
int visible = term_rows - 2; /* scroll region height */
|
||||||
|
int count = win_buf[current_level].count;
|
||||||
|
int show = count < visible ? count : visible;
|
||||||
|
|
||||||
|
/* Clear scroll region */
|
||||||
|
for (int i = 1; i <= term_rows - 2; 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;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_statusbar();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wprintf(int level, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
char msg[512];
|
||||||
|
char timestamped[512];
|
||||||
|
time_t now = time(NULL);
|
||||||
|
struct tm *tm = localtime(&now);
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
/* Add timestamp */
|
||||||
|
snprintf(timestamped, sizeof(timestamped), "%02d:%02d %.500s",
|
||||||
|
tm->tm_hour, tm->tm_min, msg);
|
||||||
|
|
||||||
|
/* Remove trailing newline for storage */
|
||||||
|
size_t len = strlen(timestamped);
|
||||||
|
if (len > 0 && timestamped[len-1] == '\n')
|
||||||
|
timestamped[len-1] = '\0';
|
||||||
|
buf_store(level, timestamped);
|
||||||
|
|
||||||
|
/* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void die(const char *msg)
|
||||||
|
{
|
||||||
|
perror(msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sock_write(const void *buf, size_t len)
|
||||||
|
{
|
||||||
|
ssize_t n = write(sock_fd, buf, len);
|
||||||
|
(void)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void irc_send_raw(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
char buf[IRC_MAX];
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vsnprintf(buf, sizeof(buf) - 2, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
size_t len = strlen(buf);
|
||||||
|
buf[len] = '\r';
|
||||||
|
buf[len + 1] = '\n';
|
||||||
|
len += 2;
|
||||||
|
|
||||||
|
sock_write(buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void irc_send_converted(const char *prefix, const unsigned char *text,
|
||||||
|
size_t text_len)
|
||||||
|
{
|
||||||
|
char converted[IRC_MAX];
|
||||||
|
char line[IRC_MAX];
|
||||||
|
|
||||||
|
to_iso8859_1(text, text_len, converted, sizeof(converted) / 2);
|
||||||
|
size_t len = (size_t)snprintf(line, sizeof(line) - 2, "%s%.200s",
|
||||||
|
prefix, converted);
|
||||||
|
if (len > sizeof(line) - 3) len = sizeof(line) - 3;
|
||||||
|
line[len] = '\r';
|
||||||
|
line[len + 1] = '\n';
|
||||||
|
len += 2;
|
||||||
|
|
||||||
|
sock_write(line, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int irc_connect(const char *host, const char *port)
|
||||||
|
{
|
||||||
|
struct addrinfo hints, *res, *p;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
if (getaddrinfo(host, port, &hints, &res) != 0)
|
||||||
|
die("getaddrinfo");
|
||||||
|
|
||||||
|
for (p = res; p; p = p->ai_next) {
|
||||||
|
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||||
|
if (fd < 0) continue;
|
||||||
|
if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break;
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
}
|
||||||
|
freeaddrinfo(res);
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
die("connect");
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find window index for a channel, or -1 */
|
||||||
|
static int chan_win_idx(const char *chan)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < MAX_CHAN_WINS; i++) {
|
||||||
|
if (strcasecmp(win_chans[i].name, chan) == 0)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get level for a channel (find existing or assign to current window) */
|
||||||
|
static int chan_to_level(const char *chan)
|
||||||
|
{
|
||||||
|
int idx = chan_win_idx(chan);
|
||||||
|
if (idx >= 0)
|
||||||
|
return WL_CHAN + idx;
|
||||||
|
|
||||||
|
/* Assign to current window if it's a channel window and empty */
|
||||||
|
if (current_level >= WL_CHAN) {
|
||||||
|
idx = current_level - WL_CHAN;
|
||||||
|
if (win_chans[idx].name[0] == '\0') {
|
||||||
|
snprintf(win_chans[idx].name,
|
||||||
|
sizeof(win_chans[idx].name), "%s", chan);
|
||||||
|
return current_level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find first empty slot */
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < MAX_CHAN_WINS; i++) {
|
||||||
|
if (win_chans[i].name[0] == '\0') {
|
||||||
|
snprintf(win_chans[i].name,
|
||||||
|
sizeof(win_chans[i].name), "%s", chan);
|
||||||
|
return WL_CHAN + i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return WL_CHAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update our prefix for a channel based on MODE changes */
|
||||||
|
static void update_my_prefix(const char *chan, const char *modestr,
|
||||||
|
const char *args)
|
||||||
|
{
|
||||||
|
int idx = chan_win_idx(chan);
|
||||||
|
if (idx < 0) return;
|
||||||
|
|
||||||
|
int adding = 1;
|
||||||
|
const char *m = modestr;
|
||||||
|
/* Simple parse: walk mode chars, consume args for o/v */
|
||||||
|
char arg_buf[512];
|
||||||
|
snprintf(arg_buf, sizeof(arg_buf), "%s", args ? args : "");
|
||||||
|
char *arg = arg_buf;
|
||||||
|
|
||||||
|
while (*m) {
|
||||||
|
if (*m == '+') { adding = 1; m++; continue; }
|
||||||
|
if (*m == '-') { adding = 0; m++; continue; }
|
||||||
|
|
||||||
|
/* Modes that take a parameter */
|
||||||
|
char *this_arg = NULL;
|
||||||
|
if (*m == 'o' || *m == 'v' || *m == 'b' ||
|
||||||
|
*m == 'k' || *m == 'l') {
|
||||||
|
this_arg = arg;
|
||||||
|
char *sp = strchr(arg, ' ');
|
||||||
|
if (sp) { *sp = '\0'; arg = sp + 1; }
|
||||||
|
else arg = arg + strlen(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*m == 'o' || *m == 'v') && this_arg &&
|
||||||
|
strcasecmp(this_arg, nick) == 0) {
|
||||||
|
if (*m == 'o')
|
||||||
|
win_chans[idx].my_prefix = adding ? '@' : '\0';
|
||||||
|
else if (*m == 'v' && win_chans[idx].my_prefix != '@')
|
||||||
|
win_chans[idx].my_prefix = adding ? '+' : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Track channel modes (non-user modes) */
|
||||||
|
if (*m != 'o' && *m != 'v' && *m != 'b') {
|
||||||
|
char *cm = win_chans[idx].modes;
|
||||||
|
size_t clen = strlen(cm);
|
||||||
|
if (adding) {
|
||||||
|
/* Add if not present */
|
||||||
|
if (!strchr(cm, *m) &&
|
||||||
|
clen + 1 < sizeof(win_chans[idx].modes)) {
|
||||||
|
if (clen == 0) {
|
||||||
|
cm[0] = '+';
|
||||||
|
cm[1] = *m;
|
||||||
|
cm[2] = '\0';
|
||||||
|
} else {
|
||||||
|
cm[clen] = *m;
|
||||||
|
cm[clen+1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Remove */
|
||||||
|
char *p = strchr(cm, *m);
|
||||||
|
if (p) memmove(p, p + 1, strlen(p));
|
||||||
|
/* Remove '+' if empty */
|
||||||
|
if (strcmp(cm, "+") == 0) cm[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_line(char *line)
|
||||||
|
{
|
||||||
|
char converted[BUF_SIZE];
|
||||||
|
|
||||||
|
to_iso8859_1((unsigned char *)line, strlen(line),
|
||||||
|
converted, sizeof(converted));
|
||||||
|
|
||||||
|
if (strncmp(converted, "PING ", 5) == 0) {
|
||||||
|
irc_send_raw("PONG %s", converted + 5);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *prefix = NULL;
|
||||||
|
char *cmd = converted;
|
||||||
|
|
||||||
|
if (cmd[0] == ':') {
|
||||||
|
prefix = cmd + 1;
|
||||||
|
cmd = strchr(cmd, ' ');
|
||||||
|
if (!cmd) return;
|
||||||
|
*cmd++ = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*cmd == ' ') cmd++;
|
||||||
|
|
||||||
|
char sender[64] = "";
|
||||||
|
if (prefix) {
|
||||||
|
char *bang = strchr(prefix, '!');
|
||||||
|
if (bang) {
|
||||||
|
size_t nlen = (size_t)(bang - prefix);
|
||||||
|
if (nlen >= sizeof(sender)) nlen = sizeof(sender) - 1;
|
||||||
|
memcpy(sender, prefix, nlen);
|
||||||
|
sender[nlen] = '\0';
|
||||||
|
} else {
|
||||||
|
snprintf(sender, sizeof(sender), "%.63s", prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *params = strchr(cmd, ' ');
|
||||||
|
if (params) *params++ = '\0';
|
||||||
|
|
||||||
|
if (strcmp(cmd, "PRIVMSG") == 0 && params) {
|
||||||
|
char *target = params;
|
||||||
|
char *text = strchr(params, ':');
|
||||||
|
if (text) {
|
||||||
|
char *sp = strchr(target, ' ');
|
||||||
|
if (sp) *sp = '\0';
|
||||||
|
text++;
|
||||||
|
if (text[0] == '\x01') {
|
||||||
|
if (strncmp(text, "\x01VERSION\x01", 9) == 0) {
|
||||||
|
struct utsname ut;
|
||||||
|
uname(&ut);
|
||||||
|
irc_send_raw("NOTICE %s :\x01VERSION "
|
||||||
|
"Holck's Mirk, OS: %s %s %s"
|
||||||
|
" :: This space available for rent\x01",
|
||||||
|
sender, ut.sysname,
|
||||||
|
ut.release, ut.machine);
|
||||||
|
}
|
||||||
|
wprintf(WL_STATUS, "* CTCP from %s: %s\n",
|
||||||
|
sender, text + 1);
|
||||||
|
} else if (strcasecmp(target, nick) == 0) {
|
||||||
|
wprintf(WL_MSG, "<%s> %s\n", sender, text);
|
||||||
|
} else {
|
||||||
|
int lvl = chan_to_level(target);
|
||||||
|
wprintf(lvl, "<%s> %s\n", sender, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "NOTICE") == 0 && params) {
|
||||||
|
char *text = strchr(params, ':');
|
||||||
|
if (text) text++;
|
||||||
|
else text = params;
|
||||||
|
wprintf(WL_STATUS, "-%s- %s\n", sender[0] ? sender : "*", text);
|
||||||
|
} else if (strcmp(cmd, "JOIN") == 0) {
|
||||||
|
char *chan = params;
|
||||||
|
if (chan && chan[0] == ':') chan++;
|
||||||
|
int lvl = chan ? chan_to_level(chan) : WL_STATUS;
|
||||||
|
wprintf(lvl, "* %s has joined %s\n", sender, chan ? chan : "");
|
||||||
|
draw_statusbar();
|
||||||
|
} else if (strcmp(cmd, "PART") == 0) {
|
||||||
|
char *chan = params;
|
||||||
|
char *reason = NULL;
|
||||||
|
if (chan) {
|
||||||
|
reason = strchr(chan, ':');
|
||||||
|
if (reason) {
|
||||||
|
char *sp = strchr(chan, ' ');
|
||||||
|
if (sp) *sp = '\0';
|
||||||
|
reason++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int lvl = chan ? chan_to_level(chan) : WL_STATUS;
|
||||||
|
wprintf(lvl, "* %s has left %s (%s)\n", sender,
|
||||||
|
chan ? chan : "", reason ? reason : "");
|
||||||
|
/* If we parted, clear the window */
|
||||||
|
if (strcasecmp(sender, nick) == 0 && chan) {
|
||||||
|
int idx = chan_win_idx(chan);
|
||||||
|
if (idx >= 0) {
|
||||||
|
win_chans[idx].name[0] = '\0';
|
||||||
|
win_chans[idx].modes[0] = '\0';
|
||||||
|
win_chans[idx].my_prefix = '\0';
|
||||||
|
draw_statusbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "QUIT") == 0) {
|
||||||
|
char *reason = params;
|
||||||
|
if (reason && reason[0] == ':') reason++;
|
||||||
|
wprintf(WL_STATUS, "* %s has quit (%s)\n",
|
||||||
|
sender, reason ? reason : "");
|
||||||
|
} else if (strcmp(cmd, "NICK") == 0) {
|
||||||
|
char *newnick = params;
|
||||||
|
if (newnick && newnick[0] == ':') newnick++;
|
||||||
|
wprintf(WL_STATUS, "* %s is now known as %s\n", sender,
|
||||||
|
newnick ? newnick : "");
|
||||||
|
if (strcasecmp(sender, nick) == 0 && newnick) {
|
||||||
|
snprintf(nick, sizeof(nick), "%s", newnick);
|
||||||
|
draw_statusbar();
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "MODE") == 0 && params) {
|
||||||
|
/* MODE #chan +modes [args] */
|
||||||
|
char *target = params;
|
||||||
|
char *modestr = strchr(params, ' ');
|
||||||
|
if (modestr) {
|
||||||
|
*modestr++ = '\0';
|
||||||
|
char *modeargs = strchr(modestr, ' ');
|
||||||
|
if (modeargs) *modeargs++ = '\0';
|
||||||
|
|
||||||
|
if (target[0] == '#' || target[0] == '&') {
|
||||||
|
update_my_prefix(target, modestr, modeargs);
|
||||||
|
int lvl = chan_to_level(target);
|
||||||
|
wprintf(lvl, "* %s sets mode %s %s\n",
|
||||||
|
sender[0] ? sender : "*", modestr,
|
||||||
|
modeargs ? modeargs : "");
|
||||||
|
draw_statusbar();
|
||||||
|
} else {
|
||||||
|
wprintf(WL_STATUS, "* %s sets mode %s\n",
|
||||||
|
sender[0] ? sender : "*", modestr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "324") == 0 && params) {
|
||||||
|
/* RPL_CHANNELMODEIS: <nick> <channel> <modes> */
|
||||||
|
char *p = params;
|
||||||
|
/* skip our nick */
|
||||||
|
char *sp = strchr(p, ' ');
|
||||||
|
if (sp) {
|
||||||
|
p = sp + 1;
|
||||||
|
char *chan = p;
|
||||||
|
sp = strchr(p, ' ');
|
||||||
|
if (sp) {
|
||||||
|
*sp = '\0';
|
||||||
|
char *modes = sp + 1;
|
||||||
|
/* Strip trailing params */
|
||||||
|
sp = strchr(modes, ' ');
|
||||||
|
if (sp) *sp = '\0';
|
||||||
|
int idx = chan_win_idx(chan);
|
||||||
|
if (idx >= 0) {
|
||||||
|
snprintf(win_chans[idx].modes,
|
||||||
|
sizeof(win_chans[idx].modes),
|
||||||
|
"%s", modes);
|
||||||
|
draw_statusbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (strcmp(cmd, "353") == 0 && params) {
|
||||||
|
/* RPL_NAMREPLY: <nick> = <channel> :names...
|
||||||
|
* Check our prefix in the names list */
|
||||||
|
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) {
|
||||||
|
int idx = chan_win_idx(chan);
|
||||||
|
if (idx >= 0) {
|
||||||
|
colon++;
|
||||||
|
/* Find our nick in the list */
|
||||||
|
char *names = colon;
|
||||||
|
char *tok = strtok(names, " ");
|
||||||
|
while (tok) {
|
||||||
|
const char *n = tok;
|
||||||
|
char pf = '\0';
|
||||||
|
if (*n == '@' || *n == '+') {
|
||||||
|
pf = *n;
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
if (strcasecmp(n, nick) == 0) {
|
||||||
|
win_chans[idx].my_prefix = pf;
|
||||||
|
draw_statusbar();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tok = strtok(NULL, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (params) {
|
||||||
|
char *text = strchr(params, ':');
|
||||||
|
if (text) text++;
|
||||||
|
else text = params;
|
||||||
|
wprintf(WL_STATUS, "[%s] %s\n", cmd, text);
|
||||||
|
} else {
|
||||||
|
wprintf(WL_STATUS, "[%s]\n", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_recv(void)
|
||||||
|
{
|
||||||
|
char *crlf;
|
||||||
|
|
||||||
|
while ((crlf = strstr(recv_buf, "\r\n")) != NULL) {
|
||||||
|
*crlf = '\0';
|
||||||
|
handle_line(recv_buf);
|
||||||
|
|
||||||
|
size_t line_len = (size_t)(crlf - recv_buf) + 2;
|
||||||
|
recv_len -= line_len;
|
||||||
|
memmove(recv_buf, crlf + 2, recv_len);
|
||||||
|
recv_buf[recv_len] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_input(char *line)
|
||||||
|
{
|
||||||
|
size_t len = strlen(line);
|
||||||
|
if (len > 0 && line[len-1] == '\n') line[--len] = '\0';
|
||||||
|
if (len == 0) return;
|
||||||
|
|
||||||
|
if (line[0] == '/') {
|
||||||
|
char *cmd = line + 1;
|
||||||
|
char *args = strchr(cmd, ' ');
|
||||||
|
if (args) *args++ = '\0';
|
||||||
|
|
||||||
|
if (strcasecmp(cmd, "join") == 0 && args) {
|
||||||
|
/* Assign channel to current window if it's a chan window */
|
||||||
|
chan_to_level(args);
|
||||||
|
irc_send_raw("JOIN %s", args);
|
||||||
|
} else if (strcasecmp(cmd, "part") == 0) {
|
||||||
|
const char *chan = args ? args : current_channel();
|
||||||
|
if (chan[0])
|
||||||
|
irc_send_raw("PART %s", chan);
|
||||||
|
} else if (strcasecmp(cmd, "msg") == 0 && args) {
|
||||||
|
char *target = args;
|
||||||
|
char *text = strchr(args, ' ');
|
||||||
|
if (text) {
|
||||||
|
*text++ = '\0';
|
||||||
|
char pfx[IRC_MAX];
|
||||||
|
snprintf(pfx, sizeof(pfx),
|
||||||
|
"PRIVMSG %s :", target);
|
||||||
|
irc_send_converted(pfx,
|
||||||
|
(unsigned char *)text,
|
||||||
|
strlen(text));
|
||||||
|
wprintf(WL_MSG, "-> %s: %s\n", target, text);
|
||||||
|
}
|
||||||
|
} else if (strcasecmp(cmd, "nick") == 0 && args) {
|
||||||
|
snprintf(nick, sizeof(nick), "%s", args);
|
||||||
|
irc_send_raw("NICK %s", args);
|
||||||
|
draw_statusbar();
|
||||||
|
} else if (strcasecmp(cmd, "mode") == 0 && args) {
|
||||||
|
irc_send_raw("MODE %s", args);
|
||||||
|
} else if (strcasecmp(cmd, "whois") == 0 && args) {
|
||||||
|
if (strchr(args, ' '))
|
||||||
|
irc_send_raw("WHOIS %s", args);
|
||||||
|
else
|
||||||
|
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, "quit") == 0) {
|
||||||
|
irc_send_raw("QUIT :%s", args ? args : "See you later");
|
||||||
|
close(sock_fd);
|
||||||
|
exit(0);
|
||||||
|
} else if (strcasecmp(cmd, "raw") == 0 && args) {
|
||||||
|
irc_send_raw("%s", args);
|
||||||
|
} else {
|
||||||
|
wprintf(current_level, "Unknown command: /%s\n", cmd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const char *chan = current_channel();
|
||||||
|
if (chan[0] == '\0') {
|
||||||
|
wprintf(current_level,
|
||||||
|
"* Not in a channel on this window.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char pfx[IRC_MAX];
|
||||||
|
snprintf(pfx, sizeof(pfx), "PRIVMSG %s :", chan);
|
||||||
|
irc_send_converted(pfx, (unsigned char *)line, len);
|
||||||
|
wprintf(current_level, "<%s> %s\n", nick, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Count display columns for a UTF-8 byte string */
|
||||||
|
static size_t display_cols(const char *buf, size_t bytes)
|
||||||
|
{
|
||||||
|
size_t cols = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < bytes) {
|
||||||
|
unsigned char c = (unsigned char)buf[i];
|
||||||
|
if (c < 0x80) { i++; cols++; }
|
||||||
|
else if ((c & 0xE0) == 0xC0) { i += 2; cols++; }
|
||||||
|
else if ((c & 0xF0) == 0xE0) { i += 3; cols++; }
|
||||||
|
else if ((c & 0xF8) == 0xF0) { i += 4; cols++; }
|
||||||
|
else { i++; cols++; }
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return number of bytes in the UTF-8 char ending before pos */
|
||||||
|
static size_t utf8_back(const char *buf, size_t pos)
|
||||||
|
{
|
||||||
|
size_t n = 1;
|
||||||
|
while (n < pos && n < 4 &&
|
||||||
|
((unsigned char)buf[pos - n] & 0xC0) == 0x80)
|
||||||
|
n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
fwrite(input_line, 1, input_len, stdout);
|
||||||
|
printf("\033[%d;%dH", term_rows, (int)(cpos_cols + 3));
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usage(const char *prog)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s <nick> <server> [port]\n", prog);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
const char *host, *port = "6667";
|
||||||
|
|
||||||
|
if (argc < 3) usage(argv[0]);
|
||||||
|
snprintf(nick, sizeof(nick), "%s", argv[1]);
|
||||||
|
host = argv[2];
|
||||||
|
if (argc >= 4) port = argv[3];
|
||||||
|
|
||||||
|
memset(win_chans, 0, sizeof(win_chans));
|
||||||
|
|
||||||
|
/* Set terminal to raw mode */
|
||||||
|
struct termios orig_term, raw_term;
|
||||||
|
tcgetattr(STDIN_FILENO, &orig_term);
|
||||||
|
raw_term = orig_term;
|
||||||
|
raw_term.c_lflag &= ~(ICANON | ECHO);
|
||||||
|
raw_term.c_cc[VMIN] = 1;
|
||||||
|
raw_term.c_cc[VTIME] = 0;
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &raw_term);
|
||||||
|
|
||||||
|
signal(SIGINT, sigint_handler);
|
||||||
|
signal(SIGWINCH, sigwinch_handler);
|
||||||
|
|
||||||
|
/* Set up scrolling region (leave last 2 rows for status + input) */
|
||||||
|
get_term_size();
|
||||||
|
printf("\033[2J\033[H"); /* clear screen */
|
||||||
|
printf("\033[1;%dr", term_rows - 2);
|
||||||
|
printf("\033[1;1H"); /* cursor to top of scroll region */
|
||||||
|
draw_statusbar();
|
||||||
|
|
||||||
|
wprintf(WL_STATUS, "Connecting to %s:%s as %s...\n", host, port, nick);
|
||||||
|
|
||||||
|
sock_fd = irc_connect(host, port);
|
||||||
|
|
||||||
|
wprintf(WL_STATUS, "Connected.\n");
|
||||||
|
|
||||||
|
irc_send_raw("NICK %s", nick);
|
||||||
|
const char *user = getenv("USER");
|
||||||
|
if (!user) user = nick;
|
||||||
|
const char *realname = nick;
|
||||||
|
struct passwd *pw = getpwuid(getuid());
|
||||||
|
if (pw && pw->pw_gecos && pw->pw_gecos[0]) {
|
||||||
|
/* GECOS may have commas; use only first field */
|
||||||
|
static char gecos[128];
|
||||||
|
snprintf(gecos, sizeof(gecos), "%s", pw->pw_gecos);
|
||||||
|
char *comma = strchr(gecos, ',');
|
||||||
|
if (comma) *comma = '\0';
|
||||||
|
realname = gecos;
|
||||||
|
}
|
||||||
|
irc_send_raw("USER %s 0 * :%s", user, realname);
|
||||||
|
|
||||||
|
draw_statusbar();
|
||||||
|
|
||||||
|
fd_set fds;
|
||||||
|
char input_line[BUF_SIZE];
|
||||||
|
size_t input_pos = 0; /* cursor position */
|
||||||
|
size_t input_len = 0; /* total length */
|
||||||
|
char yank_buf[BUF_SIZE] = "";
|
||||||
|
size_t yank_len = 0;
|
||||||
|
int esc_pending = 0;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
FD_ZERO(&fds);
|
||||||
|
FD_SET(sock_fd, &fds);
|
||||||
|
FD_SET(STDIN_FILENO, &fds);
|
||||||
|
|
||||||
|
int maxfd = sock_fd > STDIN_FILENO ? sock_fd : STDIN_FILENO;
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = esc_pending ? 50000 : 500000;
|
||||||
|
|
||||||
|
int ret = select(maxfd + 1, &fds, NULL, NULL, &tv);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
die("select");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0 && esc_pending) {
|
||||||
|
esc_pending = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (got_sigint) {
|
||||||
|
got_sigint = 0;
|
||||||
|
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')) {
|
||||||
|
irc_send_raw("QUIT :Leaving");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (got_sigwinch) {
|
||||||
|
got_sigwinch = 0;
|
||||||
|
get_term_size();
|
||||||
|
printf("\033[1;%dr", term_rows - 2);
|
||||||
|
redraw_window();
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(sock_fd, &fds)) {
|
||||||
|
ssize_t n = read(sock_fd, recv_buf + recv_len,
|
||||||
|
sizeof(recv_buf) - recv_len - 1);
|
||||||
|
if (n <= 0) {
|
||||||
|
printf("* Disconnected from server.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
recv_len += (size_t)n;
|
||||||
|
recv_buf[recv_len] = '\0';
|
||||||
|
process_recv();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(STDIN_FILENO, &fds)) {
|
||||||
|
unsigned char ch;
|
||||||
|
ssize_t n = read(STDIN_FILENO, &ch, 1);
|
||||||
|
if (n <= 0) {
|
||||||
|
irc_send_raw("QUIT :EOF");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esc_pending) {
|
||||||
|
esc_pending = 0;
|
||||||
|
if (ch >= '1' && ch <= '9') {
|
||||||
|
int lvl = ch - '1';
|
||||||
|
if (lvl < WL_MAX) {
|
||||||
|
current_level = lvl;
|
||||||
|
redraw_window();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == 0x1B) {
|
||||||
|
esc_pending = 1;
|
||||||
|
} else if (ch == '\r' || ch == '\n') {
|
||||||
|
printf("\033[%d;1H\033[K", term_rows);
|
||||||
|
input_line[input_len] = '\0';
|
||||||
|
if (input_len > 0)
|
||||||
|
handle_input(input_line);
|
||||||
|
input_pos = 0;
|
||||||
|
input_len = 0;
|
||||||
|
printf("\033[%d;1H> ", term_rows);
|
||||||
|
fflush(stdout);
|
||||||
|
} else if (ch == 0x01) {
|
||||||
|
/* Ctrl-A: beginning of line */
|
||||||
|
input_pos = 0;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
} else if (ch == 0x05) {
|
||||||
|
/* Ctrl-E: end of line */
|
||||||
|
input_pos = input_len;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
} else if (ch == 0x15) {
|
||||||
|
/* Ctrl-U: kill to beginning */
|
||||||
|
if (input_pos > 0) {
|
||||||
|
yank_len = input_pos;
|
||||||
|
memcpy(yank_buf, input_line, yank_len);
|
||||||
|
memmove(input_line, input_line + input_pos,
|
||||||
|
input_len - input_pos);
|
||||||
|
input_len -= input_pos;
|
||||||
|
input_pos = 0;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
}
|
||||||
|
} else if (ch == 0x19) {
|
||||||
|
/* Ctrl-Y: yank (paste) */
|
||||||
|
if (yank_len > 0 && input_len + yank_len < sizeof(input_line) - 1) {
|
||||||
|
memmove(input_line + input_pos + yank_len,
|
||||||
|
input_line + input_pos,
|
||||||
|
input_len - input_pos);
|
||||||
|
memcpy(input_line + input_pos, yank_buf, yank_len);
|
||||||
|
input_len += yank_len;
|
||||||
|
input_pos += yank_len;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
}
|
||||||
|
} else if (ch == 0x0B) {
|
||||||
|
/* Ctrl-K: kill to end */
|
||||||
|
yank_len = input_len - input_pos;
|
||||||
|
memcpy(yank_buf, input_line + input_pos, yank_len);
|
||||||
|
input_len = input_pos;
|
||||||
|
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);
|
||||||
|
memmove(input_line + input_pos - clen,
|
||||||
|
input_line + input_pos,
|
||||||
|
input_len - input_pos);
|
||||||
|
input_pos -= clen;
|
||||||
|
input_len -= clen;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
}
|
||||||
|
} else if (ch == 0x04) {
|
||||||
|
irc_send_raw("QUIT :EOF");
|
||||||
|
break;
|
||||||
|
} else if (ch >= 32 && input_len < sizeof(input_line) - 1) {
|
||||||
|
/* For UTF-8 lead bytes, figure out how many
|
||||||
|
* bytes to expect and read them all */
|
||||||
|
size_t seq_len = 1;
|
||||||
|
if ((ch & 0xE0) == 0xC0) seq_len = 2;
|
||||||
|
else if ((ch & 0xF0) == 0xE0) seq_len = 3;
|
||||||
|
else if ((ch & 0xF8) == 0xF0) seq_len = 4;
|
||||||
|
|
||||||
|
unsigned char seq[4];
|
||||||
|
seq[0] = ch;
|
||||||
|
for (size_t si = 1; si < seq_len; si++) {
|
||||||
|
unsigned char cb;
|
||||||
|
ssize_t r = read(STDIN_FILENO, &cb, 1);
|
||||||
|
if (r <= 0) break;
|
||||||
|
seq[si] = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_len + seq_len < sizeof(input_line) - 1) {
|
||||||
|
memmove(input_line + input_pos + seq_len,
|
||||||
|
input_line + input_pos,
|
||||||
|
input_len - input_pos);
|
||||||
|
memcpy(input_line + input_pos, seq, seq_len);
|
||||||
|
input_pos += seq_len;
|
||||||
|
input_len += seq_len;
|
||||||
|
redraw_input(input_line, input_len, input_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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