aboutsummaryrefslogtreecommitdiff
path: root/colitur-x.c
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-21 09:58:05 +0200
committerLukasz Kasprzak <lukasz@arcofasiagroup.com>2026-08-21 09:58:05 +0200
commit5312537c9fc12eed4d14d89d9cc1a226f2df94c3 (patch)
treec05b34d3cc1fb35b399aa9f2ecdabc4bb7138178 /colitur-x.c
parent147932ae2da02b5eceb6be6e1d704e1199bcb72e (diff)
downloadcolitur-x-5312537c9fc12eed4d14d89d9cc1a226f2df94c3.tar.gz
colitur-x-5312537c9fc12eed4d14d89d9cc1a226f2df94c3.zip
fix(colitur-x): make the selection and the I-class marker legible
Both markers were drawn in gold, which works on a dark cell and very nearly disappears on a white one -- and most days are white. The selection is now ringed twice, dark outside and bright inside, so it reads against a white cell and a red one without knowing which it is. The I-class marker is a badge rather than coloured text: filled with the cell's own foreground and lettered in its background, so it inverts against whatever the day's liturgical colour is. It also opens on TODAY when the input covers it, which is what a viewer should do; colitur's kernel still never reads a clock. Drawing now goes to an off-screen pixmap and CX_DUMP writes one frame out, so the result can be checked without screenshotting a window whose contents X does not retain while it is obscured.
Diffstat (limited to 'colitur-x.c')
-rw-r--r--colitur-x.c106
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);