circo

claudio's IRC oasis
git clone git://git.bitsmanent.org/circo
Log | Files | Refs | README | LICENSE

commit c92ec291e2aa1ea8975601ee0a289458e5cc4536
parent 075a9402346f2a4d19c9418fb4b8c338b8160718
Author: Claudio Alessi <smoppy@gmail.com>
Date:   Sat,  5 Feb 2022 16:29:24 +0100

Add 256 colors support

Diffstat:
MMakefile | 2+-
MREADME.md | 1+
Mcirco.c | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mconfig.def.h | 12++++++++++--
Aprintfc.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aprintfc.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);