1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
#!/bin/bash
# Sample khal's upcoming events into a cache file, for widgets/calendar.lua.
#
# Conky's Lua has no way to run a process, and shelling out to khal every two
# seconds would fork a Python interpreter per draw. khal takes ~200ms to start,
# which is two orders of magnitude more than the whole frame budget.
set -u
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/udt"
CACHE="$CACHE_DIR/calendar.txt"
umask 077
mkdir -p "$CACHE_DIR"
# Without khal the list below fails silently (its stderr is discarded), and the
# cache would claim an empty week. Removing the cache instead makes the card
# show its "no calendar data" face, which is the truth.
if ! command -v khal >/dev/null 2>&1; then
rm -f "$CACHE"
exit 1
fi
TMP="$CACHE.tmp.$$"
trap 'rm -f "$TMP"' EXIT
# {start-date-long}, not {start-date}: the latter renders with khal's
# `dateformat`, which here is '%d.%m.' with no year at all, so an event in
# January sorts before one in December of the year before and the widget cannot
# tell which is which. The long form uses `longdateformat` and carries the
# year. Both are the user's own format strings and khal offers no ISO override
# (a strftime spec inside the field, '{start-date:%Y-%m-%d}', raises), so the
# widget parses the three numbers out by position rather than assuming an
# order.
#
# --day-format '' suppresses the 'Today, 18.09.2026' header lines khal
# interleaves between days; without it every group of events is preceded by a
# line that does not parse as a record.
#
# Fields are pipe-separated: a title may contain anything except a newline, so
# the separator has to be a character no date, time or calendar name uses, and
# the parser splits on the first four only, leaving the title intact even if it
# contains a pipe itself.
#
# 8d, not 7d: khal's range is exclusive of the final day in practice for
# all-day events, and one extra day costs nothing but avoids a birthday
# vanishing from the card on the morning it matters.
EVENTS=$(khal list --day-format '' \
--format '{start-date-long}|{start-time}|{end-time}|{calendar}|{title}' \
--notstarted today 8d 2>/dev/null)
# khal's exit status is 0 even with no calendars configured, so emptiness is
# not distinguishable from failure by status alone. The widget treats a missing
# 'generated' key as "the sampler never ran" and an empty event list as "no
# events", which are genuinely different states and read differently on the
# card.
{
echo "generated $(date +%s)"
date +'today %Y-%m-%d'
# Calendar colours, as khal itself declares them. Parsed from khal's config
# rather than hardcoded, so adding a calendar does not mean editing Lua.
# Only the [[name]] / color = value pairs inside [calendars]; the section
# test stops at the next top-level [section] so a 'color' key elsewhere in
# the config cannot be misread as a calendar's.
awk '
/^\[calendars\]/ { in_cal = 1; next }
/^\[[^[]/ { in_cal = 0 }
in_cal && /^\[\[/ {
name = $0
gsub(/^\[\[|\]\]$/, "", name)
next
}
in_cal && /^[ \t]*color[ \t]*=/ {
sub(/^[^=]*=[ \t]*/, "")
if (name != "") print "color " name " " $0
}
' "${XDG_CONFIG_HOME:-$HOME/.config}/khal/config" 2>/dev/null
if [ -n "$EVENTS" ]; then
printf '%s\n' "$EVENTS" | sed 's/^/event /'
fi
} > "$TMP"
mv -f "$TMP" "$CACHE"
|