* Support external monitor brightness Route brightness through the focused Hyprland monitor so internal panels keep using the kernel backlight while compatible external displays use DDC/CI. Preserve the Apple Display backend and leave brightness unavailable when the focused display cannot be controlled. Add cached DDC bus and VCP range handling, install ddcutil for new and existing systems, and cover backend selection and brightness conversion with shell tests. * Harden external brightness caching --------- Co-authored-by: David Heinemeier Hansson <david@hey.com>
169 lines
4.2 KiB
Bash
Executable File
169 lines
4.2 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Show or adjust DDC/CI display brightness for a Hyprland monitor.
|
|
# omarchy:args=<monitor> [+N%|N%-|N%]
|
|
# omarchy:examples=omarchy-brightness-display-ddc DP-1 | omarchy-brightness-display-ddc DP-1 50%
|
|
|
|
monitor="${1:-}"
|
|
step="${2:-}"
|
|
|
|
[[ -n $monitor ]] || exit 1
|
|
|
|
cache_dir="${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display-ddc"
|
|
cache_name="${monitor//[^[:alnum:]_.-]/_}"
|
|
cache_file="$cache_dir/$cache_name.bus"
|
|
unavailable_cache_seconds=60
|
|
range_cache_seconds=10
|
|
|
|
cache_unavailable() {
|
|
mkdir -p "$cache_dir" 2>/dev/null || true
|
|
printf 'unavailable %s\n' "$(date +%s)" >"$cache_file" 2>/dev/null || true
|
|
}
|
|
|
|
detect_bus() {
|
|
ddcutil --skip-ddc-checks detect --brief 2>/dev/null | awk -v monitor="$monitor" '
|
|
/I2C bus:/ {
|
|
bus = $NF
|
|
sub(/^.*\/i2c-/, "", bus)
|
|
}
|
|
|
|
/DRM connector:/ {
|
|
connector = $NF
|
|
sub(/^card[0-9]+-/, "", connector)
|
|
if (connector == monitor && bus != "") {
|
|
print bus
|
|
exit
|
|
}
|
|
bus = ""
|
|
}
|
|
'
|
|
}
|
|
|
|
find_bus() {
|
|
local bus=""
|
|
local cached_value=""
|
|
local now=0
|
|
|
|
if [[ -r $cache_file ]]; then
|
|
read -r bus cached_value <"$cache_file" || true
|
|
fi
|
|
|
|
if [[ $bus == "unavailable" ]]; then
|
|
now=$(date +%s)
|
|
if [[ $cached_value =~ ^[0-9]+$ ]] && (( now - cached_value < unavailable_cache_seconds )); then
|
|
return 1
|
|
fi
|
|
bus=""
|
|
rm -f "$cache_file"
|
|
fi
|
|
|
|
if [[ -z $bus ]]; then
|
|
bus="$(detect_bus)" || return 1
|
|
if [[ ! $bus =~ ^[0-9]+$ ]]; then
|
|
cache_unavailable
|
|
return 1
|
|
fi
|
|
mkdir -p "$cache_dir" 2>/dev/null || true
|
|
printf '%s\n' "$bus" >"$cache_file" 2>/dev/null || true
|
|
fi
|
|
|
|
printf '%s\n' "$bus"
|
|
}
|
|
|
|
read_vcp() {
|
|
local bus="$1"
|
|
|
|
ddcutil --bus "$bus" --skip-ddc-checks getvcp 10 --brief 2>/dev/null | awk '
|
|
$1 == "VCP" && toupper($2) == "10" && $3 == "C" && $4 ~ /^[0-9]+$/ && $5 ~ /^[0-9]+$/ && $5 > 0 {
|
|
print $4, $5
|
|
found = 1
|
|
exit
|
|
}
|
|
|
|
END { exit !found }
|
|
'
|
|
}
|
|
|
|
read_brightness() {
|
|
local bus=""
|
|
local now=0
|
|
local values=""
|
|
|
|
bus="$(find_bus)" || return 1
|
|
values="$(read_vcp "$bus")" || {
|
|
rm -f "$cache_file"
|
|
return 1
|
|
}
|
|
|
|
now=$(date +%s)
|
|
mkdir -p "$cache_dir" 2>/dev/null || true
|
|
printf '%s %s %s\n' "$bus" "${values##* }" "$now" >"$cache_file" 2>/dev/null || true
|
|
printf '%s %s\n' "$bus" "$values"
|
|
}
|
|
|
|
bus=""
|
|
current=""
|
|
maximum=""
|
|
percent=""
|
|
range_cached_at=""
|
|
|
|
if [[ -z $step ]]; then
|
|
read -r bus current maximum < <(read_brightness) || exit 1
|
|
[[ -n ${bus:-} && -n ${current:-} && -n ${maximum:-} ]] || exit 1
|
|
(( percent = (current * 100 + maximum / 2) / maximum ))
|
|
printf '%s\n' "$percent"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ $step =~ ^([0-9]+)%$ ]]; then
|
|
target="${BASH_REMATCH[1]}"
|
|
range_cache_fresh=0
|
|
if [[ -r $cache_file ]]; then
|
|
read -r bus maximum range_cached_at <"$cache_file" || true
|
|
fi
|
|
|
|
if [[ $bus =~ ^[0-9]+$ && $maximum =~ ^[0-9]+$ && $range_cached_at =~ ^[0-9]+$ ]] && (( maximum > 0 )); then
|
|
now=$(date +%s)
|
|
if (( range_cached_at <= now && now - range_cached_at < range_cache_seconds )); then
|
|
range_cache_fresh=1
|
|
fi
|
|
fi
|
|
|
|
if (( ! range_cache_fresh )); then
|
|
read -r bus current maximum < <(read_brightness) || exit 1
|
|
fi
|
|
elif [[ $step =~ ^\+([0-9]+)%$ ]]; then
|
|
read -r bus current maximum < <(read_brightness) || exit 1
|
|
[[ -n ${bus:-} && -n ${current:-} && -n ${maximum:-} ]] || exit 1
|
|
(( percent = (current * 100 + maximum / 2) / maximum ))
|
|
amount="${BASH_REMATCH[1]}"
|
|
if (( amount == 5 && percent < 5 )); then
|
|
(( target = percent + 1 ))
|
|
else
|
|
(( target = percent + amount ))
|
|
fi
|
|
elif [[ $step =~ ^([0-9]+)%-$ ]]; then
|
|
read -r bus current maximum < <(read_brightness) || exit 1
|
|
[[ -n ${bus:-} && -n ${current:-} && -n ${maximum:-} ]] || exit 1
|
|
(( percent = (current * 100 + maximum / 2) / maximum ))
|
|
amount="${BASH_REMATCH[1]}"
|
|
if (( amount == 5 && percent <= 5 )); then
|
|
(( target = percent - 1 ))
|
|
else
|
|
(( target = percent - amount ))
|
|
fi
|
|
else
|
|
exit 1
|
|
fi
|
|
|
|
(( target < 1 )) && target=1
|
|
(( target > 100 )) && target=100
|
|
(( raw_target = (target * maximum + 50) / 100 ))
|
|
|
|
if ! ddcutil --bus "$bus" --skip-ddc-checks --noverify setvcp 10 "$raw_target" >/dev/null 2>&1; then
|
|
rm -f "$cache_file"
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s\n' "$target"
|