commit e6325e2c57e58dfb353132a807814504e20c6f58
Author: Claudio Alessi <smoppy@gmail.com>
Date: Sat, 8 Jul 2017 00:00:43 +0200
hello circo
Diffstat:
A | LICENSE | | | 21 | +++++++++++++++++++++ |
A | Makefile | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | README.md | | | 19 | +++++++++++++++++++ |
A | arg.h | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
A | circo.c | | | 737 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | config.def.h | | | 18 | ++++++++++++++++++ |
A | config.mk | | | 16 | ++++++++++++++++ |
7 files changed, 913 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2017 Claudio Alessi <smoppy at gmail dot com>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,61 @@
+# circo
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+APPNAME=circo
+SRC = ${APPNAME}.c
+OBJ = ${SRC:.c=.o}
+
+all: options ${APPNAME}
+
+options:
+ @echo ${APPNAME} build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ @echo CC $<
+ @${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.h config.mk
+
+config.h:
+ @echo creating $@ from config.def.h
+ @cp config.def.h $@
+
+${APPNAME}: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ @echo cleaning
+ @rm -f ${APPNAME} ${OBJ} ${APPNAME}-${VERSION}.tar.gz
+
+dist: clean
+ @echo creating dist tarball
+ @mkdir -p ${APPNAME}-${VERSION}
+ @cp -R LICENSE Makefile README config.mk \
+ ${APPNAME}.1 ${SRC} ${APPNAME}-${VERSION}
+ @tar -cf ${APPNAME}-${VERSION}.tar ${APPNAME}-${VERSION}
+ @gzip ${APPNAME}-${VERSION}.tar
+ @rm -rf ${APPNAME}-${VERSION}
+
+install: all
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
+ @mkdir -p ${DESTDIR}${PREFIX}/bin
+ @cp -f ${APPNAME} ${DESTDIR}${PREFIX}/bin
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/${APPNAME}
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ @sed "s/VERSION/${VERSION}/g" < ${APPNAME}.1 > ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1
+ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1
+
+uninstall:
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f ${DESTDIR}${PREFIX}/bin/${APPNAME}
+ @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
+ @rm -f ${DESTDIR}${MANPREFIX}/man1/${APPNAME}.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/README.md b/README.md
@@ -0,0 +1,19 @@
+Hello
+=====
+circo is a small IRC client for the terminal. It provides a list of views to
+differentiate between channels, users, and other messages. Each buffer has its
+own input text while other relevant informations such as server and nickname
+are always visible the top.
+
+Status
+======
+Input editing is yet incomplete and parsing of server messages need some
+cleanup. The code is working, I'm using it for a while and didn't notice any
+issue.
+
+Despite it's minimal design, circo is definitely not a suckless piece of code.
+It don't even follows in all respect the UNIX philosophy but it provides a very
+power bridge to the IRC servers with a such small code base. In this regard
+circo is unique. I'm also having a lot of fun in writing circo.
+
+If you also wanna have fun, please consider submitting a pull request.
diff --git a/arg.h b/arg.h
@@ -0,0 +1,41 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef __ARG_H__
+#define __ARG_H__
+
+extern char *argv0;
+
+#define USED(x) ((void)(x))
+
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char _argc;\
+ char **_argv;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (argv[0]++, _argv = argv; argv[0][0];\
+ argv[0]++) {\
+ if (_argv != argv)\
+ break;\
+ _argc = argv[0][0];\
+ switch (_argc)
+
+#define ARGEND }\
+ USED(_argc);\
+ }\
+ USED(argv);\
+ USED(argc);
+
+#define EARGF(x) ((argv[1] == NULL)? ((x), abort(), (char *)0) :\
+ (argc--, argv++, argv[0]))
+
+#endif
+
diff --git a/circo.c b/circo.c
@@ -0,0 +1,737 @@
+/* claudio's IRC oasis */
+
+#include <ctype.h>
+#include <errno.h>
+#include <locale.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "arg.h"
+char *argv0;
+
+/* macros */
+#define LENGTH(X) (sizeof X / sizeof X[0])
+
+/* VT100 escape sequences */
+#define CLEAR "\33[2J"
+#define CLEARLN "\33[2K"
+#define CLEARRIGHT "\33[0K"
+#define CURPOS "\33[%d;%dH"
+#define CURSON "\33[?25h"
+#define CURSOFF "\33[?25l"
+
+/* enums */
+enum { KeyUp = -50, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw };
+
+typedef union {
+ int i;
+ unsigned int ui;
+ float f;
+ const void *v;
+} Arg;
+
+typedef struct {
+ char text[512];
+ int len;
+} Text;
+
+typedef struct Buffer Buffer;
+struct Buffer {
+ Text *texts;
+ char name[256];
+ char cmd[512];
+ int cmdlen;
+ int txtsz;
+ int ntxt;
+ int offy;
+ Buffer *next;
+};
+
+typedef struct {
+ char *name;
+ void (*func)(char *);
+} Command;
+
+typedef struct {
+ const int key;
+ void (*func)(const Arg *);
+ const Arg arg;
+} Key;
+
+void attach(Buffer *b);
+void cleanup(void);
+void detach(Buffer *b);
+int dial(char *host, char *port);
+void die(const char *errstr, ...);
+void draw(void);
+void drawbar(void);
+void drawbuf(void);
+void drawcmdln(void);
+void *ecalloc(size_t nmemb, size_t size);
+void editor_clear(const Arg *arg);
+Buffer *getbuf(char *name);
+void focusnext(const Arg *arg);
+void focusprev(const Arg *arg);
+int getkey(void);
+void logw(char *txt);
+void msg(char *s);
+int mvprintf(int x, int y, char *fmt, ...);
+Buffer *newbuf(char *name);
+void parsecmd(void);
+void parsesrv(void);
+int printb(Buffer *b, char *fmt, ...);
+void quit(char *s);
+void resize(int x, int y);
+void scroll(const Arg *arg);
+void server(char *s);
+void setup(void);
+void sigwinch(int unused);
+char *skip(char *s, char c);
+void sout(char *fmt, ...);
+void trim(char *s);
+void usage(void);
+void usrin(void);
+
+/* variables */
+FILE *srv, *logp;
+Buffer *buffers, *sel;
+struct termios origti;
+int running = 1;
+int rows, cols;
+
+char *host = "irc.freenode.org";
+char *port = "6667";
+char logfile[64] = "/tmp/circo.log";
+char nick[32];
+
+
+/* configuration, allows nested code to access above variables */
+#include "config.h"
+
+/* function implementations */
+void
+attach(Buffer *b) {
+ b->next = buffers;
+ buffers = b;
+}
+
+void
+cleanup(void) {
+ Buffer *b;
+
+ while(buffers) {
+ b = buffers;
+ buffers = buffers->next;
+ free(b->texts);
+ free(b);
+ }
+ tcsetattr(0, TCSANOW, &origti);
+}
+
+void
+detach(Buffer *b) {
+ Buffer **tb;
+
+ for (tb = &buffers; *tb && *tb != b; tb = &(*tb)->next);
+ *tb = b->next;
+}
+
+int
+dial(char *host, char *port) {
+ static struct addrinfo hints;
+ struct addrinfo *res, *r;
+ int srvfd;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ if(getaddrinfo(host, port, &hints, &res) != 0)
+ die("error: cannot resolve hostname '%s':", host);
+ for(r = res; r; r = r->ai_next) {
+ if((srvfd = socket(r->ai_family, r->ai_socktype, r->ai_protocol)) == -1)
+ continue;
+ if(connect(srvfd, r->ai_addr, r->ai_addrlen) == 0)
+ break;
+ close(srvfd);
+ }
+ freeaddrinfo(res);
+ if(!r)
+ die("error: cannot connect to host '%s'\n", host);
+ return srvfd;
+}
+
+void
+die(const char *errstr, ...) {
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void
+draw(void) {
+ drawbuf();
+ drawbar();
+ drawcmdln();
+}
+
+void
+drawbar(void) {
+ mvprintf(1, 1, "%s@%s:%s - %s%s",
+ srv ? nick : "", srv ? host : "", srv ? port : "",
+ sel->name, CLEARRIGHT);
+}
+
+void
+drawbuf(void) {
+ Text txt;
+ int x, y, i, j;
+
+ printf(CURSOFF);
+ y = rows - 1;
+ for(i = sel->ntxt - 1 - sel->offy; i >= 0 && y > 1; --i) {
+ txt = sel->texts[i];
+ x = 1;
+ if(txt.len < cols - 1) {
+ mvprintf(x, y, "%s", CLEARLN);
+ x += mvprintf(x, y, "%s", txt.text);
+ }
+ else {
+ y -= txt.len / cols;
+ for(j = 0; j < txt.len && i > 1; ++j) {
+ if(x >= cols - 1) {
+ ++y;
+ x = 1;
+ }
+ x += mvprintf(x, y, "%c", txt.text[j]);
+ }
+ x += mvprintf(x, y, "%s", CLEARRIGHT);
+ y -= txt.len / cols;
+ }
+ --y;
+ }
+ while(--y > 1)
+ mvprintf(1, y, "%s", CLEARLN);
+ printf(CURSON);
+}
+
+void
+drawcmdln(void) {
+ int pslen = 4 + strlen(sel->name); /* 1 for the cursor */
+ int cmdsz = cols - pslen;
+ int i = sel->cmdlen > cmdsz ? sel->cmdlen - cmdsz : 0;
+
+ mvprintf(1, rows, "[%s] %s%s", sel->name, &sel->cmd[i], CLEARRIGHT);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size) {
+ void *p;
+
+ if(!(p = calloc(nmemb, size)))
+ die("Cannot allocate memory.\n");
+ return p;
+}
+
+void
+editor_clear(const Arg *arg) {
+ sel->cmd[0] = '\0';
+ sel->cmdlen = 0;
+ drawcmdln();
+}
+
+void
+focusnext(const Arg *arg) {
+ sel = sel->next ? sel->next : buffers;
+ draw();
+}
+
+void
+focusprev(const Arg *arg) {
+ Buffer *b;
+
+ if(sel == buffers) {
+ for(b = buffers; b; b = b->next)
+ sel = b;
+ }
+ else {
+ for(b = buffers; b && b->next; b = b->next)
+ if(b->next == sel)
+ sel = b;
+ }
+ draw();
+}
+
+Buffer *
+getbuf(char *name) {
+ Buffer *b;
+
+ for(b = buffers; b; b = b->next)
+ if(!strcmp(b->name, name))
+ return b;
+
+ return NULL;
+}
+
+/* XXX quick'n dirty implementation */
+int
+getkey(void) {
+ int key = getchar(), c;
+
+ if(key != '\x1b' || getchar() != '[')
+ return key;
+ switch((c = getchar())) {
+ case 'A': key = KeyUp; break;
+ case 'B': key = KeyDown; break;
+ case 'C': key = KeyRight; break;
+ case 'D': key = KeyLeft; break;
+ case 'H': key = KeyHome; break;
+ case 'F': key = KeyEnd; break;
+ case '1': key = KeyHome; break;
+ case '3': key = KeyDel; break;
+ case '4': key = KeyEnd; break;
+ case '5': key = KeyPgUp; break;
+ case '6': key = KeyPgDw; break;
+ case '7': key = KeyHome; break;
+ case '8': key = KeyEnd; break;
+ default:
+ /* debug */
+ mvprintf(1, rows, "Unknown char: %c (%d)", c, c);
+ break;
+ }
+ return key;
+}
+
+void
+logw(char *txt) {
+ if(!logp)
+ return;
+ fprintf(logp, "%s\n", txt);
+ fflush(logp);
+}
+
+void
+msg(char *s) {
+ char *to, *txt;
+
+ if(!srv) {
+ printb(sel, "You're offline.");
+ return;
+ }
+ to = s;
+ txt = skip(to, ' ');
+ sout("PRIVMSG %s :%s", to, txt);
+}
+
+int
+mvprintf(int x, int y, char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ printf(CURPOS, y, x);
+ va_start(ap, fmt);
+ len = vfprintf(stdout, fmt, ap);
+ va_end(ap);
+ return len;
+}
+
+Buffer *
+newbuf(char *name) {
+ Buffer *b;
+
+ b = ecalloc(1, sizeof(Buffer));
+ strncpy(b->name, name, sizeof b->name);
+ attach(b);
+ return b;
+}
+
+void
+parsecmd(void) {
+ char *p, *tp;
+ int i, len;
+
+ /* command */
+ p = &sel->cmd[1];
+ if(!*p)
+ return;
+ tp = p + 1;
+ tp = skip(p, ' ');
+ len = strlen(p);
+ for(i = 0; i < LENGTH(commands); ++i) {
+ if(!strncmp(commands[i].name, p, len)) {
+ commands[i].func(tp);
+ return;
+ }
+ }
+ if(srv) {
+ sout("%s %s", p, tp);
+ fflush(srv);
+ }
+ else {
+ printb(sel, "/%s: not connected.", p);
+ }
+}
+
+void
+parsesrv(void) {
+ Buffer *b;
+ char *cmd, *usr, *par, *txt;
+ char buf[4096]; /* XXX size */
+
+ if(fgets(buf, sizeof buf, srv) == NULL) {
+ srv = NULL;
+ printb(sel, "! Remote host closed connection");
+ draw();
+ return;
+ }
+ cmd = buf;
+ 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(txt);
+ trim(par);
+ printb(getbuf("status"), "[DEBUG] %s | %s | %s | txt:(%s)", cmd, usr, par, txt);
+ if(!strcmp("PRIVMSG", cmd)) {
+ b = getbuf(par);
+ if(!b)
+ b = newbuf(par);
+ printb(b, "%s: %s", usr, txt);
+ if(b != sel)
+ return;
+ }
+ else if(!strcmp("JOIN", cmd)) {
+ if(strcmp(usr, nick)) {
+ printb(getbuf(par), "JOIN %s (%s)", usr, txt);
+ return;
+ }
+ printb((sel = newbuf(par)), "You joined %s", par);
+ }
+ else if(!strcmp("331", cmd) || !strcmp("332", cmd)) {
+ printb(sel, "Topic on %s is %s", par, txt);
+ }
+ else if(!strcmp("TOPIC", cmd)) {
+ printb(getbuf(par), "%s has changed the topic: %s", usr, txt);
+ }
+ else if(!strcmp("QUIT", cmd)) {
+ /* XXX no channel here */
+ printb(sel, "QUIT %s (%s)", usr, txt);
+ }
+ else if(!strcmp("PART", cmd)) {
+ if(strcmp(usr, nick)) {
+ printb(getbuf(par), "PART %s (%s)", usr, txt);
+ return;
+ }
+ b = getbuf(par);
+ if(b != sel) {
+ detach(b);
+ return;
+ }
+ sel = sel->next ? sel->next : buffers;
+ detach(b);
+ }
+ else if(!strcmp("PING", cmd)) {
+ sout("PONG %s", txt);
+ return;
+ }
+ else if(!strcmp("PONG", cmd) || !strcmp("366", cmd) || !strcmp("375", cmd) || !strcmp("376", cmd))
+ return;
+ else if(!strcmp("NOTICE", cmd))
+ printb(sel, "NOTICE: %s: %s", usr, txt);
+ else if(!strcmp("MODE", cmd)) {
+ if(*nick)
+ return;
+ strcpy(nick, par);
+ }
+ else if(!strcmp("NICK", cmd)) {
+ if(strcmp(usr, nick)) {
+ /* XXX no channel here */
+ printb(sel, "NICK %s: %s", usr, txt);
+ return;
+ }
+ strcpy(nick, txt);
+ printb(sel, "Your nick is now %s", nick);
+ }
+ else if(!strcmp("437", cmd)) {
+ printb(sel, "%s is busy, choose a different /nick", nick);
+ *nick = '\0';
+ }
+ else if(!strcmp("353", cmd)) {
+ par = skip(par, '@') + 1;
+ printb(sel, "Users in %s: %s", par, txt); /* XXX par is wrong */
+ }
+ else {
+ /* XXX 470 channel forward */
+
+ if(!strcmp("372", cmd)) /* motd */
+ b = getbuf("status");
+ else
+ b = sel;
+ printb(b, "%s", txt);
+ if(b != sel)
+ return;
+ }
+ draw();
+}
+
+int
+printb(Buffer *b, char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ if(!b->txtsz || b->ntxt >= b->txtsz)
+ if(!(b->texts = realloc(b->texts, (b->txtsz += 512) * sizeof(Text))))
+ die("cannot realloc\n");
+ va_start(ap, fmt);
+ len = vsprintf(b->texts[b->ntxt].text, fmt, ap);
+ va_end(ap);
+ logw(b->texts[b->ntxt].text);
+ b->texts[b->ntxt++].len = len;
+ return len;
+}
+
+void
+quit(char *s) {
+ running = 0;
+}
+
+void
+resize(int x, int y) {
+ rows = x;
+ cols = y;
+}
+
+void
+scroll(const Arg *arg) {
+ int y, n;
+
+ if(sel->ntxt < rows - 1)
+ return;
+ y = sel->offy + arg->i;
+ if(y < 0)
+ y = 0;
+ else {
+ n = sel->ntxt - rows - 1;
+ if(y > n)
+ y = n;
+ }
+ sel->offy = y;
+ draw();
+}
+
+void
+server(char *s) {
+ if(srv)
+ quit(NULL);
+ srv = fdopen(dial(host, port), "r+");
+ if(!srv)
+ die("cannot connect to server");
+ setbuf(srv, NULL);
+ sout("NICK %s", nick);
+ sout("USER %s localhost %s :%s", nick, host, nick);
+}
+
+void
+setup(void) {
+ struct termios ti;
+ struct sigaction sa;
+ struct winsize ws;
+
+ setlocale(LC_CTYPE, "");
+ sa.sa_flags = 0;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = sigwinch;
+ sigaction(SIGWINCH, &sa, NULL);
+ tcgetattr(0, &origti);
+ ti.c_lflag &= ~(ECHO | ICANON);
+ ti.c_iflag |= ICRNL;
+ tcsetattr(0, TCSAFLUSH, &ti);
+
+ ioctl(0, TIOCGWINSZ, &ws);
+ resize(ws.ws_row, ws.ws_col);
+}
+
+void
+sigwinch(int unused) {
+ struct winsize ws;
+
+ ioctl(0, TIOCGWINSZ, &ws);
+ resize(ws.ws_row, ws.ws_col);
+ draw();
+}
+
+char *
+skip(char *s, char c) {
+ while(*s != c && *s != '\0')
+ s++;
+ if(*s != '\0')
+ *s++ = '\0';
+ return s;
+}
+
+void
+sout(char *fmt, ...) {
+ va_list ap;
+ char buf[512];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof buf, fmt, ap);
+ va_end(ap);
+ fprintf(srv, "%s\r\n", buf);
+}
+
+void
+trim(char *s) {
+ char *e;
+
+ e = s + strlen(s) - 1;
+ while(isspace(*e) && e > s)
+ e--;
+ *(e + 1) = '\0';
+}
+
+void
+usage(void) {
+ die("Usage: %s ...", argv0);
+}
+
+void
+usrin(void) {
+ int key = getkey(), iskey = 0, i;
+
+ for(i = 0; i < LENGTH(keys); ++i) {
+ if(keys[i].key == key) {
+ keys[i].func(&keys[i].arg);
+ iskey = 1;
+ }
+ }
+ if(iskey) {
+ while(getkey() != EOF); /* discard remaining input */
+ return;
+ }
+
+ if(key == '\n') {
+ logw(sel->cmd);
+ if(sel->cmd[0] == '\0')
+ return;
+ sel->cmd[sel->cmdlen] = '\0';
+
+ if(sel->cmd[0] == '/') {
+ if(sel->cmdlen == 1)
+ return;
+ parsecmd();
+ }
+ else {
+ if(!strcmp(sel->name, "status")) {
+ printb(sel, "Cannot send text here.");
+ }
+ else {
+ if(!srv) {
+ printb(sel, "You're not connected.");
+ return;
+ }
+ sout("PRIVMSG %s :%s", sel->name, sel->cmd);
+ printb(sel, "%s: %s", nick, sel->cmd);
+ }
+ }
+
+ sel->cmd[0] = '\0';
+ sel->cmdlen = 0;
+ draw();
+ }
+ else if(isgraph(key) || (key == ' ' && sel->cmdlen)) {
+ if(sel->cmdlen >= sizeof sel->cmd)
+ return;
+ sel->cmd[sel->cmdlen++] = key;
+ sel->cmd[sel->cmdlen] = '\0';
+ drawcmdln();
+ }
+}
+
+int
+main(int argc, char *argv[]) {
+ struct timeval tv;
+ fd_set rd;
+ int n, nfds;
+ const char *user = getenv("USER");
+
+ ARGBEGIN {
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'p':
+ port = EARGF(usage());
+ break;
+ case 'n':
+ strncpy(nick, EARGF(usage()), sizeof nick);
+ break;
+ case 'l':
+ strncpy(logfile, EARGF(usage()), sizeof logfile);
+ break;
+ case 'v':
+ die("foo..");
+ default:
+ usage();
+ } ARGEND;
+
+ if(!*nick)
+ strncpy(nick, user ? user : "unknown", sizeof nick);
+ setup();
+
+ if(*logfile)
+ logp = fopen(logfile, "a");
+ sel = newbuf("status");
+ setbuf(stdout, NULL);
+ printf(CLEAR);
+ draw();
+ while(running) {
+ FD_ZERO(&rd);
+ FD_SET(0, &rd);
+ tv.tv_sec = 120;
+ tv.tv_usec = 0;
+ nfds = 0;
+ if(srv) {
+ FD_SET(fileno(srv), &rd);
+ nfds = fileno(srv);
+ }
+ n = select(nfds + 1, &rd, 0, 0, &tv);
+ if(n < 0) {
+ if(errno == EINTR)
+ continue;
+ die("circo: error on select()\n");
+ }
+ else if(n == 0) {
+ if(srv)
+ sout("PING %s", host);
+ continue;
+ }
+ if(srv && FD_ISSET(fileno(srv), &rd))
+ parsesrv();
+ if(FD_ISSET(0, &rd))
+ usrin();
+ }
+ mvprintf(1, cols, "\n");
+ cleanup();
+ return 0;
+}
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+
+Command commands[] = {
+ { "connect", server },
+ { "msg", msg },
+ { "quit", quit },
+ { "server", server },
+};
+
+/* key definitions */
+static Key keys[] = {
+ /* key function argument */
+ { CTRL('u'), editor_clear, {0} },
+ { CTRL('n'), focusnext, {0} },
+ { CTRL('p'), focusprev, {0} },
+ { KeyPgUp, scroll, {.i = +20} },
+ { KeyPgDw, scroll, {.i = -20} },
+};
diff --git a/config.mk b/config.mk
@@ -0,0 +1,16 @@
+# circo
+VERSION = 0.1
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+# flags
+CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\"
+CFLAGS = -std=c99 -g -pedantic -Wall -O0 ${CPPFLAGS}
+#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${CPPFLAGS}
+
+# compiler and linker
+CC = cc