diff --git a/.local/bin/i3hwlock b/.local/bin/i3hwlock new file mode 100755 index 0000000..69c22ca --- /dev/null +++ b/.local/bin/i3hwlock @@ -0,0 +1,109 @@ +#!/bin/bash + +# i3hwlock +# +# Allows locking i3 by the presence of a file. This file is intended to be +# created using udev rules so that you can trigger the lock when you remove +# a device such as a YubiKey. Example rule below. +# +# /etc/udev/rules.d/99-i3hwlock-yubikey.rules: +# ACTION=="add", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="1050", RUN+="/usr/bin/rm /tmp/i3hwlock.trigger" +# ACTION=="remove", SUBSYSTEM=="input", ENV{ID_VENDOR_ID}=="1050", RUN+="/usr/bin/touch /tmp/i3hwlock.trigger" + +LOCK_TIMEOUT=3 # How many seconds to wait before locking. +DMENU_FILE="/tmp/i3hwlock" +TRIGGER_FILE="$DMENU_FILE.trigger" +ABORT_FILE="$DMENU_FILE.abort" +CONTINUE_FILE="$DMENU_FILE.continue" +LOCKING_FILE="$DMENU_FILE.locking" + +function check_prerequisites() { + [ -e "$ABORT_FILE" ] && rm "$ABORT_FILE" + + if ! which inotifywait >/dev/null; then + echo "ERROR: inotifywait not found." 1>&2 + return 1 + fi +} + +function dmenu_selection() { + selection="$(xargs -n 1 <<<"Continue Abort" | dmenu -i -p "The screen is about to lock")" + [ -z "$selection" ] && return + file="$DMENU_FILE.${selection,,}" + echo "INFO: Touching $file" + touch "$file" +} + +function abort_lock() { + [ -e "$ABORT_FILE" ] && rm "$ABORT_FILE" + + if ! [ -f "$LOCKING_FILE" ]; then + echo "WARN: Abort initiated without file '$LOCKING_FILE'." 1>&2 + return 0 + fi + + echo "INFO: Aborting lock." + killall dmenu + notify-send "Lock aborted." & + [ -e $LOCKING_FILE ] && rm "$LOCKING_FILE" +} + +function lock() { + [ -e "$CONTINUE_FILE" ] && rm "$CONTINUE_FILE" + + if ! [ -f "$LOCKING_FILE" ]; then + echo "WARN: Won't lock as file '$LOCKING_FILE' is missing." 1>&2 + return 0 + fi + + echo "INFO: Locking now." + killall dmenu + i3exit lock 2>/dev/null || i3lock --color=29414f --nofork + [ -e $LOCKING_FILE ] && rm "$LOCKING_FILE" +} + +function delayed_lock() { + echo "INFO: Delayed lock initiated." + touch "$DMENU_FILE.locking" + + for i in $(seq $LOCK_TIMEOUT); do + notify-send "Locking in $((LOCK_TIMEOUT - i + 1))" -t 1000 & + sleep 1 + + if [ -e "$ABORT_FILE" ]; then + abort_lock + return 0 + elif [ -e "$CONTINUE_FILE" ]; then + break + fi + done + + ! [ -e $LOCKING_FILE ] && return 0 + lock +} + +function listen() { + while read -r directory action file; do + if [[ "$file" == "${TRIGGER_FILE##*/}" ]]; then + if [[ "$action" == "CLOSE_WRITE,CLOSE" ]] && ! [ -e "$LOCKING_FILE" ]; then + $0 dmenu-selection & + $0 delayed-lock & + elif [[ "$action" == "DELETE" ]]; then + abort_lock + fi + fi + done < <(inotifywait -m "${TRIGGER_FILE%/*}") +} + +check_prerequisites || exit 1 + +if [ -z "$1" ]; then + listen || exit 2 +elif [[ "$1" == delayed-lock ]]; then + delayed_lock || exit 3 +elif [[ "$1" == dmenu-selection ]]; then + dmenu_selection || exit 4 +else + echo "ERROR: Unrecognized action: '$1'." 1>&2 + exit 127 +fi