Files
dotfiles/bin/executable_term-background

86 lines
2.8 KiB
Bash

#!/bin/sh
# term-background: print "light" or "dark" for the current terminal.
#
# Asks the terminal for its background color with the OSC 11 escape sequence,
# converts the reply to perceived luminance, and prints "light" or "dark".
# Prints "dark" (the safe default) when the terminal does not answer.
#
# All terminal I/O happens on /dev/tty, so this is safe inside $(...).
#
# Works locally and over SSH (the query reaches whichever real terminal draws
# the screen). Inside tmux/screen the multiplexer may answer instead of the
# outer terminal, so detection can be wrong there; override it with
# export TERM_BACKGROUND=light # or dark
# in ~/.environment.local on that machine.
#
# Tune the per-attempt wait via TERM_BACKGROUND_TIMEOUT (tenths of a second)
# and the number of attempts via TERM_BACKGROUND_ATTEMPTS. We poll rather than
# do a single fixed-length read because on a freshly booted WSL instance the
# very first terminal's OSC 11 reply can arrive well after a short window
# closes; if we restored cooked/echo mode by then, the late reply leaks onto
# the screen (and into the shell's input buffer) as literal text. Staying in
# raw/no-echo mode across multiple short attempts lets a slow first reply
# still be captured silently instead of leaking.
# Need a controlling terminal to talk to.
[ -t 0 ] || { echo dark; exit 0; }
exec 3<>/dev/tty || { echo dark; exit 0; }
saved=$(stty -g <&3 2>/dev/null) || { echo dark; exit 0; }
stty raw -echo min 0 time "${TERM_BACKGROUND_TIMEOUT:-4}" <&3 2>/dev/null
printf '\033]11;?\033\\' >&3 # OSC 11 query
answer=""
attempts="${TERM_BACKGROUND_ATTEMPTS:-3}"
i=0
while [ "$i" -lt "$attempts" ]; do
answer="$answer$(dd bs=64 count=1 <&3 2>/dev/null)"
# A full "rgb:RRRR/GGGG/BBBB" reply has two '/' separators; stop as soon
# as we have them instead of waiting out every remaining attempt.
rest=${answer#*rgb:}
if [ "$rest" != "$answer" ]; then
tmp=$rest
count=0
while [ "$tmp" != "${tmp#*/}" ]; do
count=$((count + 1))
tmp=${tmp#*/}
done
[ "$count" -ge 2 ] && break
fi
i=$((i + 1))
done
stty "$saved" <&3 2>/dev/null # restore before anything else
exec 3>&-
# Expected reply: ESC ] 11 ; rgb:RRRR/GGGG/BBBB (ESC \ | BEL)
case "$answer" in
*rgb:*) ;;
*) echo dark; exit 0 ;;
esac
rgb=${answer#*rgb:} # RRRR/GGGG/BBBB<terminator>
r=${rgb%%/*}; rgb=${rgb#*/}
g=${rgb%%/*}; rgb=${rgb#*/}
b=$rgb
# Keep the most-significant two hex digits of each component.
r=${r%"${r#??}"}
g=${g%"${g#??}"}
b=${b%"${b#??}"}
case "$r$g$b" in
""|*[!0-9a-fA-F]*) echo dark; exit 0 ;;
esac
r=$(( 0x$r )); g=$(( 0x$g )); b=$(( 0x$b ))
# Rec. 601 perceived luminance, 0-255. Over half => light.
lum=$(( (r * 299 + g * 587 + b * 114) / 1000 ))
if [ "$lum" -gt 127 ]; then
echo light
else
echo dark
fi