summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c208
1 files changed, 164 insertions, 44 deletions
diff --git a/main.c b/main.c
index 3255b93..f9adb85 100644
--- a/main.c
+++ b/main.c
@@ -63,20 +63,21 @@
/* colors */
#define CPAIR_ENUM_X(cp, fg, bg, name) cp,
+#define CPAIR_NAME_X(cp, fg, bg, name) S(name),
#define CPAIR_INIT_X(cp, fg, bg, name) init_pair(cp, fg, bg);
#define COLOR_NORM -1
#define COLOR_FG COLOR_NORM
#define COLOR_BG COLOR_NORM
#define CPAIR_LIST(X)\
- X(CPAIR_TEXT , COLOR_FG , COLOR_BG, "text")\
- X(CPAIR_MENTION , COLOR_BG , COLOR_FG, "mention")\
- X(CPAIR_USER , COLOR_YELLOW , COLOR_BG, "user")\
- X(CPAIR_PRONOUNS , COLOR_CYAN , COLOR_BG, "pronouns")\
- X(CPAIR_TIME , COLOR_BLUE , COLOR_BG, "time")\
- X(CPAIR_PFP , COLOR_BLUE , COLOR_BG, "pfp")\
- X(CPAIR_PFP_SELF , COLOR_YELLOW , COLOR_BG, "pfp.self")\
- X(CPAIR_BANNER , COLOR_FG , COLOR_BG, "banner")\
- X(CPAIR_BORDER , COLOR_BLUE , COLOR_BG, "border")\
+ X(CPAIR_TEXT , COLOR_FG , COLOR_BG, "text")\
+ X(CPAIR_MENTION , COLOR_BG , COLOR_FG, "mention")\
+ X(CPAIR_USER , COLOR_YELLOW , COLOR_BG, "user")\
+ X(CPAIR_PRONOUNS , COLOR_CYAN , COLOR_BG, "pronouns")\
+ X(CPAIR_TIME , COLOR_BLUE , COLOR_BG, "time")\
+ X(CPAIR_PFP , COLOR_BLUE , COLOR_BG, "pfp")\
+ X(CPAIR_PFP_SELF , COLOR_YELLOW , COLOR_BG, "pfp.self")\
+ X(CPAIR_BANNER , COLOR_FG , COLOR_BG, "banner")\
+ X(CPAIR_BORDER , COLOR_BLUE , COLOR_BG, "border")\
X(CPAIR_MSG_OK , COLOR_GREEN , COLOR_BG, "msg.ok")\
X(CPAIR_MSG_WARN , COLOR_YELLOW , COLOR_BG, "msg.warn")\
X(CPAIR_MSG_ERR , COLOR_RED , COLOR_BG, "msg.err")
@@ -88,6 +89,11 @@ typedef enum {
CPAIR_MAX
} ColorPair;
+Str cpair_name[CPAIR_MAX] = {
+ S("N/A"),
+ CPAIR_LIST(CPAIR_NAME_X)
+};
+
/* dynamic arrays */
#define DYNARR(T) struct { ptrdiff_t len, cap; T *data; }
@@ -145,10 +151,12 @@ typedef struct {
typedef struct {
struct {
- int pfp, pronouns;
- } see;
+ int pfp, pronouns, date;
+ const char *datefmt;
+ } post;
struct {
int left, right, top, bottom;
+ int text_x, text_y;
} margin;
int default_colors;
ColorOpt color[CPAIR_MAX];
@@ -158,13 +166,19 @@ typedef struct {
regex_t re_mention;
Options opt = {
- .see = {
+ .post = {
.pfp = 1,
- .pronouns = 1
+ .pronouns = 1,
+ .date = 1,
+ .datefmt = NULL
},
.margin = {
- .left = 0,
- .right = 0
+ .left = GFX_MARGIN_X,
+ .right = GFX_MARGIN_X,
+ .top = GFX_MARGIN_Y,
+ .bottom = GFX_MARGIN_Y,
+ .text_x = GFX_TEXT_MARGIN_X,
+ .text_y = GFX_TEXT_MARGIN_Y,
},
.default_colors = 1
};
@@ -437,16 +451,21 @@ static inline int ch_space(char c) {
return c <= 0x20;
}
+static inline int ch_break_at(char c) {
+ return ch_space(c) || c == '-' || c == '/';
+}
+
int str_cat_wrap(Str *out, Str s, int width, Arena *a) {
int lines = 1;
int x = 1;
for (int i = 0; i < s.n; i++) {
- if (ch_space(s.s[i])) {
+ if (ch_break_at(s.s[i])) {
int w = 0;
- for (int j = i + 1; j < s.n && !ch_space(s.s[j]); j++) w++;
+ for (int j = i + 1; j < s.n && !ch_break_at(s.s[j]); j++) w++;
if (x + w >= width || s.s[i] == '\n') {
x = 1;
str_catc(out, '\n', a);
+ if (!ch_space(s.s[i])) str_catc(out, s.s[i], a);
lines++;
} else if (x < width) {
str_catc(out, s.s[i], a);
@@ -480,16 +499,16 @@ typedef struct {
int post_left_margin(Post *post) {
int lm = opt.margin.left;
- if (opt.see.pfp && post->user->pfp.cols >= lm) lm = post->user->pfp.cols + GFX_PFP_MARGIN;
+ if (opt.post.pfp) lm += post->user->pfp.cols + GFX_PFP_MARGIN;
return lm;
}
int gfx_post_width(Post *post) {
- return getmaxx(stdscr) - GFX_MARGIN_X * 2 - post_left_margin(post) - opt.margin.right;
+ return getmaxx(stdscr) - (post_left_margin(post) + opt.margin.right);
}
int gfx_wrap_width(Post *post) {
- return gfx_post_width(post) - GFX_TEXT_MARGIN_X * 2;
+ return gfx_post_width(post) - opt.margin.text_x * 2 - 1;
}
void gfx_load_post(GfxPost *post, Post *src, int width, Arena *a) {
@@ -522,13 +541,13 @@ void gfx_predraw_post(GfxPost *post) {
}
int gfx_post_text_height(GfxPost *post) {
- return post->lines + 2 + GFX_TEXT_MARGIN_Y * 2;
+ return post->lines + 2 + opt.margin.text_y * 2;
}
int gfx_post_height(GfxPost *post) {
int base = gfx_post_text_height(post);
int l = MIN(post->src->user->pfp.lines, GFX_PFP_MAX_LINES);
- if (opt.see.pfp && l > base) return l;
+ if (opt.post.pfp && l > base) return l;
return base;
}
@@ -589,7 +608,7 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) {
post->drawn = 1;
}
- int left = x + post_left_margin(post->src), top = y + opt.margin.top;
+ int left = x, top = y;
int right = left + width - 1;
int self = is_post_mine(post->src);
User *u = post->src->user;
@@ -601,11 +620,23 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) {
gfx_draw_box(top, left, height, width);
- color_set(CPAIR_TIME, 0);
- Str s = str_trim(str_from_cstr(ctime(&(time_t){post->src->timestamp.tv_sec})));
- mvaddnstr(top, right - s.n, s.s, s.n);
+ if (opt.post.date) {
+ color_set(CPAIR_TIME, 0);
+ char time_buf[256] = { 0 };
+ Str time_str = { 0 };
+ time_t timep = post->src->timestamp.tv_sec;
+ if (opt.post.datefmt) {
+ struct tm *p = localtime(&timep);
+ strftime(time_buf, 255, opt.post.datefmt, p);
+ time_str = str_from_cstr(time_buf);
+ } else {
+ time_str = str_from_cstr(ctime(&timep));
+ }
+ time_str = str_trim(time_str);
+ mvaddnstr(top, right - fast_utf8_width(time_str), time_str.s, time_str.n);
+ }
- if (opt.see.pfp) {
+ if (opt.post.pfp) {
color_set(self ? CPAIR_PFP_SELF : CPAIR_PFP, 0);
Pic *pfp = &u->pfp;
int pfpy = y;
@@ -622,7 +653,7 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) {
color_set(CPAIR_USER, 0);
mvaddnstr(top, left + 2, u->name.s, u->name.n);
- if (opt.see.pronouns && u->pronouns.n) {
+ if (opt.post.pronouns && u->pronouns.n) {
color_set(CPAIR_PRONOUNS, 0);
mvaddstr(top, left + 2 + fast_utf8_width(u->name) + GFX_PRONOUNS_MARGIN, cstr_fmt(scratch, GFX_PRONOUNS_FMT, u->pronouns));
}
@@ -632,15 +663,15 @@ void gfx_draw_post(GfxPost *post, int y, int x, int width, Arena *scratch) {
Str txt = post->text;
for (int i = 0; i < post->lines; i++) {
Cut c = str_cut(txt, '\n');
- mvaddnstr(top + i + 1 + GFX_TEXT_MARGIN_Y, left + 1 + GFX_TEXT_MARGIN_X, c.head.s, c.head.n);
+ mvaddnstr(top + i + 1 + opt.margin.text_y, left + 1 + opt.margin.text_x, c.head.s, c.head.n);
txt = c.tail;
}
}
void gfx_draw(Gfx *gfx, int cur, Arena *scratch) {
erase();
- for (int i = cur, y = GFX_MARGIN_Y; i < gfx->len && y < getmaxy(stdscr) - GFX_MARGIN_Y; i++) {
- gfx_draw_post(&gfx->posts[i], y, GFX_MARGIN_X, gfx_post_width(gfx->posts[i].src), scratch);
+ for (int i = cur, y = opt.margin.top; i < gfx->len && y < getmaxy(stdscr) - opt.margin.bottom; i++) {
+ gfx_draw_post(&gfx->posts[i], y, post_left_margin(gfx->posts[i].src), gfx_post_width(gfx->posts[i].src), scratch);
y += gfx_post_height(&gfx->posts[i]) + GFX_POST_SPACING;
}
}
@@ -691,15 +722,15 @@ void gfx_draw_user(User *user, Arena *scratch) {
int cy = getmaxy(stdscr) >> 1, cx = getmaxx(stdscr) >> 1;
const char *binks = cstr_fmt(scratch, "%u binks", user->post_count);
- int box_height = 3 + GFX_TEXT_MARGIN_Y;
+ int box_height = 3 + opt.margin.text_y;
int box_width = MAX((int)strlen(binks), MAX(fast_utf8_width(user->name), fast_utf8_width(user->pronouns)));
- int bh = MIN(user->banner.lines, getmaxy(stdscr) - 3 - GFX_TEXT_MARGIN_Y * 2 - 4);
- int bw = MIN(user->banner.cols, getmaxx(stdscr) - GFX_TEXT_MARGIN_X * 2 - 4);
+ int bh = MIN(user->banner.lines, getmaxy(stdscr) - 3 - opt.margin.text_y * 2 - 4);
+ int bw = MIN(user->banner.cols, getmaxx(stdscr) - opt.margin.text_x * 2 - 4);
- int total_height = (bh ? bh + 1 : 0) + MAX(box_height, user->pfp.lines) + GFX_TEXT_MARGIN_Y * 2;
- int total_width = MAX(bw, user->pfp.cols + 2 + box_width) + GFX_TEXT_MARGIN_X * 2;
- gfx_cleared_box(cy - (total_height >> 1) - 2 - GFX_TEXT_MARGIN_Y, cx - (total_width >> 1) - 2 - GFX_TEXT_MARGIN_X,
+ int total_height = (bh ? bh + 1 : 0) + MAX(box_height, user->pfp.lines) + opt.margin.text_y * 2;
+ int total_width = MAX(bw, user->pfp.cols + 2 + box_width) + opt.margin.text_x * 2;
+ gfx_cleared_box(cy - (total_height >> 1) - 2 - opt.margin.text_y, cx - (total_width >> 1) - 2 - opt.margin.text_x,
total_height + 4, total_width + 4);
cy -= (total_height >> 1);
@@ -727,13 +758,13 @@ void gfx_draw_user(User *user, Arena *scratch) {
}
int y = cy;
- mvaddnstr(y++, left + GFX_TEXT_MARGIN_X, user->name.s, user->name.n);
+ mvaddnstr(y++, left + opt.margin.text_x, user->name.s, user->name.n);
if (user->pronouns.n > 0) {
color_set(CPAIR_PRONOUNS, 0);
- mvaddnstr(y++, left + GFX_TEXT_MARGIN_X, user->pronouns.s, user->pronouns.n);
+ mvaddnstr(y++, left + opt.margin.text_x, user->pronouns.s, user->pronouns.n);
}
color_set(CPAIR_TEXT, 0);
- mvaddstr(y++, left + GFX_TEXT_MARGIN_X, binks);
+ mvaddstr(y++, left + opt.margin.text_x, binks);
}
/* will skip at least one post, so not suitable for just scrolling one line at
@@ -856,8 +887,96 @@ void fini_curses(void) {
endwin();
}
-void load_config(void) {
- /* TODO: load cbinkrc */
+/* configuration */
+
+FILE *open_config_file(Arena *a) {
+ const char *s = cstr_fmt(a, "/home/%s/.cbinkrc", getlogin());
+ FILE *f = fopen(s, "r/o");
+ if (f) return f;
+ s = cstr_fmt(a, "/home/%s/.config/cbink/cbinkrc", getlogin());
+ f = fopen(s, "r/o");
+ if (f) return f;
+ return NULL;
+}
+
+typedef struct {
+ const char *name;
+ int (*fn)(Str key, Str value, Arena *perm);
+} Section;
+
+int sect_margin(Str key, Str value, Arena *perm) {
+ (void)perm;
+ uint64_t u;
+ if (str_to_u64(value, &u)) return -1;
+ if (str_eql(key, S("left"))) { opt.margin.left = u; return 0; }
+ if (str_eql(key, S("right"))) { opt.margin.right = u; return 0; }
+ if (str_eql(key, S("top"))) { opt.margin.top = u; return 0; }
+ if (str_eql(key, S("bottom"))) { opt.margin.bottom = u; return 0; }
+ if (str_eql(key, S("text.x"))) { opt.margin.text_x = u; return 0; }
+ if (str_eql(key, S("text.y"))) { opt.margin.text_y = u; return 0; }
+ return -1;
+}
+
+int sect_post(Str key, Str value, Arena *perm) {
+ (void)perm;
+ if (str_eql(key, S("datefmt"))) { opt.post.datefmt = str_to_cstr(value, perm); return 0; }
+ /* bools */
+ uint64_t u;
+ if (str_to_u64(value, &u)) return -1;
+ if (u != !!u) return -1;
+ if (str_eql(key, S("pfp"))) { opt.post.pfp = u; return 0; }
+ if (str_eql(key, S("pronouns"))) { opt.post.pronouns = u; return 0; }
+ if (str_eql(key, S("date"))) { opt.post.date = u; return 0; }
+ return 0;
+}
+
+Section sections[] = {
+ { "margin", &sect_margin },
+ { "post", &sect_post },
+};
+
+int load_config(Arena *perm, Arena *temp) {
+ FILE *f = open_config_file(temp);
+ Str buf = { 0 };
+ if (!f) return -1;
+ if (read_all(f, &buf, temp)) {
+ fclose(f);
+ return -1;
+ }
+
+ int err = 0;
+ Str line = { 0 };
+ int (*fn)(Str key, Str value, Arena *perm) = NULL;
+ while (next_line(&buf, &line)) {
+ if (line.n > 2 && line.s[0] == '[' && line.s[line.n - 1] == ']') {
+ Str sect_name = (Str) { line.s + 1, line.n - 2 };
+ fn = NULL;
+ for (size_t i = 0; i < sizeof sections / sizeof *sections; i++) {
+ if (str_eql(str_from_cstr(sections[i].name), sect_name)) {
+ fn = sections[i].fn;
+ break;
+ }
+ }
+ continue;
+ }
+ if (!fn) continue;
+ line = str_trim(line);
+ if (!line.n) continue;
+ if (line.s[0] == '#') continue;
+ Cut c = str_cut(line, '=');
+ c.head = str_trim(c.head);
+ c.tail = str_trim(c.tail);
+ if (!c.head.n || !c.tail.n) {
+ err = -1;
+ continue;
+ }
+ if (fn(c.head, c.tail, perm)) {
+ err = -1;
+ }
+ }
+
+ fclose(f);
+ return err;
}
/* main */
@@ -865,6 +984,7 @@ void load_config(void) {
int main(void) {
/* init */
+ Arena cfg_arena = { 0 };
Arena post_arena = { 0 };
Arena gfx_arena = { 0 };
Arena temp_arena = { 0 };
@@ -881,7 +1001,7 @@ int main(void) {
"([ \t\n]+|^)[@~]?%s([:;,.!? \t\n]|$)", getlogin()),
REG_EXTENDED | REG_ICASE | REG_NOSUB);
- load_config();
+ load_config(&cfg_arena, &temp_arena);
init_curses();
posts_refresh(&posts, &post_arena);
@@ -962,7 +1082,7 @@ refresh:
post_stats.gather_ns / 1000000);
goto resize;
case '\t':
- opt.see.pfp = !opt.see.pfp;
+ opt.post.pfp = !opt.post.pfp;
/* fallthrough */
case KEY_RESIZE: