diff options
Diffstat (limited to 'cic.c')
| -rw-r--r-- | cic.c | 380 |
1 files changed, 380 insertions, 0 deletions
@@ -0,0 +1,380 @@ + /* See LICENSE file for license details. */ +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <curses.h> + +#include "arg.h" +#include "config.h" + +#define CONTROL(c) ((c) ^ 0x40) + +char *argv0; +static char *host = DEFAULT_HOST; +static char *port = DEFAULT_PORT; +static char *password; +static char nick[32]; +static char bufsrv[4096]; +static char bufin[4096]; +static char bufout[4096]; +static char channel[256]; +static time_t trespond; +static FILE *srv; +static WINDOW *viewwin; +static WINDOW *inputwin; +static int rows; +static int cols; +static int running; +static int got_sigwinch; +static int curline; + +#include "util.c" + +static void +pout(char *channel, char *fmt, ...) { + static char timestr[80]; + time_t t; + va_list ap; + + if (curline >= rows-2) { + wscrl(viewwin, 4); + curline -= 3; + } + else + curline++; + + va_start(ap, fmt); + vsnprintf(bufout, sizeof bufout, fmt, ap); + va_end(ap); + t = time(NULL); + strftime(timestr, sizeof timestr, TIMESTAMP_FORMAT, localtime(&t)); + mvwprintw(viewwin, curline, 0, "%-12s: %s %s", channel, timestr, bufout); + wrefresh(viewwin); + wrefresh(inputwin); +} + +static void +sout(char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vsnprintf(bufsrv, sizeof bufsrv, fmt, ap); + va_end(ap); + fprintf(srv, "%s\r\n", bufsrv); +} + +static void +privmsg(char *channel, char *msg) { + if(channel[0] == '\0') { + pout("", "No channel to send to"); + return; + } + pout(channel, "<%s> %s", nick, msg); + sout("PRIVMSG %s :%s", channel, msg); +} + +static void +parsein(char *s) { + char c, *p; + + if(s[0] == '\0') + return; + skip(s, '\n'); + if(s[0] != COMMAND_PREFIX_CHARACTER) { + privmsg(channel, s); + return; + } + c = *++s; + if(c != '\0' && isspace(s[1])) { + p = s + 2; + switch(c) { + case 'j': + sout("JOIN %s", p); + if(channel[0] == '\0') + strlcpy(channel, p, sizeof channel); + return; + case 'l': + s = eat(p, isspace, 1); + p = eat(s, isspace, 0); + if(!*s) + s = channel; + if(*p) + *p++ = '\0'; + if(!*p) + p = DEFAULT_PARTING_MESSAGE; + sout("PART %s :%s", s, p); + return; + case 'm': + s = eat(p, isspace, 1); + p = eat(s, isspace, 0); + if(*p) + *p++ = '\0'; + privmsg(s, p); + return; + case 's': + strlcpy(channel, p, sizeof channel); + return; + } + } + sout("%s", s); +} + +static void +parsesrv(char *cmd) { + char *usr, *par, *txt; + + usr = host; + if(!cmd || !*cmd) + return; + if(cmd[0] == ':') { + usr = cmd + 1; + cmd = skip(usr, ' '); + if(cmd[0] == '\0') + return; + skip(usr, '!'); + } + skip(cmd, '\r'); + par = skip(cmd, ' '); + txt = skip(par, ':'); + trim(par); + if(!strcmp("PONG", cmd)) + return; + if(!strcmp("PRIVMSG", cmd)) + pout(par, "<%s> %s", usr, txt); + else if(!strcmp("PING", cmd)) + sout("PONG %s", txt); + else { + pout(usr, ">< %s (%s): %s", cmd, par, txt); + if(!strcmp("NICK", cmd) && !strcmp(usr, nick)) + strlcpy(nick, txt, sizeof nick); + } +} + +int getinput(char *buf, size_t sz) +{ + int ch; + size_t ret = 0; + static size_t pos = 0; + + wmove(inputwin, 0, pos); + wrefresh(inputwin); + while ((ch = getch()) != ERR) { + switch (ch) { + case KEY_RESIZE: + got_sigwinch = 1; + goto done; + case KEY_ENTER: + case CONTROL('M'): + ret = pos; + pos = 0; + wmove(inputwin, 0, 0); + wclrtoeol(inputwin); + wrefresh(inputwin); + goto done; + case KEY_BACKSPACE: + if (pos) { + pos--; + wmove(inputwin, 0, pos); + wclrtoeol(inputwin); + wrefresh(inputwin); + } + break; + default: + if (pos == sizeof bufin) { + pout("", "input line too long"); + goto done; + } + mvwaddch(inputwin, 0, pos, ch); + bufin[pos++] = ch; + bufin[pos] = '\0'; + wrefresh(inputwin); + break; + } + } + +done: + wrefresh(inputwin); + + return ret; +} + +void gettermsize(void) +{ + struct winsize w; + + while (ioctl(STDIN_FILENO, TIOCGWINSZ, &w) == -1) + if (errno != EINTR) { + endwin(); + eprint("cic: failed to get term size"); + } + rows = w.ws_row; + cols = w.ws_col; + wsetscrreg(viewwin, 0, rows-1); +} + +void doresize(void) +{ + int ch; + + gettermsize(); + + resizeterm(rows, cols); + + if ((ch = getch()) != KEY_RESIZE) + ungetch(ch); + + wresize(viewwin, rows-1, cols); + wmove(viewwin, 0, 0); + wresize(inputwin, 1, cols); + wmove(inputwin, rows-1, 0); + + wrefresh(viewwin); + wrefresh(inputwin); +} + +void sigint(int unused) +{ + running = 0; +} + +void sigwinch(int unused) +{ + got_sigwinch = 1; +} + + +static void +usage(void) { + eprint("usage: cic [-h host] [-p port] [-n nick] [-k keyword] [-v]\n", argv0); +} + +int +main(int argc, char *argv[]) { + struct sigaction sa; + struct timeval tv; + const char *user = getenv("USER"); + int n; + fd_set rd; + + if (!isatty(STDIN_FILENO)) + eprint("cic: stdin is not a terminal"); + if (!isatty(STDOUT_FILENO)) + eprint("cic: stdout is not a terminal"); + + strlcpy(nick, user ? user : "unknown", sizeof nick); + ARGBEGIN { + case 'h': + host = EARGF(usage()); + break; + case 'p': + port = EARGF(usage()); + break; + case 'n': + strlcpy(nick, EARGF(usage()), sizeof nick); + break; + case 'k': + password = EARGF(usage()); + break; + case 'v': + eprint("cic-"VERSION", © 2005-2014 Kris Maglione, Anselm R. Garbe, Nico Golde, John Vogel\n"); + break; + default: + usage(); + } ARGEND; + + /* signals init */ + sa.sa_handler = sigint; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sa.sa_handler = sigwinch; + sigaction(SIGWINCH, &sa, NULL); + + srv = fdopen(dial(host, port), "r+"); + if (!srv) { + endwin(); + eprint("fdopen:"); + } + /* login */ + if(password) + sout("PASS %s", password); + sout("NICK %s", nick); + sout("USER %s localhost %s :%s", nick, host, nick); + fflush(srv); + setbuf(srv, NULL); + + /* curses init */ + initscr(); + cbreak(); + noecho(); + nonl(); + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(1); + timeout(0); + refresh(); + + gettermsize(); + + viewwin = newwin(rows - 1, cols, 0, 0); + idlok(viewwin, TRUE); + scrollok(viewwin, TRUE); + inputwin = newwin(1, cols, rows - 1, 0); + wrefresh(viewwin); + wrefresh(inputwin); + + running = 1; + got_sigwinch = 0; + while (running) { /* main loop */ + if (got_sigwinch) { + doresize(); + got_sigwinch = 0; + } + FD_ZERO(&rd); + FD_SET(0, &rd); + FD_SET(fileno(srv), &rd); + tv.tv_sec = 120; + tv.tv_usec = 0; + n = select(fileno(srv) + 1, &rd, 0, 0, &tv); + if(n < 0) { + if(errno == EINTR) + continue; + endwin(); + eprint("cic: error on select():"); + } + else if(n == 0) { + if(time(NULL) - trespond >= 300) { + endwin(); + eprint("cic shutting down: parse timeout\n"); + } + sout("PING %s", host); + continue; + } + if(FD_ISSET(fileno(srv), &rd)) { + if(fgets(bufsrv, sizeof bufin, srv) == NULL) { + endwin(); + eprint("cic: remote host closed connection\n"); + } + parsesrv(bufsrv); + trespond = time(NULL); + } + if(FD_ISSET(0, &rd)) { + if (getinput(bufin, sizeof bufin) > 0) + parsein(bufin); + } + } + delwin(viewwin); + delwin(inputwin); + endwin(); + return 0; +} |
