Compare commits

...
2 Commits
Author SHA1 Message Date
Daniele Fucini 3bb25669ea calcurse: sync with Google Calendar via calcurse-caldav
Hourly timer started from xmonad, plus UI hooks that sync before load and after exit. calcurse-sync notifies once when the OAuth login expires and restarts the daemon after imports, since it never re-reads its files.
2026-11-12 14:16:36 +01:00
Daniele Fucini 84eec8c855 calcurse: notify whole-day events every morning
The calcurse daemon only alerts timed appointments, so birthdays went unnoticed. A daily 09:00 timer, started from xmonad and persistent across suspend, sends today's events through notify-send.
2026-11-12 11:27:30 +01:00
9 changed files with 100 additions and 0 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Sync the UI's saved changes once it exits (syncing while it runs would change files under it).
[ "$(ps -o args= -p "$PPID")" = calcurse ] && [ "$(ps -o tty= -p "$PPID" | tr -d ' ')" != '?' ] || exit 0
# shellcheck disable=SC2016 # $1 expands in the inner shell
setsid -f sh -c 'while kill -0 "$1" 2>/dev/null; do sleep 1; done; systemctl --user start calcurse-sync.service' \
sh "$PPID" >/dev/null 2>&1
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
# Sync with Google before the UI loads. Non-interactive calls (-Q, -d, -i, calcurse-caldav's own) run this hook
# too, so act only for the UI: a bare "calcurse" with a terminal (the daemon is bare too, but has none).
[ "$(ps -o args= -p "$PPID")" = calcurse ] && [ "$(ps -o tty= -p "$PPID" | tr -d ' ')" != '?' ] || exit 0
calcurse-sync --now 2>&1 | systemd-cat -t calcurse-sync
@@ -0,0 +1,6 @@
[Unit]
Description=Sync calcurse with Google Calendar
[Service]
Type=oneshot
ExecStart=%h/.local/bin/calcurse-sync
+7
View File
@@ -0,0 +1,7 @@
[Unit]
Description=Sync calcurse with Google Calendar hourly
[Timer]
OnActiveSec=1min
OnCalendar=hourly
Persistent=true
@@ -0,0 +1,7 @@
[Unit]
Description=Notify today's calcurse events
After=calcurse-sync.service
[Service]
Type=oneshot
ExecStart=%h/.local/bin/calcurse-today
@@ -0,0 +1,6 @@
[Unit]
Description=Notify today's calcurse events every morning
[Timer]
OnCalendar=*-*-* 09:00
Persistent=true
+2
View File
@@ -358,6 +358,8 @@ myStartupHook = do
spawnOnce "nm-applet" spawnOnce "nm-applet"
spawnOnce "arch-audit-gtk" spawnOnce "arch-audit-gtk"
spawnOnce "calcurse --daemon" spawnOnce "calcurse --daemon"
spawnOnce "systemctl --user start calcurse-today.timer"
spawnOnce "systemctl --user start calcurse-sync.timer"
spawnOnce "systemctl --user start dunst" spawnOnce "systemctl --user start dunst"
spawnOnce "lxpolkit" spawnOnce "lxpolkit"
spawnOnce "numlockx" spawnOnce "numlockx"
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# Sync calcurse with Google Calendar via calcurse-caldav; notify once when the weekly OAuth login expires.
# --now: called from the UI's pre-load hook (skip the UI check, shorter timeout, no daemon restart).
set -uo pipefail
data=${XDG_DATA_HOME:-$HOME/.local/share}/calcurse
state=${XDG_STATE_HOME:-$HOME/.local/state}/calcurse-sync
expired_flag=$state/login-expired
now=false
[[ ${1:-} == --now ]] && now=true
# The UI keeps the data in memory, so syncing under it would lose changes; its post-save hook syncs after it exits.
ui_running() {
# shellcheck disable=SC2009 # pgrep can't match "has any terminal"
ps -C calcurse -o tty= | grep -qv '?'
}
if ! $now && ui_running; then
echo "calcurse UI is running, skipping"
exit 0
fi
mkdir -p "$state"
# BROWSER=true: an expired login must not open browser tabs on its own.
out=$(BROWSER=true timeout "$($now && echo 15 || echo 60)" calcurse-caldav 2>&1)
rc=$?
printf '%s\n' "$out"
if ((rc != 0)); then
if grep -q 'Access token is missing or refresh token is expired' <<<"$out"; then
if [[ ! -e $expired_flag ]]; then
notify-send -a calcurse -u critical -i x-office-calendar "Google Calendar login expired" \
"Run calcurse-caldav in a terminal, allow access in the browser, then calcurse-caldav --authcode '<code>'."
touch "$expired_flag"
fi
elif grep -q 'Another synchronization instance' <<<"$out"; then
exit 0
fi
exit "$rc"
fi
rm -f "$expired_flag"
# A running daemon never re-reads the data files: restart it so imported appointments get alerts.
if ! $now && ! grep -q '^0 items imported, 0 items removed locally' <<<"$out"; then
pidfile=$data/.daemon.pid
if [[ -s $pidfile ]] && pid=$(<"$pidfile") && kill "$pid" 2>/dev/null; then
timeout 5 tail --pid="$pid" -f /dev/null
# Own scope, so the daemon outlives this oneshot service.
systemd-run --user --scope -q calcurse --daemon
echo "restarted calcurse daemon"
fi
fi
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
# Notify today's whole-day calcurse events (the daemon only alerts timed appointments).
set -euo pipefail
events=$(calcurse -Q --filter-type event,recur-event --days 1 \
--format-event '%m\n' --format-recur-event '%m\n' | tail -n +2)
[[ -n $events ]] || exit 0
notify-send -a calcurse -i x-office-calendar "Today" "$events"