xkcdlock/xkcdlock

364 lines
9.6 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# xkcdlock v1
#
# A wrapper around screen lockers to display xkcd images on the lock screen
#
# Depending on the given arguments xkcdlock shows a random image, the latest
# image or an image of choice.
#
# Note that 'latest' needs an internet connection for the download!
# If the download fails it falls back to a specific hard-coded image
#
# Builtin is a function to download all images.
#
# Dependencies: rbash, i3lock/swaylock, xrandr, awk, curl, recode,
# convert (from imagick)
#
# © 2016 Björn Busse (see also: LICENSE)
# bbusse@baerlin.eu
#
# Thanks to Randall Patrick Munroe! BTC 1FhCLQK2ZXtCUQDtG98p6fVH7S6mxAsEey
# Thanks to Michael Stapelberg for an awesome window manager - i3
#
# TODO:
# - Make sure we do not exceed screen boundaries in x and y
# - Improve tooltip text border overflow prevention
# wrt display resolution (and font size?)
# - Add support for other screen lockers
# - Parallelize downloads
[[ "$TRACE" ]] && set -x
set -eo pipefail
readonly SCRIPT_NAME=$(basename $0)
# screensaver executable
LOCK_BIN="swaylock"
# verbosity
VERBOSE=0
# "latest" or "random"
IMG_CHOICE="random"
# path to images
IMG_PATH="${HOME}/Pictures/xkcd"
# default / fallback image
IMG_DEFAULT="not_really_into_pokemon.png"
# background colour
BG_COLOUR="white"
# Break tooltip every x char to prevent border overflow
TOOLTIP_MAX_LINE_LEN=100
FONT="xkcd Script"
FONT_FILE="xkcd-script.ttf"
FONT_PATH="${HOME}/.local/share/fonts/${FONT_FILE}"
URL_FONT="https://github.com/ipython/xkcd-font/raw/master/xkcd-script/font/xkcd-script.ttf"
declare -a DEPS=("xrandr" "awk" "curl" "convert" "recode" "sed")
declare -a DEPS_LOCKER=("i3lock" "swaylock")
DOWNLOAD_DISCLAIMER="\nThe downloaded images will end up in your current working directory.\n\
Since we are using restricted bash, we can not change path.\n\
Use '-y' instead of '-d' to really start the download to the current working directory.\n"
log() {
if (( 1=="${VERBOSE}" )); then
echo "$@" >&2
fi
logger -p user.notice -t $SCRIPT_NAME "$@"
}
error() {
echo "$@" >&2
logger -p user.error -t $SCRIPT_NAME "$@"
}
# does not work with restricted bash (bash -r/rbash)
get_script_path() {
echo $(dirname $(readlink -f $0))
}
show_help() {
printf "\n xkcd_lock v1\n\n\
Available options are:\n\n \
-d download images to current working directory\n \
-h show this help\n \
-i show specific image file - overrides '-m'\n \
-l lock program: one of i3lock/swaylock\n \
-m latest|random default: random\n \
-v be verbose\n\n"
}
check_dependencies() {
for i in "${DEPS[@]}"
do
if [[ -z $(which "${i}") ]]; then
error "Could not find ${i}"
exit 1
fi
done
if fc-list -q "${FONT}"; then
log "Font exists"
else
log "Downloading font"
curl -o "${HOME}/${FONT_PATH}" -sLO "${URL_FONT}"
fi
}
xkcd_get_latest_image() {
log "Looking for latest image"
local img_url=$(curl -s https://xkcd.com/index.html | \
awk '/Image URL \(for hotlinking\/embedding\): / {print $5}' | \
awk 'BEGIN{FS="<";} {print $1}')
if [[ -z $img_url ]]; then
error "Can not download latest image, using fallback image"
local img_fn="${IMG_DEFAULT}"
else
log "Downloading: $img_url"
curl -sO --max-time 6 "$img_url"
if [[ 0="$?" ]]; then
local img_fn=$(echo "$img_url" | awk 'BEGIN{FS="/";} {print $5}')
else
error "Can not download latest image, using fallback image"
local img_fn="${IMG_DEFAULT}"
fi
fi
echo "$img_fn"
}
xkcd_get_img_name() {
local fn=$(echo "$img_url_hotlink" | awk 'BEGIN{FS="/";} {print $5}')
IFS='.' read -r -a array <<< "${fn}"
fn="${array[0]}"_"${i}"."${array[-1]}"
echo "$fn"
}
xkcd_get_img_name_from_file() {
IFS='/' read -r -a array <<< "$img_fn"
IFS='.' read -r -a array <<< "${array[-1]}"
IFS='_' read -r -a array <<< "${array[0]}"
local fn=${array[-1]}
echo "$fn"
}
xkcd_get_img_nr() {
IFS='.' read -r -a array <<< "$img_name"
IFS='_' read -r -a array <<< "${array[0]}"
echo "${array[-1]}"
}
xkcd_get_all_images() {
VERBOSE="1"
log "Looking for latest image"
local nimg_latest=$(curl -s https://xkcd.com/index.html | \
awk '/Permanent link to this comic: / {print $6}' | \
awk 'BEGIN{FS="/";} {print $4}')
if [[ -z "$nimg_latest" ]]; then
VERBOSE=1
error "Failed to find latest image number, not downloading anything"
exit 1
else
log "Found: $nimg_latest"
fi
for ((i=1; i<=$nimg_latest; i++)); do
local img_url_hotlink=$(xkcd_get_hotlink_url $i)
local img_name=$(xkcd_get_img_name $img_url_hotlink $i)
if [[ -e "$img_name" ]]; then
log "$img_name exists. Skipping download"
continue
fi
log "Downloading #${i} ${img_url_hotlink} (${img_name})"
$(curl -s $img_url_hotlink -o $img_name)
if (( 0 != "$?" )); then
error "Failed to download ${i}"
fi
if [ "${img_name: -3}" == "jpg" ]; then
convert_image $img_name
fi
done
echo 0
}
xkcd_get_hotlink_url() {
local url="https://xkcd.com/$i"
local url_hotlink="$(curl -sL $url | awk '/Image URL \(for hotlinking\/embedding\): / {print $5}')"
echo $url_hotlink
}
xkcd_get_img_tooltip() {
local url="https://xkcd.com/$1"
echo $(curl -sL $url | grep -A 1 '<div id=\"comic\">' | awk -F "\"" '/src=/ {print $4}' | recode html..UTF8)
}
xkcd_format_tooltip() {
local text=$(echo $text | fold -s -w $TOOLTIP_MAX_LINE_LEN)
echo "$text"
}
get_nscreens() {
local nscreens=$(xrandr -q | awk '/ connected/ {count++} END {print count}')
echo "$nscreens"
}
screen_get_smallest_resolution() {
local res=$(xrandr -q | awk '/*/ {print $1}' \
| awk 'BEGIN{FS="x";} NR==1 || $1<min {line=$0; min=$1}; END {print line}')
echo "$res"
}
screen_get_highest_resolution() {
local res=$(xrandr -q | awk '/\*/ {print $1}' \
| awk 'BEGIN{FS="x";} NR==1 || $1>max {line=$0; max=$1}; END {print line}')
echo "$res"
}
get_random_image() {
local img_fn="$(find $IMG_PATH -type f | sort -R | head -n1 )"
if ! [[ -e "$img_fn" ]]; then
error "Could not find image to display"
fi
echo "$img_fn"
}
convert_image() {
local img_name_len=$((${#img_name} - 4))
local img_name_png=${img_name:0:$img_name_len}".png"
log "Converting $img_name to $img_name_png"
$(convert $img_name $img_name_png)
$(rm -f $img_name)
}
image_add_text() {
local tmp_file=$(mktemp)
log "Adding \""${2}"\" to ${1} and saving image to ${tmp_file}"
$(convert "${1}" -font "${5}" -gravity "${3}" -pointsize "${7}" -fill "${4}" $8 -annotate "${6}" "${2}" "${tmp_file}")
if ! [[ -e "$tmp_file" ]]; then
error "Could not find image with text overlay"
exit 1
fi
echo "$tmp_file"
}
resize_image() {
local tmp_file=$(mktemp)
log "Resizing $img_fn to $res and saving image to $tmp_file"
convert -adaptive-resize $res $img_fn $tmp_file
if ! [[ -e "$tmp_file" ]]; then
error "Could not find resized image"
exit 1
fi
echo "$tmp_file"
}
center_image() {
local tmp_file=$(mktemp)
log "Centering $tmp_file and saving image to $tmp_file"
$(convert $tmp_file_r -gravity center -background $BG_COLOUR -extent $res $tmp_file)
if ! [[ -e "$tmp_file" ]]; then
error "Could not find centered image"
exit 1
fi
echo "$tmp_file"
}
prepare_image() {
local res=$(screen_get_highest_resolution)
local img_name=$(xkcd_get_img_name_from_file $img_fn)
local img_nr=$(xkcd_get_img_nr $img_name)
local text=$(xkcd_get_img_tooltip $img_nr)
img_tooltip=$(xkcd_format_tooltip $text)
local tmp_file_r=$(resize_image $img_fn $res)
local tmp_file_c=$(center_image $tmp_file_r $res)
local tmp_file_t1=$(image_add_text "${tmp_file_c}" "${img_nr}" "Northeast" "green" "${FONT_PATH}" "+50+50" "50")
local tmp_file_t2=$(image_add_text "${tmp_file_t1}" "${img_tooltip}" "Southwest" "red" "${FONT_PATH}" "+0+0" "30" " -undercolor '#00000080' ")
echo "$tmp_file_t2"
}
screen_lock() {
local img_fn_final=$(prepare_image $img_fn)
log "Locking screen with $img_fn"
$LOCK_BIN -i $img_fn_final
}
main() {
local locker=$(which $LOCK_BIN)
local OPTIND
while getopts "h?i:l:vm:dy" opt; do
case "$opt" in
d)
printf "%s${DOWNLOAD_DISCLAIMER}"
printf "%s\nCurrent working directory: ${PWD}\n\n"
exit 0
;;
h|\?)
show_help
exit 0
;;
i)
local img_fn="${OPTARG}"
local r=$(screen_lock $img_fn)
exit 0
;;
l)
if [[ "sway"="${OPTARG}" ]]; then
LOCK_BIN="swaylock"
echo "Not yet implemented since we have no xrandr with wayland"
exit 1
fi
;;
m)
IMG_CHOICE="${OPTARG}"
;;
v)
VERBOSE=1
;;
y)
r=$(xkcd_get_all_images)
exit 0
esac
done
check_dependencies
if ! [[ -d "$IMG_PATH" ]]; then
error "Image directory does not exist: $IMG_PATH"
exit 1
fi
case "$IMG_CHOICE" in
"latest") local img_fn=$(xkcd_get_latest_image);;
"random") local img_fn=$(get_random_image);;
esac
screen_lock $img_fn
}
main "$@"