#!/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"
