#!/usr/bin/env bash # panama-brightness enumerates monitors from sysfs and speaks DDC/CI to them. # # The parts worth pinning down are the ones that decide whether a slider appears # at all, and whether it appears attached to the right screen: # # * only connectors with something plugged in are probed, because probing an # empty bus costs a timeout each and there are fourteen of them here; # * a panel that cannot report brightness is omitted rather than shown as a # control that does nothing; # * the connector name matches what Hyprland calls the output, since the UI # joins on it to get the monitor's description; # * no I2C access produces the udev command that fixes it, not "no displays". # # Runs entirely against fixtures. Real monitors are never touched: both the # sysfs root and the device root are redirected, and ddcutil is replaced on PATH. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-brightness" fail() { printf 'brightness helper contract: %s\n' "$1" >&2 exit 1 } fixture="$(mktemp -d /tmp/panama-brightness.XXXXXX)" trap 'rm -rf "$fixture"' EXIT mkdir -p "$fixture/drm" "$fixture/dev" "$fixture/bin" "$fixture/i2c" # Two connectors with a monitor, two without. DP-2 answers DDC; DP-3 is # connected but does not implement brightness. HDMI-A-1 and DP-1 are empty and # must never be probed at all. make_connector() { local name="$1" bus="$2" status="$3" mkdir -p "$fixture/drm/$name" printf '%s\n' "$status" >"$fixture/drm/$name/status" mkdir -p "$fixture/i2c/i2c-$bus" ln -sfn "$fixture/i2c/i2c-$bus" "$fixture/drm/$name/ddc" } make_connector card1-DP-1 4 disconnected make_connector card1-DP-2 5 connected make_connector card1-DP-3 6 connected make_connector card1-HDMI-A-1 7 disconnected # has_accessible_bus only needs one readable/writable node to exist. touch "$fixture/dev/i2c-5" # Stub ddcutil. Records every bus it is asked about so the test can prove the # disconnected ones were skipped. Bus 6 refuses, standing in for a panel without # VCP 0x10. # # Bus 5 reports its brightness out of 200 rather than 100. Most panels do use # 100, which is exactly the problem: with a maximum of 100 the scaling # arithmetic is the identity, so a helper that ignored the reported maximum # entirely would pass every assertion. 200 makes reads and writes that skip the # conversion visibly wrong. cat >"$fixture/bin/ddcutil" <<'STUB' #!/usr/bin/env bash bus="" args=("$@") for ((i = 0; i < ${#args[@]}; i++)); do [[ "${args[$i]}" == "--bus" ]] && bus="${args[$((i + 1))]}" done printf '%s\n' "$bus" >>"$DDCUTIL_PROBE_LOG" for arg in "$@"; do if [[ "$arg" == "setvcp" ]]; then printf 'set %s %s\n' "$bus" "${args[-1]}" >>"$DDCUTIL_SET_LOG" exit 0 fi done case "$bus" in 5) printf 'VCP 10 C 120 200\n'; exit 0 ;; *) exit 1 ;; esac STUB chmod +x "$fixture/bin/ddcutil" export DDCUTIL_PROBE_LOG="$fixture/probes.log" export DDCUTIL_SET_LOG="$fixture/sets.log" : >"$DDCUTIL_PROBE_LOG" : >"$DDCUTIL_SET_LOG" run_helper() { PATH="$fixture/bin:$PATH" \ PANAMA_BRIGHTNESS_DRM_ROOT="$fixture/drm" \ PANAMA_BRIGHTNESS_DEV_ROOT="$fixture/dev" \ "$helper" "$@" } # ── Enumeration ────────────────────────────────────────────────────────────── listing="$(run_helper list)" jq -e . >/dev/null 2>&1 <<<"$listing" || fail "list did not emit JSON: $listing" [[ "$(jq -r '.displays | length' <<<"$listing")" == "1" ]] \ || fail "expected exactly one controllable display, got: $listing" [[ "$(jq -r '.displays[0].connector' <<<"$listing")" == "DP-2" ]] \ || fail "the connector name must match Hyprland's output name: $listing" [[ "$(jq -r '.displays[0].bus' <<<"$listing")" == "5" ]] \ || fail "the display was mapped to the wrong I2C bus: $listing" # 120 of a maximum of 200 is 60%. [[ "$(jq -r '.displays[0].value' <<<"$listing")" == "60" ]] \ || fail "brightness was not read as a percent of the reported maximum: $listing" [[ "$(jq -r '.error' <<<"$listing")" == "" ]] \ || fail "a successful listing must not carry an error: $listing" # A connected panel that cannot report brightness is dropped, not listed. jq -e '.displays | map(.connector) | index("DP-3") == null' >/dev/null <<<"$listing" \ || fail 'a display without VCP 0x10 was listed as controllable' # ── Disconnected connectors are never probed ───────────────────────────────── if grep -qxE '4|7' "$DDCUTIL_PROBE_LOG"; then fail "a disconnected connector was probed -- each empty bus costs a timeout: $(tr '\n' ' ' <"$DDCUTIL_PROBE_LOG")" fi # ── Writes scale to the reported maximum ───────────────────────────────────── run_helper set 5 40 [[ "$(tail -1 "$DDCUTIL_SET_LOG")" == "set 5 80" ]] \ || fail "set did not scale to the display's maximum: $(cat "$DDCUTIL_SET_LOG")" run_helper set 5 500 [[ "$(tail -1 "$DDCUTIL_SET_LOG")" == "set 5 200" ]] \ || fail "an out-of-range percent was not clamped: $(cat "$DDCUTIL_SET_LOG")" # ── No I2C access explains itself ──────────────────────────────────────────── rm -f "$fixture/dev"/i2c-* denied="$(run_helper list)" [[ "$(jq -r '.displays | length' <<<"$denied")" == "0" ]] \ || fail "displays were reported without I2C access: $denied" grep -q 'udevadm' <<<"$(jq -r '.error' <<<"$denied")" \ || fail "the no-access error must name the command that fixes it, got: $(jq -r '.error' <<<"$denied")" printf 'brightness helper contract: PASS\n'