summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile34
-rw-r--r--README18
-rw-r--r--cic.1 (renamed from sic.1)12
-rw-r--r--cic.c380
-rw-r--r--config.def.h2
-rw-r--r--config.mk6
-rw-r--r--sic.c216
7 files changed, 416 insertions, 252 deletions
diff --git a/Makefile b/Makefile
index 7b85db6..a0abdb8 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README b/README
index ee5ecac..b4bf59c 100644
--- a/README
+++ b/README
@@ -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.
diff --git a/sic.1 b/cic.1
index aba8db8..34f7b11 100644
--- a/sic.1
+++ b/cic.1
@@ -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
diff --git a/cic.c b/cic.c
new file mode 100644
index 0000000..c12d6df
--- /dev/null
+++ b/cic.c
@@ -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!"
diff --git a/config.mk b/config.mk
index 87395f4..f8a86ce 100644
--- a/config.mk
+++ b/config.mk
@@ -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
diff --git a/sic.c b/sic.c
deleted file mode 100644
index ce6d216..0000000
--- a/sic.c
+++ /dev/null
@@ -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;
-}