diff options
| -rw-r--r-- | Makefile | 34 | ||||
| -rw-r--r-- | README | 18 | ||||
| -rw-r--r-- | cic.1 (renamed from sic.1) | 12 | ||||
| -rw-r--r-- | cic.c | 380 | ||||
| -rw-r--r-- | config.def.h | 2 | ||||
| -rw-r--r-- | config.mk | 6 | ||||
| -rw-r--r-- | sic.c | 216 |
7 files changed, 416 insertions, 252 deletions
@@ -1,14 +1,14 @@ -# sic - simple irc client +# cic - curses irc client include config.mk -SRC = sic.c +SRC = cic.c OBJ = ${SRC:.c=.o} -all: options sic +all: options cic options: - @echo sic build options: + @echo cic build options: @echo "CFLAGS = ${CFLAGS}" @echo "LDFLAGS = ${LDFLAGS}" @echo "CC = ${CC}" @@ -23,36 +23,36 @@ config.h: @echo creating $@ from config.def.h @cp config.def.h $@ -sic: ${OBJ} +cic: ${OBJ} @echo CC -o $@ @${CC} -o $@ ${OBJ} ${LDFLAGS} clean: @echo cleaning - @rm -f sic ${OBJ} sic-${VERSION}.tar.gz + @rm -f cic ${OBJ} cic-${VERSION}.tar.gz dist: clean @echo creating dist tarball - @mkdir -p sic-${VERSION} - @cp -R LICENSE Makefile README arg.h config.def.h config.mk sic.1 sic.c util.c sic-${VERSION} - @tar -cf sic-${VERSION}.tar sic-${VERSION} - @gzip sic-${VERSION}.tar - @rm -rf sic-${VERSION} + @mkdir -p cic-${VERSION} + @cp -R LICENSE Makefile README arg.h config.def.h config.mk cic.1 cic.c util.c cic-${VERSION} + @tar -cf cic-${VERSION}.tar cic-${VERSION} + @gzip cic-${VERSION}.tar + @rm -rf cic-${VERSION} install: all @echo installing executable file to ${DESTDIR}${PREFIX}/bin @mkdir -p ${DESTDIR}${PREFIX}/bin - @cp -f sic ${DESTDIR}${PREFIX}/bin - @chmod 755 ${DESTDIR}${PREFIX}/bin/sic + @cp -f cic ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/cic @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 - @sed "s/VERSION/${VERSION}/g" < sic.1 > ${DESTDIR}${MANPREFIX}/man1/sic.1 - @chmod 644 ${DESTDIR}${MANPREFIX}/man1/sic.1 + @sed "s/VERSION/${VERSION}/g" < cic.1 > ${DESTDIR}${MANPREFIX}/man1/cic.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/cic.1 uninstall: @echo removing executable file from ${DESTDIR}${PREFIX}/bin - @rm -f ${DESTDIR}${PREFIX}/bin/sic + @rm -f ${DESTDIR}${PREFIX}/bin/cic @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 - @rm -f ${DESTDIR}${MANPREFIX}/man1/sic.1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/cic.1 .PHONY: all options clean dist install uninstall @@ -1,22 +1,22 @@ -sic - simple irc client +cic - curses irc client ======================= -sic is an extremly fast, small and simple irc client. It reads commands from -standard input and prints all server output to standard output. It multiplexes -also all channel traffic into one output, that you don't have to switch -different channel buffers, that's actually a feature. +cic is an extremly fast, small and simple irc client. It reads commands from +input line and prints all server output to a single output window. It also +multiplexes also all channel traffic into one output, so you don't have to +switch different channel buffers. That's actually a feature. Installation ------------ -Edit config.mk to match your local setup. sic is installed into +Edit config.mk to match your local setup. cic is installed into /usr/local by default. -Afterwards enter the following command to build and install sic +Afterwards enter the following command to build and install cic (if necessary as root): $ make clean install -Running sic +Running cic ----------- -Simply invoke the 'sic' command with the required arguments. +Simply invoke the 'cic' command with the required arguments. @@ -1,8 +1,8 @@ -.TH SIC 1 sic-VERSION +.TH CIC 1 cic-VERSION .SH NAME -sic \- simple irc client +cic \- curses irc client .SH SYNOPSIS -.B sic +.B cic .RB [ \-h .IR host ] .RB [ \-p @@ -13,10 +13,10 @@ sic \- simple irc client .IR pass ] .RB [ \-v ] .SH DESCRIPTION -.B sic +.B cic is an extremely fast, small and simple irc client. It reads commands from -standard input and prints all server output to standard output. It also -multiplexes all channel traffic into one output. That way you don't have to +the input line and prints all server output to a single output window. It +also multiplexes all channel traffic into one output, so you don't have to switch different channel buffers. So that's actually a feature. .SH OPTIONS .TP @@ -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; +} diff --git a/config.def.h b/config.def.h index 6d720e9..dc88ad0 100644 --- a/config.def.h +++ b/config.def.h @@ -11,4 +11,4 @@ #define COMMAND_PREFIX_CHARACTER ':' /* Parting message used when none is specified with ":l ..." command. */ -#define DEFAULT_PARTING_MESSAGE "sic - 250 LOC are too much!" +#define DEFAULT_PARTING_MESSAGE "cic - 500 LOC are too much!" @@ -1,5 +1,5 @@ -# sic version -VERSION = 1.3 +# cic version +VERSION = 0.0 # Customize below to fit your system @@ -9,7 +9,7 @@ MANPREFIX = ${PREFIX}/share/man # includes and libs INCS = -I. -I/usr/include -LIBS = -L/usr/lib -lc +LIBS = -L/usr/lib -lc -lcurses # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -D_GNU_SOURCE @@ -1,216 +0,0 @@ - /* 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 "arg.h" -#include "config.h" - -char *argv0; -static char *host = DEFAULT_HOST; -static char *port = DEFAULT_PORT; -static char *password; -static char nick[32]; -static char bufin[4096]; -static char bufout[4096]; -static char channel[256]; -static time_t trespond; -static FILE *srv; - -#include "util.c" - -static void -pout(char *channel, char *fmt, ...) { - static char timestr[80]; - time_t t; - va_list ap; - - va_start(ap, fmt); - vsnprintf(bufout, sizeof bufout, fmt, ap); - va_end(ap); - t = time(NULL); - strftime(timestr, sizeof timestr, TIMESTAMP_FORMAT, localtime(&t)); - fprintf(stdout, "%-12s: %s %s\n", channel, timestr, bufout); -} - -static void -sout(char *fmt, ...) { - va_list ap; - - va_start(ap, fmt); - vsnprintf(bufout, sizeof bufout, fmt, ap); - va_end(ap); - fprintf(srv, "%s\r\n", bufout); -} - -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); - } -} - - -static void -usage(void) { - eprint("usage: sic [-h host] [-p port] [-n nick] [-k keyword] [-v]\n", argv0); -} - -int -main(int argc, char *argv[]) { - struct timeval tv; - const char *user = getenv("USER"); - int n; - fd_set rd; - - 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("sic-"VERSION", © 2005-2014 Kris Maglione, Anselm R. Garbe, Nico Golde\n"); - break; - default: - usage(); - } ARGEND; - - /* init */ - srv = fdopen(dial(host, port), "r+"); - if (!srv) - 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(stdout, NULL); - setbuf(srv, NULL); - setbuf(stdin, NULL); - for(;;) { /* main loop */ - 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; - eprint("sic: error on select():"); - } - else if(n == 0) { - if(time(NULL) - trespond >= 300) - eprint("sic shutting down: parse timeout\n"); - sout("PING %s", host); - continue; - } - if(FD_ISSET(fileno(srv), &rd)) { - if(fgets(bufin, sizeof bufin, srv) == NULL) - eprint("sic: remote host closed connection\n"); - parsesrv(bufin); - trespond = time(NULL); - } - if(FD_ISSET(0, &rd)) { - if(fgets(bufin, sizeof bufin, stdin) == NULL) - eprint("sic: broken pipe\n"); - parsein(bufin); - } - } - return 0; -} |
