commit c92ec291e2aa1ea8975601ee0a289458e5cc4536
parent 075a9402346f2a4d19c9418fb4b8c338b8160718
Author: Claudio Alessi <smoppy@gmail.com>
Date: Sat, 5 Feb 2022 16:29:24 +0100
Add 256 colors support
Diffstat:
M | Makefile | | | 2 | +- |
M | README.md | | | 1 | + |
M | circo.c | | | 105 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
M | config.def.h | | | 12 | ++++++++++-- |
A | printfc.c | | | 223 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | printfc.h | | | 8 | ++++++++ |
6 files changed, 324 insertions(+), 27 deletions(-)
diff --git a/Makefile b/Makefile
@@ -4,7 +4,7 @@
include config.mk
APPNAME=circo
-SRC = ${APPNAME}.c
+SRC = ${APPNAME}.c printfc.c
OBJ = ${SRC:.c=.o}
all: options ${APPNAME}
diff --git a/README.md b/README.md
@@ -19,6 +19,7 @@ one would expect from an IRC client for the console:
- Resize handling
- Command history
- Raw IRC commands
+- Colors support
None of the CTCP specification has been (nor will be) implemented, which means
no DCC at all. In other words: direct chat and files sending are not available.
diff --git a/circo.c b/circo.c
@@ -1,4 +1,14 @@
-/* claudio's IRC oasis */
+/* See LICENSE file for copyright and license details.
+ *
+ * circo is an IRC client...
+ *
+ * The messages handlers are organized in an array which is accessed whenever a
+ * new message has been fetched. This allows message dispatching in O(1) time.
+ *
+ * Keys are organized as arrays and defined in config.h.
+ *
+ * To understand everything else, start reading main().
+*/
#include <ctype.h>
#include <errno.h>
@@ -17,6 +27,8 @@
#include <time.h>
#include <unistd.h>
+#include "printfc.h"
+
#include "arg.h"
char *argv0;
@@ -53,6 +65,13 @@ char *argv0;
enum { KeyUp = -50, KeyDown, KeyRight, KeyLeft, KeyHome, KeyEnd, KeyDel, KeyPgUp, KeyPgDw, KeyBackspace };
enum { LineToOffset, OffsetToLine, TotalLines }; /* bufinfo() flags */
+enum {
+ NickNormal,
+ NickMention,
+ IRCMessage,
+ ColorLast
+} Color;
+
typedef union {
int i;
unsigned int ui;
@@ -157,6 +176,7 @@ void setup(void);
void sigwinch(int unused);
char *skip(char *s, char c);
void sout(char *fmt, ...);
+void strip_ctrlseqs(char *s);
void trim(char *s);
void usage(void);
void usrin(void);
@@ -215,7 +235,8 @@ bprintf(Buffer *b, char *fmt, ...) {
tm = time(NULL);
len = strftime(buf, sizeof(buf), TIMESTAMP_FORMAT, localtime(&tm));
va_start(ap, fmt);
- len += vsnprintf(&buf[len], sizeof(buf) - len - 1, fmt, ap);
+ vsnprintfc(&buf[len], sizeof(buf) - len - 1, fmt, ap);
+ len += strlen(&buf[len]);
va_end(ap);
if(!b->size || b->len + len >= b->size)
if(!(b->data = realloc(b->data, b->size += len + BUFSZ)))
@@ -224,7 +245,15 @@ bprintf(Buffer *b, char *fmt, ...) {
b->len += len;
b->nlines = bufinfo(b->data, b->len, 0, TotalLines);
sel->need_redraw |= REDRAW_BUFFER;
+
+ /* It's easy to just logw() in bprintf() so we can log
+ * anything in a single place. Though this force us to:
+ * - log data in the same format as the actual UI, and
+ * - strip control sequences (colors) from the buffer.
+ * Apart from this all should be fine. */
+ strip_ctrlseqs(buf);
logw(buf);
+
return len;
}
@@ -314,14 +343,14 @@ cmd_msg(char *cmd, char *s) {
}
void
-cmd_quit(char *cmd, char *s) {
+cmd_quit(char *cmd, char *msg) {
Buffer *b;
if(!srv) {
bprintf(sel, "/%s: not connected.\n", cmd);
return;
}
- quit(s);
+ quit(*msg ? msg : QUIT_MESSAGE);
for(b = buffers; b; b = b->next)
bprintf(b, "Quit.\n");
}
@@ -355,7 +384,7 @@ cmd_server(char *cmd, char *s) {
else
strncpy(host, t, sizeof host);
if(srv)
- quit(NULL);
+ quit(QUIT_MESSAGE);
if(!*host) {
bprintf(status, "/%s: no host specified.\n", cmd);
return;
@@ -572,13 +601,24 @@ drawbuf(void) {
x = 1;
y = 2;
for(; i < sel->len; ++i) {
- c = sel->data[i];
- if(c != '\n' && x <= cols) {
- x += mvprintf(x, y, "%c", c);
+
+ /* control sequences (for colors) */
+ if(sel->data[i] == 0x1b) {
+ while(!isalpha(sel->data[i]))
+ putchar(sel->data[i++]);
+ putchar(sel->data[i]);
continue;
}
- if(c == '\n' && x <= cols)
+
+ c = sel->data[i];
+ if(x <= cols) {
+ if(c != '\n') {
+ x += mvprintf(x, y, "%c", c);
+ continue;
+ }
mvprintf(x, y, "%s", CLEARRIGHT);
+ }
+
x = 1;
if(++y == rows)
break;
@@ -597,22 +637,22 @@ drawbuf(void) {
void
drawcmdln(void) {
char buf[cols+1];
- char prompt[64];
- int pslen, cmdsz, cur, i, len;
+ char prompt[cols+1 + 31];
+ int pslen, cmdsz, i, len;
if(!(cols && rows))
return;
+
pslen = snprintf(prompt, sizeof prompt, "[%s] ", sel->name);
cmdsz = pslen < cols ? cols - pslen : 0;
if(cmdsz) {
- cur = pslen + (sel->cmdoff % cmdsz) + 1;
+ sel->cmdcur = pslen + (sel->cmdoff % cmdsz) + 1;
i = cmdsz * (sel->cmdoff / cmdsz);
}
else {
- cur = cols;
+ sel->cmdcur = cols;
i = 0;
}
- sel->cmdcur = cur;
len = snprintf(buf, sizeof buf, "%s%s", prompt, &sel->cmd[i]);
mvprintf(1, rows, "%s%s", buf, len < cols ? CLEARRIGHT : "");
}
@@ -858,7 +898,7 @@ privmsg(char *to, char *txt) {
void
quit(char *msg) {
if(srv)
- sout("QUIT :%s", msg ? msg : QUIT_MESSAGE);
+ sout("QUIT :%s", msg);
hangsup();
}
@@ -890,7 +930,8 @@ recv_join(char *who, char *chan, char *txt) {
return;
sel = b;
}
- bprintf(b, "JOIN %s\n", who);
+ bprintf(b, "%CJOIN%..0C %s\n", colors[IRCMessage], who);
+
if(b == sel)
sel->need_redraw |= REDRAW_ALL;
}
@@ -904,9 +945,9 @@ recv_kick(char *who, char *chan, char *txt) {
if(!b)
return;
if(strcmp(txt, nick))
- bprintf(b, "KICK %s %s\n", who, txt);
+ bprintf(b, "%CKICK%..0C %s %s\n", colors[IRCMessage], who, txt);
else
- bprintf(b, "Kicked.\n");
+ bprintf(b, "You got kicked from %s\n", chan);
}
void
@@ -928,13 +969,12 @@ recv_nick(char *who, char *u, char *txt) {
strcpy(nick, txt);
sel->need_redraw |= REDRAW_BAR;
}
- bprintf(sel, "NICK %s: %s\n", who, txt);
+ bprintf(sel, "%CNICK%..0C %s: %s\n", colors[IRCMessage], who, txt);
}
void
recv_notice(char *who, char *u, char *txt) {
- /* XXX redirect to the relative buffer? */
- bprintf(sel, "NOTICE: %s: %s\n", who, txt);
+ bprintf(sel, "%CNOTICE%..0C %s: %s\n", colors[IRCMessage], who, txt);
}
void
@@ -945,7 +985,7 @@ recv_part(char *who, char *chan, char *txt) {
if(!b)
return;
if(strcmp(who, nick)) {
- bprintf(b, "PART %s %s\n", who, txt);
+ bprintf(b, "%CPART%..0C %s %s\n", colors[IRCMessage], who, txt);
return;
}
if(b == sel) {
@@ -964,18 +1004,20 @@ recv_ping(char *u, char *u2, char *txt) {
void
recv_privmsg(char *from, char *to, char *txt) {
Buffer *b;
+ int mention;
if(!strcmp(nick, to))
to = from;
b = getbuf(to);
if(!b)
b = newbuf(to);
- bprintf(b, "%s: %s\n", from, txt);
+ mention = strstr(txt, nick) != NULL;
+ bprintf(b, "%C%s%..0C: %s\n", mention ? colors[NickMention] : colors[NickNormal], from, txt);
}
void
recv_quit(char *who, char *u, char *txt) {
- bprintf(sel, "QUIT %s (%s)\n", who, txt);
+ bprintf(sel, "%CQUIT%..0C %s (%s)\n", colors[IRCMessage], who, txt);
}
void
@@ -1139,6 +1181,21 @@ sout(char *fmt, ...) {
}
void
+strip_ctrlseqs(char *s) {
+ int i = 0;
+ char *c;
+
+ for(c = s; *c; ++c) {
+ if(*c == 0x1b) {
+ while(*++c && !isalpha(*c));
+ continue;
+ }
+ s[i++] = *c;
+ }
+ s[i] = '\0';
+}
+
+void
trim(char *s) {
char *e;
diff --git a/config.def.h b/config.def.h
@@ -1,7 +1,7 @@
/* See LICENSE file for copyright and license details. */
/* defaults */
-char host[32] = "irc.freenode.org";
+char host[32] = "irc.libera.chat";
char port[8] = "6667";
char nick[32] = {0}; /* 0 means getenv("USER") */
char logfile[64] = "/tmp/circo.log";
@@ -12,7 +12,15 @@ char logfile[64] = "/tmp/circo.log";
/* Used if no message is specified */
#define QUIT_MESSAGE "circo"
-Command commands[] = {
+/* color scheme */
+static int colors[ColorLast][5] = {
+ [NickNormal] = {255, -1},
+ [NickMention] = {5, 0, 1, -1},
+ [IRCMessage] = {8, 0, -1},
+};
+
+/* available commands */
+static Command commands[] = {
/* command function */
{ "close", cmd_close },
{ "connect", cmd_server },
diff --git a/printfc.c b/printfc.c
@@ -0,0 +1,223 @@
+/* See LICENSE file for copyright and license details.
+ *
+ * Bring colors to printf().
+ *
+ * Format: CSV of values.
+ * Values: fg, bg, att1, att2, attN, ...
+ * See: https://en.wikipedia.org/wiki/ANSI_escape_code
+ *
+ * At least one value must be provided.
+ * Any value may be omitted (e.g. %.1C omit fg and set bg=1)
+ * Any value may be replaced with * (value is specified by a variable)
+ * %C expect an array of values (last one must be -1)
+ *
+ * Important: this family of function must be used veri careful. The compiler
+ * known nothing about %C and also recognized format strings are built at
+ * runtime thus the compiler has no hint to test for correctness.
+ *
+ * Examples:
+ *
+ * %fg.bg.att1.attN
+ * %..0: RESET (no fg, no bg, att 0)
+ * %fg
+ * %.bg
+ * %.bg.att
+ * %..att
+ * %C (int[]){fg, bg, att1, attN, ..., -1}
+ * %*C fg
+ * %.*C bg
+ * %..*C att
+ * %..*.*C att1, att2
+ *
+ * To understand everything else, start reading doprintfc().
+*/
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "printfc.h"
+
+#define COLFG "\33[38;5;%dm"
+#define COLBG "\33[48;5;%dm"
+#define ATTR "\33[%dm"
+
+int doprintfc(char *str, size_t size, size_t *slen, char *fmt, va_list ap);
+int doprintfc_parse(int *n, char *s, va_list ap);
+
+int
+doprintfc(char *str, size_t size, size_t *slen, char *fmt, va_list ap) {
+ char *p, *e;
+ char f[16]; /* everyone should be happy */
+ int len = 0, nc = 0, n;
+ int *colors;
+
+ /* both or none */
+ if((!str && size) || (!size && str))
+ return -1;
+
+/* TODO: find a better way */
+#define STRORNULL (len < size ? &str[len] : NULL)
+#define SIZORNULL (len < size ? size - len : 0)
+
+ for(p = fmt; *p; ++p) {
+ if(*p != '%') {
+ if(SIZORNULL)
+ str[len] = *p;
+ ++len;
+ ++nc;
+ continue;
+ }
+ if(!*++p)
+ break;
+ if(*p == '%') {
+ if(SIZORNULL)
+ str[len] = *p;
+ ++len;
+ ++nc;
+ continue;
+ }
+
+ /* jump to the alpha char */
+ for(e = p; *e && !isalpha(*e); ++e);
+
+ /* handle multi-char specifiers like %ld %lld %hhd ... */
+ if(*e == 'l' || *e == 'h') {
+ ++e;
+ if(*e == 'l' || *e == 'h')
+ ++e;
+ }
+
+ switch(*e) {
+ case 'C':
+ if(p == e) {
+ colors = va_arg(ap, int *);
+
+ /*
+ printf("C0=%d", colors[0]);
+ if(colors[1] == -1) printf(" C1=%d", colors[1]);
+ for(n = 2; colors[n] != -1; ++n)
+ printf(" A%d=%d", n - 2, colors[n]);
+ printf("\n");
+ */
+
+ len += snprintf(STRORNULL, SIZORNULL, COLFG, colors[0]);
+ if(colors[1] == -1)
+ break;
+ len += snprintf(STRORNULL, SIZORNULL, COLBG, colors[1]);
+ for(n = 2; colors[n] != -1; ++n)
+ len += snprintf(STRORNULL, SIZORNULL, ATTR, colors[n]);
+ break;
+ }
+
+ /* foreground */
+ p += doprintfc_parse(&n, p, ap);
+ if(n)
+ len += snprintf(STRORNULL, SIZORNULL, COLFG, n);
+
+ /* background */
+ if(*p == '.') {
+ ++p;
+ p += doprintfc_parse(&n, p, ap);
+ if(n)
+ len += snprintf(STRORNULL, SIZORNULL, COLBG, n);
+ }
+
+ /* attributes */
+ while(*p == '.') {
+ ++p;
+ p += doprintfc_parse(&n, p, ap);
+ len += snprintf(STRORNULL, SIZORNULL, ATTR, n);
+ }
+ break;
+ default:
+ /* TODO: explain what's happening here */
+ strncpy(f, p - 1, e - p + 2);
+ f[e - p + 2] = '\0';
+ p = e;
+
+ n = vsnprintf(STRORNULL, SIZORNULL, f, ap);
+ if(n < 0)
+ return n;
+ len += n;
+ nc += n;
+ break;
+ }
+ }
+ va_end(ap);
+
+ if(SIZORNULL)
+ str[len] = '\0';
+ if(slen)
+ *slen = len;
+ return nc;
+}
+
+int
+doprintfc_parse(int *n, char *s, va_list ap) {
+ char *p = s;
+
+ if(*p == '*') {
+ *n = va_arg(ap, int);
+ ++p;
+ }
+ else {
+ *n = 0;
+ while(*p && isdigit(*p)) {
+ *n = *n * 10 + *p - '0';
+ ++p;
+ }
+ }
+ return p - s;
+}
+
+int
+printfc(char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = vprintfc(fmt, ap);
+ va_end(ap);
+ return len;
+}
+
+int
+snprintfc(char *str, size_t size, char *fmt, ...) {
+ va_list ap;
+ int len;
+
+ va_start(ap, fmt);
+ len = doprintfc(str, size, NULL, fmt, ap);
+ va_end(ap);
+ return len;
+}
+
+int
+vprintfc(char *fmt, va_list ap) {
+ va_list apcopy;
+ char *buf;
+ size_t size;
+ int len;
+
+ va_copy(apcopy, ap);
+ len = doprintfc(NULL, 0, &size, fmt, ap);
+ if(size < 0)
+ return -1;
+ if(!(buf = malloc(size+1))) {
+ va_end(apcopy);
+ return -1;
+ }
+ doprintfc(buf, size+1, NULL, fmt, apcopy);
+ printf("%s", buf);
+ va_end(apcopy);
+ free(buf);
+ return len;
+}
+
+int
+vsnprintfc(char *str, size_t size, char *fmt, va_list ap) {
+ return doprintfc(str, size, NULL, fmt, ap);
+}
diff --git a/printfc.h b/printfc.h
@@ -0,0 +1,8 @@
+/* See LICENSE file for copyright and license details. */
+
+#include <stdarg.h>
+
+int printfc(char *fmt, ...);
+int snprintfc(char *str, size_t size, char *fmt, ...);
+int vprintfc(char *fmt, va_list ap);
+int vsnprintfc(char *str, size_t size, char *fmt, va_list ap);