Skip to main content

max / alloy

4.4 KB · 96 lines History Blame Raw
1 #!/bin/sh
2 # alloy-dim — warn that the lock is coming, by taking the backlight down.
3 #
4 # The sway config claimed "dim to lock at 5 minutes" for as long as it has
5 # existed and nothing dimmed: the screen went from full brightness straight to
6 # swaylock at 300 seconds. So the lock always arrived as an ambush, and the only
7 # way to prevent one was to have been touching the keyboard at the moment it
8 # fired.
9 #
10 # This is the thirty seconds of warning. swayidle calls `down` at 270 and `up` on
11 # the next input, so moving the mouse cancels a lock that has not happened yet.
12 # It is also the closest thing to a grace period available: upstream swaylock has
13 # no --grace (only the swaylock-effects fork does), and adopting a fork for one
14 # flag is a poor trade for a distro that prefers canonical packages.
15 #
16 # Nothing here may fail an idle chain. A non-zero exit from a swayidle timeout
17 # action is not the end of the world, but a machine with no backlight at all — a
18 # desktop, or the server profile — must not spend every five minutes reporting
19 # that it cannot dim a screen it does not have. So absence exits 0.
20 #
21 # Why sysfs and not brightnessctl: the image ships no brightness CLI. SwayOSD
22 # raises brightness by writing /sys/class/backlight/<dev>/brightness directly,
23 # and the Containerfile puts a udev rule on udev's search path that chgrps that
24 # file to `video` and adds group write. The installer puts the account in `video`
25 # for exactly this reason. So a session process can write it, and that is the
26 # whole mechanism the Fn keys already use.
27
28 set -eu
29
30 # The verb is checked before anything else, and before the backlight is looked
31 # for, so that a typo is an error on every machine rather than only on the ones
32 # with a panel. Ordered the other way — which is how this was first written — a
33 # misspelled verb exits 0 on a desktop, because the no-backlight case returns
34 # before the argument is ever examined. The Containerfile asserts the config and
35 # this script agree, so a typo should not reach here; making it loud anyway costs
36 # three lines and removes a way for that assertion to be wrong.
37 case "${1:-}" in
38 down | up) ;;
39 *)
40 echo "usage: alloy-dim down|up" >&2
41 exit 2
42 ;;
43 esac
44
45 # The first backlight this session can actually write. Iterating rather than
46 # hardcoding intel_backlight: the name is the driver's, so it differs between
47 # Intel, AMD and a DDC-driven external panel, and a machine can carry more than
48 # one. Writability is the test that matters, because an unwritable device means
49 # the udev rule did not apply and dimming would fail anyway.
50 device=""
51 for candidate in /sys/class/backlight/*/; do
52 [ -w "${candidate}brightness" ] || continue
53 device="${candidate%/}"
54 break
55 done
56
57 # No panel, or no group write on it. Either way there is nothing to dim and
58 # saying so once per idle period would be noise, not information.
59 [ -n "$device" ] || exit 0
60
61 # Where the pre-dim level is remembered. In the runtime directory because it is
62 # tmpfs cleared at logout: a saved level that outlived the session would restore
63 # a brightness from yesterday, and there is no cleanup path that would catch it.
64 # Keyed by device so two panels cannot overwrite each other's saved value.
65 state="${XDG_RUNTIME_DIR:-/tmp}/alloy-dim.$(basename "$device")"
66
67 case "${1:-}" in
68 down)
69 # Already dimmed. Without this guard a second `down` would save the dimmed
70 # value as the level to restore, and the screen would never come back up —
71 # which is the one failure here that looks like broken hardware.
72 [ -e "$state" ] && exit 0
73
74 current=$(cat "$device/brightness")
75 printf '%s\n' "$current" >"$state"
76
77 # A fifth of current, floored at 1. Not zero: zero is a dark panel, which is
78 # indistinguishable from the display power-off that follows at ten minutes,
79 # and this has to read as a warning rather than as the screen having gone.
80 # A fraction rather than a fixed value because the starting point is the
81 # user's own choice and a fixed target would brighten a dim screen.
82 dimmed=$((current / 5))
83 [ "$dimmed" -lt 1 ] && dimmed=1
84 printf '%s\n' "$dimmed" >"$device/brightness"
85 ;;
86 up)
87 # Nothing saved means this session did not dim, so there is nothing to put
88 # back. Restoring unconditionally would invent a brightness on any resume
89 # that followed something other than a dim.
90 [ -e "$state" ] || exit 0
91
92 printf '%s\n' "$(cat "$state")" >"$device/brightness"
93 rm -f "$state"
94 ;;
95 esac
96