SmartestHome/hosts/thin-client/configs/eww/fullscreen-watcher.sh

105 lines
3.8 KiB
Bash

#!/bin/sh
# Owns the visibility of the eww now-playing widget. Installed to
# /usr/local/bin/fullscreen-watcher, started from configs/sway/config.
#
# The rule this enforces: the widget may only appear for *audio-only* playback with
# nothing visual on screen. A status overlay on top of a film or a Steam Link stream is
# worse than no status overlay at all, so every ambiguous case resolves to "hidden".
#
# Two independent signals, because neither alone is sufficient:
#
# 1. The sway tree. Catches fullscreen anything (mpv, Steam Link, a fullscreen
# Firefox video) via fullscreen_mode, and catches a *windowed* visual app via its
# app_id/class. mpv is the interesting case here: playing an audio-only file it
# opens no window at all, so "an mpv window exists in the tree" is already a
# reliable proxy for "mpv has a video stream".
#
# 2. mpv's IPC socket, queried for the `video-format` property. This is the direct
# answer to "does the current mpv track have video", and covers the case where
# mpv was started with --force-window and so has a window even for audio. It is
# best-effort: if the socket is absent (mpv not running, or configs/mpv/mpv.conf
# not installed) signal 1 still stands on its own.
set -eu
WINDOW=now-playing
LOCK="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/fullscreen-watcher.pid"
# exec_always in the sway config re-runs this on every `swaymsg reload`; without this
# guard each reload would leave another subscription and another swaymsg behind.
if [ -f "$LOCK" ] && kill -0 "$(cat "$LOCK" 2>/dev/null)" 2>/dev/null; then
exit 0
fi
echo $$ > "$LOCK"
if ! command -v eww >/dev/null 2>&1; then
echo "fullscreen-watcher: eww is not installed, no now-playing widget on this image." >&2
echo " See live-build/config/hooks/normal/0800-eww-widget.hook.chroot." >&2
exit 0
fi
eww daemon >/dev/null 2>&1 || true
# Anything whose presence on screen means "do not draw over this". Steam Link runs
# under Xwayland so it appears as an X11 class, not a Wayland app_id — both are
# checked. This is a fixed list in this file; nothing external feeds it.
VISUAL_MATCH='^(mpv|steam|steamlink|steam_app_.*|org\.videolan\.VLC)$'
visual_on_screen() {
swaymsg -t get_tree 2>/dev/null | jq -e --arg m "$VISUAL_MATCH" '
[ recurse(.nodes[]?, .floating_nodes[]?)
| select(.type == "con" or .type == "floating_con")
| select(
((.fullscreen_mode // 0) != 0)
or ((.visible // false)
and (((.app_id // "") | ascii_downcase | test($m))
or ((.window_properties.class // "") | ascii_downcase | test($m))))
)
] | length > 0
' >/dev/null 2>&1
}
mpv_has_video() {
for sock in "${HOME:-/root}/.mpv-socket" "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/mpv.sock"; do
[ -S "$sock" ] || continue
python3 - "$sock" <<'PY' && return 0
import json, socket, sys
try:
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
conn.settimeout(1.0)
conn.connect(sys.argv[1])
conn.sendall(b'{"command":["get_property","video-format"]}\n')
reply = json.loads(conn.recv(4096).splitlines()[0])
except Exception:
sys.exit(1)
sys.exit(0 if reply.get("data") else 1)
PY
done
return 1
}
apply() {
if visual_on_screen || mpv_has_video; then
eww close "$WINDOW" >/dev/null 2>&1 || true
else
eww open "$WINDOW" >/dev/null 2>&1 || true
fi
}
cleanup() {
rm -f "$LOCK"
eww close "$WINDOW" >/dev/null 2>&1 || true
}
trap cleanup EXIT INT TERM
apply
# The event payloads are not parsed: any window or workspace change simply triggers a
# fresh look at the whole tree. That is one extra `swaymsg -t get_tree` per event and
# it removes a whole class of bugs around which fields a given sway version puts in a
# "fullscreen_mode" event.
swaymsg -t subscribe -m '["window","workspace"]' 2>/dev/null | while read -r _event; do
apply
done