Last active
June 23, 2026 13:10
-
-
Save NeoTheFox/cf0ee713a7c108931c8df6d58b9487fc to your computer and use it in GitHub Desktop.
hdr auto-toggle script for Sway
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # hdr-toggle — enable HDR on a sway output for the duration of a command | |
| # | |
| # Turns HDR on for an HDR-capable output, runs the wrapped command, then | |
| # restores the output's previous HDR state | |
| set -euo pipefail | |
| usage() { | |
| cat <<'EOF' | |
| Usage: hdr-toggle [--] <command> [args...] | |
| Enable HDR on a sway output, run <command>, then restore the prior HDR state | |
| once it exits. | |
| Output selection (in order of precedence): | |
| 1. HDR_OUTPUT — output name; must be HDR-capable. | |
| 2. WAYLANDDRV_PRIMARY_MONITOR — Wine's primary monitor; used if HDR-capable, | |
| otherwise ignored with a warning. | |
| 3. The first HDR-capable output. | |
| HDR_OUTPUT=DP-2 hdr-toggle <command> [args...] | |
| List output names with: swaymsg -t get_outputs -r | jq -r '.[].name' | |
| Environment: | |
| HDR_OUTPUT Force the target output by name (overrides everything below). | |
| HDR_DELAY Seconds to wait after enabling HDR before running the command, | |
| for monitors slow to switch (e.g. HDR_DELAY=2). Accepts decimals. | |
| Options: | |
| -h, --help Show this help and exit. | |
| Examples: | |
| hdr-toggle gamescope -- mygame | |
| HDR_OUTPUT=DP-1 hdr-toggle steam -applaunch 123456 | |
| HDR_DELAY=2 hdr-toggle mygame | |
| EOF | |
| } | |
| case "${1-}" in | |
| -h | --help) | |
| usage | |
| exit 0 | |
| ;; | |
| "") | |
| usage >&2 | |
| exit 1 | |
| ;; | |
| --) | |
| shift | |
| ;; | |
| esac | |
| if [[ $# -eq 0 ]]; then | |
| echo "hdr-toggle: no command given" >&2 | |
| usage >&2 | |
| exit 1 | |
| fi | |
| command -v swaymsg >/dev/null 2>&1 || { | |
| echo "hdr-toggle: swaymsg not found (is sway installed?)" >&2 | |
| exit 1 | |
| } | |
| command -v jq >/dev/null 2>&1 || { | |
| echo "hdr-toggle: jq not found" >&2 | |
| exit 1 | |
| } | |
| if [[ -n "${HDR_DELAY-}" ]] && ! [[ "$HDR_DELAY" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then | |
| echo "hdr-toggle: HDR_DELAY must be a non-negative number of seconds" >&2 | |
| exit 1 | |
| fi | |
| outputs=$(swaymsg -t get_outputs -r 2>/dev/null) || { | |
| echo "hdr-toggle: cannot reach sway IPC (is sway running? is SWAYSOCK set?)" >&2 | |
| exit 1 | |
| } | |
| # Returns an output's .features.hdr value ("true"/"false"), or "" if not found. | |
| output_hdr_cap() { | |
| jq -r --arg o "$1" '.[] | select(.name==$o) | .features.hdr' <<<"$outputs" | |
| } | |
| # Select the target output by precedence: | |
| # 1. HDR_OUTPUT — explicit; hard error if missing/not capable. | |
| # 2. WAYLANDDRV_PRIMARY_MONITOR — hint; warn and fall back if missing/not capable. | |
| # 3. first HDR-capable output. | |
| OUTPUT="" | |
| if [[ -n "${HDR_OUTPUT-}" ]]; then | |
| cap=$(output_hdr_cap "$HDR_OUTPUT") | |
| if [[ -z "$cap" ]]; then | |
| echo "hdr-toggle: output '$HDR_OUTPUT' not found" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$cap" != "true" ]]; then | |
| echo "hdr-toggle: output '$HDR_OUTPUT' is not HDR-capable" >&2 | |
| exit 1 | |
| fi | |
| OUTPUT="$HDR_OUTPUT" | |
| elif [[ -n "${WAYLANDDRV_PRIMARY_MONITOR-}" ]]; then | |
| cap=$(output_hdr_cap "$WAYLANDDRV_PRIMARY_MONITOR") | |
| if [[ "$cap" == "true" ]]; then | |
| OUTPUT="$WAYLANDDRV_PRIMARY_MONITOR" | |
| elif [[ -z "$cap" ]]; then | |
| echo "hdr-toggle: WAYLANDDRV_PRIMARY_MONITOR='$WAYLANDDRV_PRIMARY_MONITOR' not found; using first HDR-capable output" >&2 | |
| else | |
| echo "hdr-toggle: WAYLANDDRV_PRIMARY_MONITOR='$WAYLANDDRV_PRIMARY_MONITOR' is not HDR-capable; using first HDR-capable output" >&2 | |
| fi | |
| fi | |
| if [[ -z "$OUTPUT" ]]; then | |
| OUTPUT=$(jq -r '[.[] | select(.features.hdr==true)][0].name // empty' <<<"$outputs") | |
| if [[ -z "$OUTPUT" ]]; then | |
| echo "hdr-toggle: no HDR-capable output found" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| PRIOR=$(jq -r --arg o "$OUTPUT" '.[] | select(.name==$o) | .hdr' <<<"$outputs") | |
| restored=0 | |
| # shellcheck disable=SC2329 # invoked indirectly via trap | |
| restore_hdr() { | |
| [[ $restored -eq 1 ]] && return | |
| restored=1 | |
| if [[ "$PRIOR" == "true" ]]; then | |
| swaymsg output "$OUTPUT" hdr on >/dev/null | |
| else | |
| swaymsg output "$OUTPUT" hdr off >/dev/null | |
| fi | |
| } | |
| trap restore_hdr EXIT INT TERM | |
| # Enable HDR (no-op if it was already on). | |
| if [[ "$PRIOR" != "true" ]]; then | |
| swaymsg output "$OUTPUT" hdr on >/dev/null | |
| # Give a slow monitor time to switch modes before launching the command. | |
| [[ -n "${HDR_DELAY-}" ]] && sleep "$HDR_DELAY" | |
| fi | |
| # Run the wrapped command, preserving its exit code. The trap restores HDR. | |
| set +e | |
| "$@" | |
| rc=$? | |
| set -e | |
| exit "$rc" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment