#!/bin/bash ## ## Feed a cloudslate: an old-school slate board that gets what it shows from ## the cloud. Ours is a jailbroken Kindle sitting on a desk. ## ## What goes on the board comes from a script in lib/cloudslateassist/scripts/. ## Each writes one screenful to stdout -- PNG bytes, or plain text to be ## rendered here. Nothing about any particular one lives in this file. ## ## A slate script is handed: ## SLATE_W SLATE_H the board's pixel dimensions ## SLATE_FONT a mono font file that exists on this platform ## SLATE_TZ the slate's timezone, also exported as TZ ## SCRIPT_LIBEXEC its own libexec dir, for helpers it needs ## ## Output is published to S3; the slate polls for it. See ## lib/cloudslateassist/kindle-app/cloudslate.sh for the board end. ## CMD=$(basename $0) TMP=$HOME/.tmp/$CMD ; mkdir -p $TMP TENTEN_DIR=$(dirname $(dirname $(realpath "$0"))) LIBEXEC=$TENTEN_DIR/libexec/$CMD SCRIPTS=$TENTEN_DIR/lib/$CMD/scripts SCRIPTS_LIBEXEC=$LIBEXEC/scripts KINDLE_APP=$TENTEN_DIR/lib/$CMD/kindle-app case $(uname) in Darwin) DATE_CMD=gdate ;; *) DATE_CMD=date ;; esac CONFIG=$HOME/.config/$CMD . $CONFIG/default.config ## The slate sits on one desk and shows that desk's clock, whatever box drew it export SLATE_TZ export TZ=$SLATE_TZ usage() { cmd=$(basename $0) echo "Usage: $cmd -a render -s script [-o out.png] # slate -> png on stdout, or -o" echo "Usage: $cmd -a publish -s script # render a slate and publish it" echo "Usage: $cmd -a publish -f file # publish an image or text file" echo "Usage: $cmd -a install [-F] # install the app onto the device" echo "Usage: $cmd -a scripts # list available slate scripts" echo "Usage: $cmd -a url # print the published slate url" echo "Usage: $cmd -a new-token # generate a fresh slate token" exit 1 } ## ImageMagick needs a real font file, and they live in different places find_font() { [ -n "$SLATE_FONT" ] && { echo "$SLATE_FONT" ; return ; } for f in /usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf \ /usr/share/fonts/TTF/DejaVuSansMono.ttf \ /opt/homebrew/share/fonts/DejaVuSansMono.ttf \ /Library/Fonts/DejaVuSansMono.ttf \ /System/Library/Fonts/Menlo.ttc ; do [ -f "$f" ] && { echo "$f" ; return ; } done echo "$CMD: no mono font found -- set SLATE_FONT in config" >&2 exit 1 } ## Everything addressing the published slate goes through these, so the token ## can never be left out of one path and not the other. ## ## XXX require_token must be called from the action, not from inside a command ## substitution -- an exit there only kills the subshell. require_token() { [ -n "$SLATE_TOKEN" ] && return echo "$CMD: SLATE_TOKEN is unset -- run '$CMD -a new-token' and set it in config" >&2 exit 1 } slate_key() { echo "$S3_PREFIX/$SLATE_TOKEN/current.png" ; } ## sh reads a script incrementally, so replacing one mid-run makes the running ## shell resume at a stale byte offset and execute garbage. ## ## Liveness comes from the heartbeat the app writes while it loops. The log ## cannot answer this: a hard reboot never runs the app's trap, so its last ## line stays "starting" forever. ## ## The app writes the beat on either side of a suspend, so anything fresher ## than a full interval is alive. device_running() { local beat=$1/cloudslate.beat [ -f "$beat" ] || return 1 local age=$(( $(date +%s) - $(cat "$beat" 2>/dev/null || echo 0) )) [ "$age" -lt $(( POLL_SECONDS + 180 )) ] } slate_url() { echo "$PUBLIC_BASE/$(slate_key)" ; } ## ImageMagick guesses format from the filename, which is wrong for a temp ## file, so name it explicitly. Empty means the slate script emitted text. image_format() { local mime=$(file -b --mime-type "$1") case "$mime" in image/*) ;; *) return ;; esac mime=${mime#image/} echo "${mime%%+*}" } ## Any image a script hands us gets fitted to the panel and flattened to grey fit_image() { convert "$3:$1" -resize "${SLATE_W}x${SLATE_H}^" -gravity center \ -extent "${SLATE_W}x${SLATE_H}" -colorspace Gray "$2" } ## Plain text a script hands us gets laid out mono and drawn to fill the panel render_text() { local src=$1 out=$2 draw draw=$(php "$LIBEXEC/render-text.php" "$SLATE_W" "$SLATE_H" < "$src") || return 1 convert -size "${SLATE_W}x${SLATE_H}" xc:white -font "$SLATE_FONT" \ -draw "$draw" "$out" } ## Turn whatever a slate script emitted into a panel-sized png slate_to_png() { local src=$1 out=$2 fmt=$(image_format "$1") if [ -n "$fmt" ] ; then fit_image "$src" "$out" "$fmt" ; else render_text "$src" "$out" ; fi } ## A slate script gets the board's dimensions, a usable font, and the path to ## its own helpers in libexec. It writes one screenful to stdout. run_script() { local name=$1 out=$2 local script=$SCRIPTS/$name.sh [ -x "$script" ] || { echo "$CMD: no slate script '$name'" >&2 ; exit 1 ; } SLATE_W=$SLATE_W SLATE_H=$SLATE_H SLATE_FONT=$SLATE_FONT \ SCRIPT_LIBEXEC=$SCRIPTS_LIBEXEC/$name "$script" > "$out" } publish() { local png=$1 aws --profile=$AWS_PROFILE s3 cp "$png" "$S3_BUCKET/$(slate_key)" \ --content-type image/png --cache-control "max-age=60" > /dev/null } unset action while getopts ":a:s:f:o:Fh" o; do case "$o" in a) action=$OPTARG ;; s) S=$OPTARG ;; f) F=$OPTARG ;; o) O=$OPTARG ;; F) FORCE=yes ;; *|h) usage ;; esac done SLATE_FONT=$(find_font) case "$action" in render) [ -z "$S" ] && usage if [ -z "$O" ] && [ -t 1 ] ; then echo "$CMD: refusing to write a png to the terminal -- use -o, or redirect" >&2 exit 1 fi raw=$TMP/slate.$$.raw png=$TMP/slate.$$.png run_script "$S" "$raw" || exit 1 slate_to_png "$raw" "$png" || exit 1 if [ -n "$O" ] ; then mv "$png" "$O" ; else cat "$png" ; rm -f "$png" ; fi rm -f "$raw" ;; publish) require_token png=$TMP/publish.$$.png if [ -n "$S" ] ; then raw=$TMP/slate.$$.raw run_script "$S" "$raw" || exit 1 slate_to_png "$raw" "$png" || exit 1 rm -f "$raw" elif [ -n "$F" ] ; then slate_to_png "$F" "$png" || exit 1 else usage fi publish "$png" || exit 1 rm -f "$png" ;; install) require_token dev=$(kindleassist -a path) || exit 1 if [ -z "$FORCE" ] && device_running "$dev" ; then echo "$CMD: cloudslate still looks live on the device -- reboot the Kindle" >&2 echo " first, or pass -F to overwrite it anyway" >&2 exit 1 fi conf=$TMP/cloudslate.config.$$ cat > "$conf" </dev/null || head -c 12 /dev/urandom | od -An -tx1 | tr -d ' \n' echo ;; *) usage ;; esac