#!/bin/sh
# alloy-dim — warn that the lock is coming, by taking the backlight down.
#
# The sway config claimed "dim to lock at 5 minutes" for as long as it has
# existed and nothing dimmed: the screen went from full brightness straight to
# swaylock at 300 seconds. So the lock always arrived as an ambush, and the only
# way to prevent one was to have been touching the keyboard at the moment it
# fired.
#
# This is the thirty seconds of warning. swayidle calls `down` at 270 and `up` on
# the next input, so moving the mouse cancels a lock that has not happened yet.
# It is also the closest thing to a grace period available: upstream swaylock has
# no --grace (only the swaylock-effects fork does), and adopting a fork for one
# flag is a poor trade for a distro that prefers canonical packages.
#
# Nothing here may fail an idle chain. A non-zero exit from a swayidle timeout
# action is not the end of the world, but a machine with no backlight at all — a
# desktop, or the server profile — must not spend every five minutes reporting
# that it cannot dim a screen it does not have. So absence exits 0.
#
# Why sysfs and not brightnessctl: the image ships no brightness CLI. SwayOSD
# raises brightness by writing /sys/class/backlight/<dev>/brightness directly,
# and the Containerfile puts a udev rule on udev's search path that chgrps that
# file to `video` and adds group write. The installer puts the account in `video`
# for exactly this reason. So a session process can write it, and that is the
# whole mechanism the Fn keys already use.

set -eu

# The verb is checked before anything else, and before the backlight is looked
# for, so that a typo is an error on every machine rather than only on the ones
# with a panel. Ordered the other way — which is how this was first written — a
# misspelled verb exits 0 on a desktop, because the no-backlight case returns
# before the argument is ever examined. The Containerfile asserts the config and
# this script agree, so a typo should not reach here; making it loud anyway costs
# three lines and removes a way for that assertion to be wrong.
case "${1:-}" in
down | up) ;;
*)
    echo "usage: alloy-dim down|up" >&2
    exit 2
    ;;
esac

# The first backlight this session can actually write. Iterating rather than
# hardcoding intel_backlight: the name is the driver's, so it differs between
# Intel, AMD and a DDC-driven external panel, and a machine can carry more than
# one. Writability is the test that matters, because an unwritable device means
# the udev rule did not apply and dimming would fail anyway.
device=""
for candidate in /sys/class/backlight/*/; do
    [ -w "${candidate}brightness" ] || continue
    device="${candidate%/}"
    break
done

# No panel, or no group write on it. Either way there is nothing to dim and
# saying so once per idle period would be noise, not information.
[ -n "$device" ] || exit 0

# Where the pre-dim level is remembered. In the runtime directory because it is
# tmpfs cleared at logout: a saved level that outlived the session would restore
# a brightness from yesterday, and there is no cleanup path that would catch it.
# Keyed by device so two panels cannot overwrite each other's saved value.
state="${XDG_RUNTIME_DIR:-/tmp}/alloy-dim.$(basename "$device")"

case "${1:-}" in
down)
    # Already dimmed. Without this guard a second `down` would save the dimmed
    # value as the level to restore, and the screen would never come back up —
    # which is the one failure here that looks like broken hardware.
    [ -e "$state" ] && exit 0

    current=$(cat "$device/brightness")
    printf '%s\n' "$current" >"$state"

    # A fifth of current, floored at 1. Not zero: zero is a dark panel, which is
    # indistinguishable from the display power-off that follows at ten minutes,
    # and this has to read as a warning rather than as the screen having gone.
    # A fraction rather than a fixed value because the starting point is the
    # user's own choice and a fixed target would brighten a dim screen.
    dimmed=$((current / 5))
    [ "$dimmed" -lt 1 ] && dimmed=1
    printf '%s\n' "$dimmed" >"$device/brightness"
    ;;
up)
    # Nothing saved means this session did not dim, so there is nothing to put
    # back. Restoring unconditionally would invent a brightness on any resume
    # that followed something other than a dim.
    [ -e "$state" ] || exit 0

    printf '%s\n' "$(cat "$state")" >"$device/brightness"
    rm -f "$state"
    ;;
esac
