#!/bin/sh # Dim or restore every backlight device present, including any external monitor # that ddcci has registered. DDC/CI writes take about a second each, so the # monitors are driven concurrently rather than in sequence. DIM_LEVEL=5 RESTORE_TIMEOUT=60 STATE="${XDG_STATE_HOME:-$HOME/.local/state}/backlight-idle" LOG="$STATE/log" mkdir -p "$STATE" 2>/dev/null log() { printf '%s %s\n' "$(date '+%F %T.%3N')" "$*" >>"$LOG" 2>/dev/null; } is_num() { case "$1" in ''|*[!0-9]*) return 1 ;; *) return 0 ;; esac; } # actual_brightness is a live DDC read, so it reports what the panel really has. # brightness is only the driver's cache, which accepts a write even when the bus # is down and then reverts to the panel's value seconds later. panel_level() { cat "/sys/class/backlight/$1/actual_brightness" 2>/dev/null; } dim_dev() { dev=$1 cur=$(cat "/sys/class/backlight/$dev/brightness" 2>/dev/null) is_num "$cur" || { log " dim $dev skipped, unreadable"; return; } # Re-dimming would overwrite the saved level with the dimmed one. [ "$cur" -le "$DIM_LEVEL" ] && { log " dim $dev skipped, already dim"; return; } level=$(panel_level "$dev") is_num "$level" || level=$cur printf '%s\n' "$level" >"$STATE/$dev" || { log " dim $dev skipped, cannot save"; return; } brightnessctl -d "$dev" set "$DIM_LEVEL" >/dev/null 2>&1 log " dim $dev rc=$? saved=$level" } # The panel is read back after every write because a write issued while DDC is # down is silently kept in the driver cache and never reaches the monitor. restore_dev() { dev=$1 target=$(cat "$STATE/$dev" 2>/dev/null) is_num "$target" || { log " restore $dev skipped, no saved level"; return; } deadline=$(($(date +%s) + RESTORE_TIMEOUT)) tries=0 while :; do brightnessctl -d "$dev" set "$target" >/dev/null 2>&1 tries=$((tries + 1)) actual=$(panel_level "$dev") [ "$actual" = "$target" ] && { log " restore $dev ok target=$target tries=$tries"; return; } [ "$(date +%s)" -ge "$deadline" ] && { log " restore $dev FAILED target=$target actual=${actual:-unreadable} tries=$tries"; return; } sleep 2 done } case "$1" in dim|restore) ;; *) echo "usage: ${0##*/} {dim|restore}" >&2; exit 1 ;; esac log "$1 begin" for path in /sys/class/backlight/*; do [ -e "$path" ] || continue dev=${path##*/} case "$1" in dim) dim_dev "$dev" & ;; restore) restore_dev "$dev" & ;; esac done wait log "$1 end"