diff options
Diffstat (limited to 'colitur-x.c')
| -rw-r--r-- | colitur-x.c | 106 |
1 files changed, 95 insertions, 11 deletions
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); |
