#!/usr/bin/env bash set -euo pipefail VCP_INPUT=0x60 CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/monitor-input" CACHE_FILE="$CACHE_DIR/displays" CACHE_TTL=60 # Output one line per DDC-capable display: BUS MODEL MFG (from ddcutil detect, cached for CACHE_TTL seconds). list_ddc_displays() { mkdir -p "$CACHE_DIR" local now ts now=$(date +%s) ts=$(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) if [[ -s "$CACHE_FILE" ]] && (( ts + CACHE_TTL > now )); then cat "$CACHE_FILE" return fi ddcutil detect 2>/dev/null | awk ' /^Display [0-9]+$/ { bus=""; model=""; mfg=""; next } /^Invalid display|^ DDC communication failed/ { next } /^ I2C bus:/ { if (match($0, /i2c-[0-9]+/)) bus=substr($0, RSTART+4, RLENGTH-4); next } /^ Mfg id:[[:space:]]+/ { mfg=substr($0, index($0,":")+1); gsub(/^[[:space:]]+/, "", mfg); next } /^ Model:[[:space:]]+/ { model=substr($0, index($0,":")+1); gsub(/^[[:space:]]+/, "", model); next } /^ VCP version:/ { if (bus != "") print bus "\t" model "\t" mfg } ' | tee "$CACHE_FILE" } # Resolve DISP (bus number or name substring) to bus number. Exits 1 if not found or ambiguous. # When DISP is numeric, use it directly (no slow ddcutil detect). resolve_display() { local disp="$1" if [[ "$disp" =~ ^[0-9]+$ ]]; then echo "$disp" return 0 fi local bus r bus=$(list_ddc_displays | awk -v disp="$disp" ' BEGIN { d = tolower(disp); found = 0 } { if (index(tolower($2), d) || index(tolower($3), d)) { if (found) exit 2 found = $1 } } END { if (found) print found; exit (found ? 0 : 1) } ') r=$? [[ $r -eq 2 ]] && { echo "monitor-input: '$disp' matches multiple displays (use bus number)" >&2; return 1; } [[ $r -eq 0 && -n "$bus" ]] || { echo "monitor-input: no DDC display matching '$disp'" >&2; return 1; } echo "$bus" } # Map input name to VCP 0x60 value (common assignments; use hex for others). input_to_hex() { case "$(echo "$1" | tr '[:upper:]' '[:lower:]')" in dp|dp1) echo "0x0f" ;; dp2) echo "0x10" ;; hdmi|hdmi1) echo "0x11" ;; hdmi2) echo "0x12" ;; 0x*) echo "$1" ;; *) echo "monitor-input: unknown input '$1' (use dp, dp1, dp2, hdmi, hdmi1, hdmi2, or 0xNN)" >&2; return 1 ;; esac } case "${1:-}" in list) echo "Bus Model Mfg" list_ddc_displays ;; *) [[ -n "${1:-}" ]] || { echo "Usage: monitor-input list | monitor-input get|set [input]" >&2; exit 1; } case "${2:-}" in get) bus=$(resolve_display "$1") || exit 1 exec ddcutil --bus "$bus" getvcp "$VCP_INPUT" ;; set) [[ -n "${3:-}" ]] || { echo "Usage: monitor-input set " >&2; exit 1; } bus=$(resolve_display "$1") || exit 1 hex=$(input_to_hex "$3") || exit 1 exec ddcutil --bus "$bus" setvcp "$VCP_INPUT" "$hex" ;; *) echo "Usage: monitor-input list" >&2 echo " monitor-input get" >&2 echo " monitor-input set " >&2 echo " display: bus number (e.g. 23) or name match (e.g. benq, zowie, lg, 34GL750)" >&2 echo " input: dp, dp1, dp2, hdmi, hdmi1, hdmi2, or 0xNN" >&2 exit 1 ;; esac ;; esac