Skip to content

Instantly share code, notes, and snippets.

@mensinda
Created November 26, 2023 22:13
Show Gist options
  • Save mensinda/94d0b05604bbb50f9b5bd4a5f1b48bfb to your computer and use it in GitHub Desktop.
Save mensinda/94d0b05604bbb50f9b5bd4a5f1b48bfb to your computer and use it in GitHub Desktop.
wlroots lock screen manager
#!/bin/bash
set -e
SELF="$(readlink -f "$0")"
### _____ _____ _ _ ______ _____ _____
### / __ \ _ | \ | || ___|_ _| __ \
### | / \/ | | | \| || |_ | | | | \/
### | | | | | | . ` || _| | | | | __
### | \__/\ \_/ / |\ || | _| |_| |_\ \
### \____/\___/\_| \_/\_| \___/ \____/
###
SIMULATION_MODE=0
# The FIFO / named pipe used for IPC between the lockmgr and swayidle
FIFO="/tmp/wlroots_lockmgr"
# Common swaylock arguments.
LOCK_ARGS="--show-failed-attempts --hide-keyboard-layout --indicator-caps-lock"
# Set **ONE** of the following variables to set the background image / color of the lock screen:
#
# LOCK_COLOR="#000000"
# LOCK_IMG="/path/to/your/lock/wallpaper"
# LOCK_IMG_DIR="/path/to/a/dir/from/which/to/pick/a/random/wallpaper"
#
# Black screen by default
LOCK_COLOR="#000000"
# swayidle configuration with some changes:
# - the timeout can now be relative to the last timeout
# - the command is now comma (`,`) seperated list of commands to execute. Commands in this
# context are not shell commands, but functions inside this script. All command functions
# have a `cmd_` prefix.
#
# New functions can be added by implementing new `cmd_<command name>` functions.
SWAYIDLE_COMMANDS=(
"timeout 60 mgr_reset,notify"
"timeout +5 lock resume grace_unlock"
"timeout +3 grace_end"
"timeout +30 dpms_off resume dpms_on"
"before-sleep lock"
"lock lock"
"unlock unlock"
)
### _____ ________ ______ ___ ___ _ _______ _____
### / __ \ _ | \/ || \/ | / _ \ | \ | | _ \/ ___|
### | / \/ | | | . . || . . |/ /_\ \| \| | | | |\ `--.
### | | | | | | |\/| || |\/| || _ || . ` | | | | `--. \
### | \__/\ \_/ / | | || | | || | | || |\ | |/ / /\__/ /
### \____/\___/\_| |_/\_| |_/\_| |_/\_| \_/___/ \____/
###
# This will store the swayidle PID in the future
CHILD_PID=-1
ALLOW_GRACE_UNLOCK=0
cmd_mgr_reset() {
msg1 "Starting lock chain"
ALLOW_GRACE_UNLOCK=1
}
cmd_notify() {
msg2 "Notifying the user"
notify-send "Sleepy..." "I am about to lock your screen because of inactivity. Please do something if you don't want that to happen!"
}
cmd_lock() {
if pidof swaylock > /dev/null; then
msg2 "Screen is already locked --> Doing nothing"
return 0
fi
msg2 "Locking the screen"
extra_args=""
[ -n "$LOCK_COLOR" ] && extra_args="-c '$LOCK_COLOR'"
[ -n "$LOCK_IMG" ] && extra_args="-i '$LOCK_IMG'"
[ -n "$LOCK_IMG_DIR" ] && extra_args="-i '$LOCK_IMG_DIR/$(ls "$LOCK_IMG_DIR" | sort -R | tail -n 1)'"
# `--daemonize` should always be passed unless you feel like
# messing around with the IPC logic if this script :)
if (( SIMULATION_MODE == 1 )); then
msg3 "swaylock --daemonize $LOCK_ARGS $extra_args"
else
swaylock --daemonize $LOCK_ARGS $extra_args
fi
}
# Workaround helper for https://github.com/swaywm/swayidle/issues/54
_disable_grace_delayed() {
sleep 2
$SELF grace_end
}
cmd_manual_lock() {
msg1 "Manually lock triggered"
sleep .5
msg2 "Setting swayidle status to idle"
if ps -p $CHILD_PID &> /dev/null; then
kill -SIGUSR1 $CHILD_PID
fi
_disable_grace_delayed &
}
cmd_grace_unlock() {
if (( ALLOW_GRACE_UNLOCK == 1 )); then
msg2 "Unlocking because activity within the grace period"
if pidof swaylock > /dev/null; then
killall -SIGUSR1 swaylock
fi
else
msg2 "The grace period has passed --> No automatic unlock"
fi
}
cmd_grace_end() {
msg2 "The grace period ends now!"
ALLOW_GRACE_UNLOCK=0
}
cmd_dpms_off() {
msg2 "DPMS off"
if (( SIMULATION_MODE == 1 )); then
return 0
fi
case ${XDG_CURRENT_DESKTOP:-sway} in
Hyprland|hyprland)
hyprctl dispatch dpms off
;;
sway|Sway)
swaymsg "output * dpms off"
;;
*)
error "DPMS control for $XDG_CURRENT_DESKTOP is not yet supported!"
;;
esac
}
cmd_dpms_on() {
msg2 "DPMS on"
if (( SIMULATION_MODE == 1 )); then
return 0
fi
case ${XDG_CURRENT_DESKTOP:-sway} in
Hyprland|hyprland)
hyprctl dispatch dpms on
;;
sway|Sway)
swaymsg "output * dpms on"
;;
*)
error "DPMS control for $XDG_CURRENT_DESKTOP is not yet supported!"
;;
esac
}
# Could be used to manually terminate the lockmgr
cmd_exit() {
msg1 "Exit requested --> terminating"
exit 0
}
### _ _____ _____
### | | | _ | __ \
### | | | | | | | \/
### | | | | | | | __
### | |___\ \_/ / |_\ \
### \_____/\___/ \____/
###
msg1() {
echo -e "\x1b[1;35m[\x1b[0m$(date '+%F %T')\x1b[1;35m]\x1b[0m \x1b[1;32m=>\x1b[0m $*"
}
msg2() {
echo -e "\x1b[1;35m[\x1b[0m$(date '+%F %T')\x1b[1;35m]\x1b[0m \x1b[1;34m - \x1b[0m $*"
}
msg3() {
echo -e "\x1b[1;35m[\x1b[0m$(date '+%F %T')\x1b[1;35m]\x1b[0m \x1b[1;36m -- \x1b[0m $*"
}
warn() {
echo -e "\x1b[1;35m[\x1b[0m$(date '+%F %T')\x1b[1;35m]\x1b[0m \x1b[1;33mWARN:\x1b[0m $*"
}
error() {
echo -e "\x1b[1;35m[\x1b[0m$(date '+%F %T')\x1b[1;35m]\x1b[0m \x1b[1;31mERROR:\x1b[0m $*"
}
### ___ ___ ___ _____ _ _
### | \/ | / _ \|_ _| \ | |
### | . . |/ /_\ \ | | | \| |
### | |\/| || _ | | | | . ` |
### | | | || | | |_| |_| |\ |
### \_| |_/\_| |_/\___/\_| \_/
###
cleanup() {
msg1 "Begin cleanup"
if [ -e $FIFO ]; then
msg2 "Deleting named pipe $FIFO"
rm -f $FIFO
fi
if ps -p $CHILD_PID &> /dev/null; then
msg2 "Terminating swayidle child process $CHILD_PID"
kill -SIGTERM $CHILD_PID
fi
msg2 "Cleanup DONE"
}
add_time() {
t=$1
[[ ${t:0:1} == '+' || ${t:0:1} == '-' ]] && (( t = $last_time ${t:0:1} ${t:1} ))
last_time=$t
swayidle_cmd+=($t)
}
dispatch_main() {
if [ ! -p $FIFO ]; then
warn "FIFO $FIFO does not exist. The manager does not seem to be running. Doing nothing."
exit 1
fi
echo $1 > $FIFO
}
manager_main() {
if [ -e $FIFO ]; then
cat <<EOF
FIFO $FIFO already exists.
Only one instance of $0 may run at a time. However,
the existence of $FIFO indicates that another
instance is already running.
Please manually delete $FIFO, if you are sure that
the other instance of $0 has exited / crashed.
EOF
exit 1
fi
msg1 "Startup"
# Set the cleanup exit hook
trap cleanup EXIT
# Prepare FIFO
msg2 "Creating named pipe $FIFO"
mkfifo $FIFO
# Building swayidle command
msg2 "Building swayidle command:"
swayidle_cmd=(swayidle -w)
last_time=0
for i in "${SWAYIDLE_COMMANDS[@]}"; do
c=($i)
swayidle_cmd+=(${c[0]})
case "${c[0]}" in
timeout)
add_time ${c[1]}
swayidle_cmd+=("$SELF '${c[2]}'")
if [[ "${c[3]}" == 'resume' ]]; then
swayidle_cmd+=(resume)
swayidle_cmd+=("$SELF '${c[4]}'")
fi
;;
before-sleep|after-resume|lock|unlock)
swayidle_cmd+=("$SELF '${c[1]}'")
;;
idlehint)
add_time ${c[1]}
;;
*)
error "Unknown command '${c[0]}'"
exit 1
;;
esac
done
# Start swayidle
"${swayidle_cmd[@]}" &
CHILD_PID=$!
msg2 "Started swayidle: PID=$CHILD_PID"
# Main loop
msg2 "Starting main loop"
while true; do
# Read from the FIFO with a timeout
read -st 1 cmd <> $FIFO || cmd=''
# Check if the FIFO was deleted
if [ ! -p $FIFO ]; then
warn "FIFO $FIFO was deleted. --> Exiting"
exit 1
fi
# Empty command / timeout?
[ -z "$cmd" ] && continue
# Commands may be chained via `,`
for i in ${cmd//,/ }; do
# Exec command if known
if [[ "$(LC_ALL=C type -t "cmd_$i")" == 'function' ]]; then
cmd_$i
else
warn "Unknown command '$i' --> ignoring"
fi
done
done
}
if [ -n "$1" ]; then
dispatch_main "$1"
else
manager_main
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment