Support external monitor brightness in Quattro (#6490)
* 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>
This commit is contained in:
co-authored by
David Heinemeier Hansson
parent
fa3d28ca61
commit
c0d4037237
@@ -1,28 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Show or adjust brightness on the most likely display device.
|
||||
# omarchy:args=[--no-osd] [+N%|N%-|N%|off|on]
|
||||
# omarchy:examples=omarchy brightness display | omarchy brightness display +5% | omarchy brightness display --no-osd 50% | omarchy brightness display off | omarchy brightness display on
|
||||
# omarchy:summary=Show or adjust brightness on the focused display.
|
||||
# omarchy:args=[--no-osd] [--monitor name] [+N%|N%-|N%|off|on]
|
||||
# omarchy:examples=omarchy brightness display | omarchy brightness display +5% | omarchy brightness display --monitor DP-1 50% | omarchy brightness display off | omarchy brightness display on
|
||||
|
||||
no_osd=0
|
||||
if [[ ${1:-} == "--no-osd" ]]; then
|
||||
no_osd=1
|
||||
shift
|
||||
fi
|
||||
monitor=""
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--no-osd)
|
||||
no_osd=1
|
||||
shift
|
||||
;;
|
||||
--monitor)
|
||||
(( $# >= 2 )) || exit 1
|
||||
monitor="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Get the brightness of the passed display
|
||||
display_brightness() {
|
||||
backlight_brightness() {
|
||||
brightnessctl -d "$1" -m 2>/dev/null | awk -F, '{ gsub("%", "", $4); print $4; found=1 } END{ exit !found }'
|
||||
}
|
||||
|
||||
[[ -n $monitor ]] || monitor="$(omarchy-hyprland-monitor-focused 2>/dev/null || true)"
|
||||
|
||||
monitor_is_internal() {
|
||||
[[ $monitor =~ ^(eDP|LVDS|DSI)- ]]
|
||||
}
|
||||
|
||||
use_apple_display() {
|
||||
omarchy-hyprland-monitor-focused-apple "$monitor"
|
||||
}
|
||||
|
||||
use_ddc_display() {
|
||||
[[ -n $monitor ]] && ! monitor_is_internal
|
||||
}
|
||||
|
||||
if (( $# == 0 )); then
|
||||
if omarchy-hyprland-monitor-focused-apple; then
|
||||
if use_apple_display; then
|
||||
omarchy-brightness-display-apple
|
||||
exit
|
||||
elif use_ddc_display; then
|
||||
omarchy-brightness-display-ddc "$monitor"
|
||||
exit
|
||||
fi
|
||||
|
||||
device="$(omarchy-hw-display)" || exit 1
|
||||
display_brightness "$device"
|
||||
backlight_brightness "$device"
|
||||
exit
|
||||
fi
|
||||
|
||||
@@ -45,18 +76,21 @@ fi
|
||||
exec {lock_fd}>"${XDG_RUNTIME_DIR:-/tmp}/omarchy-brightness-display.lock"
|
||||
flock -n "$lock_fd" || exit 0
|
||||
|
||||
if omarchy-hyprland-monitor-focused-apple; then
|
||||
if use_apple_display; then
|
||||
if (( no_osd )); then
|
||||
omarchy-brightness-display-apple --no-osd "$step"
|
||||
else
|
||||
omarchy-brightness-display-apple "$step"
|
||||
fi
|
||||
elif use_ddc_display; then
|
||||
brightness="$(omarchy-brightness-display-ddc "$monitor" "$step")" || exit 1
|
||||
(( no_osd )) || omarchy-osd -i brightness -p "$brightness"
|
||||
else
|
||||
# Current device highlighted
|
||||
device="$(omarchy-hw-display)" || exit 1
|
||||
|
||||
# Current brightness percentage
|
||||
current=$(display_brightness "$device") || exit 1
|
||||
current=$(backlight_brightness "$device") || exit 1
|
||||
|
||||
# Apply non-uniform step size: 1% steps if at or below 5%, otherwise set an
|
||||
# absolute target percentage to avoid raw backlight rounding causing uneven OSD steps.
|
||||
@@ -84,5 +118,5 @@ else
|
||||
brightnessctl -d "$device" set "$step" >/dev/null
|
||||
|
||||
# Show the new brightness in OSD
|
||||
(( no_osd )) || omarchy-osd -i brightness -p "$(display_brightness "$device")"
|
||||
(( no_osd )) || omarchy-osd -i brightness -p "$(backlight_brightness "$device")"
|
||||
fi
|
||||
|
||||
Executable
+168
@@ -0,0 +1,168 @@
|
||||
#!/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"
|
||||
@@ -1,5 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Return success if the focused Hyprland monitor is an Apple display.
|
||||
# omarchy:summary=Return success if the focused or named Hyprland monitor is an Apple display.
|
||||
# omarchy:args=[monitor]
|
||||
|
||||
hyprctl monitors -j | jq -e '.[] | select(.focused == true) | select(.make == "Apple Computer Inc" and (.model | test("StudioDisplay|ProDisplayXDR|Studio XDR")))' >/dev/null
|
||||
monitor="${1:-}"
|
||||
|
||||
hyprctl monitors -j | jq -e --arg monitor "$monitor" '
|
||||
.[]
|
||||
| select(if $monitor == "" then .focused == true else .name == $monitor end)
|
||||
| select(.make == "Apple Computer Inc" and (.model | test("StudioDisplay|ProDisplayXDR|Studio XDR")))
|
||||
' >/dev/null
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
# omarchy:summary=Print monitor panel state for the shell
|
||||
# omarchy:group=monitor
|
||||
|
||||
{ omarchy-brightness-display 2>/dev/null; echo; } | head -n 1
|
||||
|
||||
monitors_json=$(hyprctl monitors all -j)
|
||||
focused_monitor=$(printf '%s\n' "$monitors_json" | jq -r '[.[] | select(.focused == true)][0].name // ""')
|
||||
|
||||
{ omarchy-brightness-display --monitor "$focused_monitor" 2>/dev/null; echo; } | head -n 1
|
||||
|
||||
printf '%s\n' "$monitors_json" | jq -r '
|
||||
def internal: test("^(eDP|LVDS|DSI)-");
|
||||
@@ -15,7 +16,7 @@ printf '%s\n' "$monitors_json" | jq -r '
|
||||
([.[] | select((.name | internal) and .mirrorOf != "none")][0].mirrorOf // "")
|
||||
'
|
||||
|
||||
omarchy-hyprland-monitor-focused 2>/dev/null || echo
|
||||
printf '%s\n' "$focused_monitor"
|
||||
omarchy-hyprland-monitor-scaling 2>/dev/null || echo
|
||||
|
||||
printf '%s\n' "$monitors_json" | jq -c \
|
||||
|
||||
Reference in New Issue
Block a user