/* See LICENSE file for license details. */ #include #include #include #include #include #include #include #include #include #include #include #include #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 *barwin; 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-3) { wscrl(viewwin, 1); curline -= 1; } 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)); wprintw(viewwin, "%-12s: %s %s\n", channel, timestr, bufout); //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-2); } void doresize(void) { int ch; gettermsize(); resizeterm(rows, cols); if ((ch = getch()) != KEY_RESIZE) ungetch(ch); wresize(viewwin, rows-2, cols); wmove(viewwin, 0, 0); wresize(barwin, 1, cols); wmove(barwin, rows-2, 0); whline(barwin, ACS_HLINE, cols); wresize(inputwin, 1, cols); wmove(inputwin, rows-1, 0); wrefresh(viewwin); wrefresh(barwin); 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 - 2, cols, 0, 0); idlok(viewwin, TRUE); scrollok(viewwin, TRUE); barwin = newwin(1, cols, rows - 2, 0); whline(barwin, ACS_HLINE, cols); inputwin = newwin(1, cols, rows - 1, 0); wrefresh(viewwin); wrefresh(barwin); 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); } } endwin(); return 0; }