aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rwxr-xr-xcolitur-xbin17968 -> 27024 bytes
-rw-r--r--colitur-x.c106
-rw-r--r--config.h14
4 files changed, 115 insertions, 12 deletions
diff --git a/README.md b/README.md
index 9df7b8e..6530ec9 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,13 @@ liturgical colour, and shows the selected day's readings underneath.
Keys: arrows or `hjkl` move, `n`/`p` change month, `q` quits.
+It opens on **today** when the input covers it. Reading the clock is a
+presentation choice and belongs in a viewer: colitur's own kernel is pure
+and deliberately never asks what day it is, which is what lets it be tested
+to the year 9999.
+
+ CX_DUMP=/tmp/f.ppm colitur-x < day.csv # write one frame, then exit
+
It reads CSV on **stdin** and nothing else, so the shell decides what it
shows. One month:
diff --git a/colitur-x b/colitur-x
index b5db32a..a49ed72 100755
--- a/colitur-x
+++ b/colitur-x
Binary files differ
diff --git a/colitur-x.c b/colitur-x.c
index 06f2f1a..03e2793 100644
--- a/colitur-x.c
+++ b/colitur-x.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <time.h>
#include "config.h"
@@ -29,6 +30,9 @@ static int cur; /* selected row */
static Display *dpy;
static Window win;
+static Pixmap buf; /* draw off-screen, then blit */
+static GC gc;
+static int bufw, bufh;
static int scr;
static XftFont *fnt, *fnt_big;
static XftDraw *xd;
@@ -80,6 +84,21 @@ static void load(void)
}
}
+/* Start on TODAY if the input covers it. Reading the clock is a
+ presentation choice and belongs here: colitur's own kernel is pure and
+ deliberately never asks what day it is, which is what makes it testable
+ to the year 9999. A viewer has no such duty. */
+static void select_today(void)
+{
+ time_t t = time(NULL);
+ struct tm tm;
+ if (!localtime_r(&t, &tm)) return;
+ char iso[11];
+ strftime(iso, sizeof iso, "%Y-%m-%d", &tm);
+ for (int i = 0; i < nrows; i++)
+ if (!strcmp(rows[i].f[0], iso)) { cur = i; return; }
+}
+
/* --- colour helpers ----------------------------------------------------- */
static void alloc(const char *spec, XftColor *c)
{
@@ -109,6 +128,17 @@ static void text(XftColor *c, XftFont *f, int x, int y, const char *s, int maxw)
XftDrawStringUtf8(xd, c, f, x, y, (FcChar8 *)s, len);
}
+/* An n-pixel ring just inside (x,y,w,h). */
+static void ring(XftColor *c, int x, int y, int w, int h, int n)
+{
+ for (int k = 0; k < n; k++) {
+ XftDrawRect(xd, c, x + k, y + k, w - 2*k, 1);
+ XftDrawRect(xd, c, x + k, y + h - 1 - k, w - 2*k, 1);
+ XftDrawRect(xd, c, x + k, y + k, 1, h - 2*k);
+ XftDrawRect(xd, c, x + w - 1 - k, y + k, 1, h - 2*k);
+ }
+}
+
static void fill(const char *spec, int x, int y, int w, int h)
{
XftColor c; alloc(spec, &c);
@@ -130,15 +160,48 @@ static int dow_of(int i)
return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7;
}
+/* CX_DUMP=path writes one frame as a PPM and exits. It reads back the
+ off-screen PIXMAP, not the window: a window's contents are not retained
+ while it is obscured or on an unfocused tag, so screenshotting one can
+ return solid black while the program draws perfectly. */
+static void dump_and_exit(const char *path, int w, int h)
+{
+ XImage *im = XGetImage(dpy, buf, 0, 0, w, h, AllPlanes, ZPixmap);
+ FILE *f = im ? fopen(path, "wb") : NULL;
+ if (!f) { fprintf(stderr, "CX_DUMP: cannot write %s\n", path); exit(1); }
+ fprintf(f, "P6\n%d %d\n255\n", w, h);
+ for (int y = 0; y < h; y++)
+ for (int x = 0; x < w; x++) {
+ unsigned long px = XGetPixel(im, x, y);
+ unsigned char rgb[3] = { (unsigned char)((px >> 16) & 0xff),
+ (unsigned char)((px >> 8) & 0xff),
+ (unsigned char)(px & 0xff) };
+ fwrite(rgb, 1, 3, f);
+ }
+ fclose(f);
+ XDestroyImage(im);
+ fprintf(stderr, "CX_DUMP: wrote %s (%dx%d)\n", path, w, h);
+ exit(0);
+}
+
static void redraw(void)
{
XWindowAttributes wa;
XGetWindowAttributes(dpy, win, &wa);
+ if (!buf || wa.width != bufw || wa.height != bufh) {
+ if (xd) { XftDrawDestroy(xd); xd = NULL; }
+ if (buf) { XFreePixmap(dpy, buf); }
+ buf = XCreatePixmap(dpy, win, wa.width, wa.height,
+ DefaultDepth(dpy, DefaultScreen(dpy)));
+ xd = XftDrawCreate(dpy, buf, vis, cmap);
+ bufw = wa.width; bufh = wa.height;
+ }
fill(col_bg, 0, 0, wa.width, wa.height);
- XftColor head, detail, rank1, grid;
+ XftColor head, detail, sel_dark, sel_light, grid;
alloc(col_head, &head); alloc(col_detail, &detail);
- alloc(col_rank1, &rank1); alloc(col_grid, &grid);
+ alloc(col_sel_dark, &sel_dark); alloc(col_sel_light, &sel_light);
+ alloc(col_grid, &grid);
int mon = month_of(cur);
char title[128];
@@ -162,17 +225,30 @@ static void redraw(void)
XftColor bg, fg;
colours_for(rows[i].f[10], &bg, &fg);
XftDrawRect(xd, &bg, x, y, CELL_W - 2, CELL_H - 2);
- if (i == cur)
- for (int k = 0; k < 3; k++)
- XftDrawRect(xd, &rank1, x+k, y+k, CELL_W-2-2*k, 1),
- XftDrawRect(xd, &rank1, x+k, y+CELL_H-3-k, CELL_W-2-2*k, 1),
- XftDrawRect(xd, &rank1, x+k, y+k, 1, CELL_H-2-2*k),
- XftDrawRect(xd, &rank1, x+CELL_W-3-k, y+k, 1, CELL_H-2-2*k);
+ if (i == cur) {
+ /* dark ring outside, bright ring inside: readable on a white
+ cell and on a red one without knowing which it is */
+ ring(&sel_dark, x, y, CELL_W - 2, CELL_H - 2, SEL_RING);
+ ring(&sel_light, x + SEL_RING, y + SEL_RING,
+ CELL_W - 2 - 2*SEL_RING, CELL_H - 2 - 2*SEL_RING, SEL_RING);
+ }
char d[8]; snprintf(d, sizeof d, "%d", dom_of(i));
text(&fg, fnt_big, x + 6, y + 20, d, 40);
- if (!strcmp(rows[i].f[8], "class-1"))
- text(&rank1, fnt, x + CELL_W - 26, y + 18, "I", 20);
+ if (!strcmp(rows[i].f[8], "class-1")) {
+ /* badge: filled with the cell's FOREGROUND, glyph in its
+ BACKGROUND, so it inverts against any liturgical colour */
+ int gw = 0, bw2, bx, by = y + 5, bh2 = 17;
+ XGlyphInfo gi;
+ XftTextExtentsUtf8(dpy, fnt, (FcChar8 *)rank1_glyph,
+ strlen(rank1_glyph), &gi);
+ gw = gi.xOff;
+ bw2 = gw + 10;
+ bx = x + CELL_W - 4 - bw2;
+ XftDrawRect(xd, &fg, bx, by, bw2, bh2);
+ XftDrawStringUtf8(xd, &bg, fnt, bx + 5, by + 13,
+ (FcChar8 *)rank1_glyph, strlen(rank1_glyph));
+ }
/* Feast name over up to three lines, breaking on spaces that fit
the cell. Measured with XftTextExtents rather than guessed at a
@@ -213,12 +289,19 @@ static void redraw(void)
text(&detail, fnt, PAD, dy + 62, line, wa.width - 2*PAD);
snprintf(line, sizeof line, "Evangelium %s", rows[cur].f[14]);
text(&detail, fnt, PAD, dy + 80, line, wa.width - 2*PAD);
+
+ XCopyArea(dpy, buf, win, gc, 0, 0, wa.width, wa.height, 0, 0);
+ {
+ const char *p = getenv("CX_DUMP");
+ if (p) dump_and_exit(p, wa.width, wa.height);
+ }
}
int main(void)
{
load();
if (!nrows) { fputs("colitur-x: no rows on stdin\n", stderr); return 2; }
+ select_today();
if (!(dpy = XOpenDisplay(NULL))) { fputs("cannot open display\n", stderr); return 1; }
scr = DefaultScreen(dpy);
@@ -244,7 +327,8 @@ int main(void)
fnt = XftFontOpenName(dpy, scr, font);
fnt_big = XftFontOpenName(dpy, scr, font_big);
- xd = XftDrawCreate(dpy, win, vis, cmap);
+ gc = XCreateGC(dpy, win, 0, NULL);
+ /* xd is created against the off-screen pixmap in redraw() */
for (XEvent ev;;) {
XNextEvent(dpy, &ev);
diff --git a/config.h b/config.h
index 5b242b4..6fe5857 100644
--- a/config.h
+++ b/config.h
@@ -18,6 +18,18 @@ static const char *col_bg = "#141414"; /* window background */
static const char *col_grid = "#3a3a3a"; /* cell borders */
static const char *col_head = "#8a8a8a"; /* weekday headings */
static const char *col_detail = "#e8e8e8"; /* detail pane text */
-static const char *col_rank1 = "#ffcc55"; /* I-class marker */
+/* The selected day is ringed TWICE: a dark ring outside a bright one, so
+ the marker survives on a white cell and on a red one alike. A single
+ bright ring vanishes against white; a single dark one vanishes against
+ black. */
+static const char *col_sel_dark = "#1a1a1a";
+static const char *col_sel_light = "#ffd24a";
+enum { SEL_RING = 3 }; /* px per ring */
+
+/* A I-class day is marked with a BADGE, not coloured text: the badge is
+ filled with the cell's own foreground and the glyph drawn in its
+ background, so it inverts against whatever the day's colour is. Gold on
+ white -- the first attempt -- was very nearly invisible. */
+static const char *rank1_glyph = "I";
enum { CELL_W = 122, CELL_H = 74, PAD = 10, HEAD_H = 46, DETAIL_H = 92 };