#!/usr/bin/env bash # rice-display - monitor state for the eww panel, and the actions it runs. # # rice-display info JSON: output, modes, refresh rates, DDC brightness # rice-display brightness <0-100> # rice-display mode switch resolution (reverts in 15s unless you keep it) # rice-display rate switch refresh rate at the current resolution set -uo pipefail run="${XDG_RUNTIME_DIR:-/tmp}/rice" modes_tsv="$run/modes.tsv" rates_tsv="$run/rates.tsv" bright_cache="$run/brightness" mkdir -p "$run" say() { rice-notify send "$1" "${2:-}" -i video-display; } primary() { xrandr --query | awk '/ connected primary/ { print $1; exit } / connected/ && !first { first = $1 } END { if (first) print first }' | head -n1 } # "WxHratecurrent(1/0)" for every mode of the output, xrandr order. mode_lines() { xrandr --query | awk -v out="$1" ' $1 == out { inblock = 1; next } inblock && /^[^ ]/ { exit } inblock { mode = $1 for (i = 2; i <= NF; i++) { r = $i; cur = (r ~ /\*/) ? 1 : 0; gsub(/[*+]/, "", r) if (r != "") printf "%s\t%s\t%d\n", mode, r, cur } }' } # DDC is slow (up to a second per read), so reads are cached for a minute. brightness() { if [[ -f $bright_cache ]] && (( $(date +%s) - $(stat -c %Y "$bright_cache") < 60 )); then cat "$bright_cache"; return fi local v v=$(ddcutil getvcp 10 --brief 2>/dev/null | awk '$1 == "VCP" { print $4; exit }') [[ -n $v ]] && echo "$v" | tee "$bright_cache" } info() { local out cur_mode cur_rate out=$(primary) mode_lines "$out" > "$run/all-modes.tsv" IFS=$'\t' read -r cur_mode cur_rate _ < <(awk -F'\t' '$3 == 1' "$run/all-modes.tsv") awk -F'\t' '!seen[$1]++ { print $1 }' "$run/all-modes.tsv" | head -n 6 | awk -v cur="$cur_mode" '{ printf "%d\t%s\t%d\n", NR - 1, $1, ($1 == cur) }' > "$modes_tsv" # One entry per rounded rate (monitors list 120.00, 119.98 and 119.88), keeping the active one. awk -F'\t' -v cur="$cur_mode" '$1 == cur { printf "%d\t%s\t%d\n", n++, $2, $3 }' "$run/all-modes.tsv" | sort -t$'\t' -k3,3nr -k2,2nr | awk -F'\t' '!seen[int($2 + 0.5)]++' | sort -t$'\t' -k2,2nr | head -n 5 > "$rates_tsv" local b ddc=false if command -v ddcutil >/dev/null; then b=$(brightness) [[ -n $b ]] && ddc=true fi jq -cn --arg output "$out" --arg mode "$cur_mode" --arg rate "$cur_rate" \ --argjson ddc "$ddc" --argjson brightness "${b:-0}" \ --argjson bar "$(pgrep -x polybar >/dev/null && echo true || echo false)" \ --argjson modes "$(jq -R -s -c 'split("\n") | map(select(length > 0) | split("\t") | {id: (.[0] | tonumber), mode: .[1], current: (.[2] == "1")})' "$modes_tsv")" \ --argjson rates "$(jq -R -s -c 'split("\n") | map(select(length > 0) | split("\t") | {id: (.[0] | tonumber), label: ((.[1] | tonumber | round | tostring) + "Hz"), current: (.[2] == "1")})' "$rates_tsv")" \ '{output: $output, mode: $mode, rate: $rate, ddc: $ddc, brightness: $brightness, bar: $bar, modes: $modes, rates: $rates, ddcHint: "Needs ddcutil and the i2c-dev module"}' } # Apply an xrandr change, then revert unless it is confirmed within 15 seconds. apply_with_revert() { local out="$1" old_mode="$2" old_rate="$3" new_mode="$4" new_rate="$5" xrandr --output "$out" --mode "$new_mode" --rate "$new_rate" || { say "Display" "That mode did not apply"; return 1; } local keep keep=$(printf 'Keep %s @ %sHz\nRevert\n' "$new_mode" "$new_rate" | timeout 15 rofi -dmenu -theme "$HOME/.config/rofi/menu.rasi" -p "Display" -mesg "Reverting in 15 seconds") || keep="" if [[ $keep != Keep* ]]; then xrandr --output "$out" --mode "$old_mode" --rate "$old_rate" say "Display" "Reverted to $old_mode @ ${old_rate}Hz" fi } case "${1:-}" in info) info ;; brightness) [[ ${2:-} =~ ^[0-9]+$ ]] && (( $2 <= 100 )) || { echo "rice-display: brightness 0-100" >&2; exit 2; } echo "$2" > "$bright_cache" # Sliders fire many values; only the newest one talks to the monitor. pkill -u "$UID" -f 'ddcutil setvcp 10' 2>/dev/null setsid -f ddcutil setvcp 10 "$2" --noverify >/dev/null 2>&1 ;; mode|rate) [[ ${2:-} =~ ^[0-9]+$ ]] || { echo "rice-display: $1 takes an id" >&2; exit 2; } out=$(primary) IFS=$'\t' read -r old_mode old_rate _ < <(mode_lines "$out" | awk -F'\t' '$3 == 1') if [[ $1 == mode ]]; then new_mode=$(awk -F'\t' -v id="$2" '$1 == id { print $2 }' "$modes_tsv") new_rate=$(mode_lines "$out" | awk -F'\t' -v m="$new_mode" '$1 == m { print $2 }' | sort -nr | head -n1) else new_mode="$old_mode" new_rate=$(awk -F'\t' -v id="$2" '$1 == id { print $2 }' "$rates_tsv") fi [[ -n $new_mode && -n $new_rate ]] || exit 1 rice-panel close apply_with_revert "$out" "$old_mode" "$old_rate" "$new_mode" "$new_rate" ;; *) sed -n '2,8p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; esac