mirror of
https://github.com/theupriser/steamify-cachyos.git
synced 2026-10-03 17:41:58 +02:00
207 lines
9.4 KiB
Bash
207 lines
9.4 KiB
Bash
#!/bin/bash
|
|
# Machine-readable mode for the graphical app (steamify-ui): the same
|
|
# components and plan as the menu, as JSON on stdout.
|
|
# steamify.sh --backend status
|
|
# one JSON object: version, machine, and every menu item with its
|
|
# current state and what a run would pick by default.
|
|
# steamify.sh --backend apply [--reapply] [--boot gamescope|desktop] <id>...
|
|
# turns on the listed components and off the others (like ticking them
|
|
# in the menu), then streams one JSON object per line: plan, start,
|
|
# log, done, finished. sudo asks through $SUDO_ASKPASS (no terminal).
|
|
# steamify.sh --backend bios-prepare | bios-flash
|
|
# the BIOS update in two steps, so the app shows both warnings between
|
|
# them (bios-ready event: current, newest, checksum, compatible).
|
|
# Sourced by steamify.sh; not meant to be run on its own.
|
|
|
|
json_str() {
|
|
# json_str <text>: <text> as a JSON string (quotes, backslashes and
|
|
# control characters escaped; colour codes dropped).
|
|
local s="$1"
|
|
s="$(printf '%s' "$s" | sed -E $'s/\x1b\\[[0-9;]*[A-Za-z]//g')"
|
|
s="${s//\\/\\\\}"; s="${s//\"/\\\"}"
|
|
s="${s//$'\t'/\\t}"; s="${s//$'\r'/}"; s="${s//$'\n'/\\n}"
|
|
s="$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037')"
|
|
printf '"%s"' "$s"
|
|
}
|
|
|
|
json_list() {
|
|
# json_list <item>...: a JSON array of strings.
|
|
local out="" x
|
|
for x in "$@"; do out+="${out:+,}$(json_str "$x")"; done
|
|
printf '[%s]' "$out"
|
|
}
|
|
|
|
backend_event() {
|
|
# backend_event <event> [<json fields without braces>]
|
|
printf '{"event":%s%s}\n' "$(json_str "$1")" "${2:+,$2}"
|
|
}
|
|
|
|
backend_status() {
|
|
local c first_run=true items="" kind parent now
|
|
detect_components
|
|
for c in "${COMPONENTS[@]}"; do
|
|
component_available "$c" && ! is_action "$c" && [[ "${CURRENT[$c]}" == 1 ]] && first_run=false
|
|
done
|
|
for c in "${COMPONENTS[@]}"; do
|
|
component_available "$c" || continue
|
|
kind=toggle; is_action "$c" && kind=action; [[ "$c" == boot ]] && kind=choice
|
|
parent="${PARENT[$c]:-}"
|
|
now="${CURRENT[$c]:-0}"
|
|
items+="${items:+,}{\"id\":$(json_str "$c"),\"label\":$(json_str "${LABEL[$c]%%:*}")"
|
|
items+=",\"hint\":$(json_str "$( [[ "${LABEL[$c]}" == *:* ]] && echo "${LABEL[$c]#*: }")")"
|
|
items+=",\"kind\":\"$kind\",\"parent\":$(json_str "$parent")"
|
|
items+=",\"needs\":$(json_str "$([[ "$c" == silent ]] && silent_needs_boot && echo boot)")"
|
|
items+=",\"on\":$( [[ "$now" == 1 ]] && echo true || echo false)"
|
|
items+=",\"wanted\":$( [[ "${WANTED[$c]:-0}" == 1 ]] && echo true || echo false)"
|
|
items+=",\"update\":$(feature_outdated "$c" && echo true || echo false)"
|
|
items+=",\"new\":$({ feature_new "$c" || feature_new_optin "$c"; } && echo true || echo false)"
|
|
items+=",\"selectable\":$(component_selectable "$c" && echo true || echo false)"
|
|
# Why it can't be turned on, for the app's explanation.
|
|
[[ "$c" == vram && -n "${VRAM_NVIDIA_CASE:-}" ]] &&
|
|
items+=",\"note\":$(json_str "$(vram_nvidia_note "$VRAM_NVIDIA_CASE")")"
|
|
items+="}"
|
|
done
|
|
local cec="" f
|
|
for f in /dev/cec*; do [[ -e "$f" ]] && cec+="${cec:+ }$(basename "$f")"; done
|
|
local bios='null'
|
|
if bios_available; then
|
|
bios="{\"current\":$(json_str "$(bios_current)"),\"newest\":$(json_str "${BIOS_NEWEST:-}"),\"selectable\":$(bios_selectable && echo true || echo false),\"dryRun\":$([[ -n "$BIOS_DRY_RUN" ]] && echo true || echo false)}"
|
|
fi
|
|
printf '{"version":%s,"firstRun":%s,"steamMachine":%s,"kernel":%s,"pinnedKernel":%s,"cecDevices":%s,"leds":%s,"bios":%s,"items":[%s]}\n' \
|
|
"$(json_str "$VERSION")" "$first_run" \
|
|
"$(detect_valve_fremont && echo true || echo false)" \
|
|
"$(json_str "$(uname -r)")" "$(json_str "${PINNED_KERNEL_VER:-}")" \
|
|
"$(json_str "$cec")" \
|
|
"$(compgen -G '/sys/class/leds/valve-leds*' | wc -l)" \
|
|
"$bios" "$items"
|
|
}
|
|
|
|
backend_run_component() {
|
|
# backend_run_component <id> <enable|disable>: runs it in this shell
|
|
# (its variables, e.g. RESTART_FOR_LOGIN, must survive), each output line
|
|
# as a log event.
|
|
local c="$1" what="$2" rc
|
|
backend_event start "\"id\":$(json_str "$c"),\"action\":\"$what\",\"label\":$(json_str "${LABEL[$c]%%:*}")"
|
|
"${c}_${what}" > >(while IFS= read -r line; do backend_event log "\"id\":$(json_str "$c"),\"line\":$(json_str "$line")"; done) 2>&1
|
|
rc=$?
|
|
# Let the log reader finish before the next event.
|
|
wait $! 2>/dev/null
|
|
backend_event "done" "\"id\":$(json_str "$c"),\"ok\":$([[ $rc -eq 0 ]] && echo true || echo false)"
|
|
return $rc
|
|
}
|
|
|
|
backend_sudo() {
|
|
# sudo from the app has no terminal: ask through its helper, and keep the
|
|
# credentials fresh as the menu does.
|
|
if [[ -z "${SUDO_ASKPASS:-}" ]] && ! sudo -n true 2>/dev/null; then
|
|
backend_event finished '"failed":[],"restart":false,"error":"no-sudo"'
|
|
return 1
|
|
fi
|
|
sudo() { command sudo ${SUDO_ASKPASS:+-A} "$@"; }
|
|
if ! sudo -n true 2>/dev/null && ! sudo true 2>/dev/null; then
|
|
backend_event finished '"failed":[],"restart":false,"error":"wrong-password"'
|
|
return 1
|
|
fi
|
|
while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done 2>/dev/null &
|
|
}
|
|
|
|
backend_bios_prepare() {
|
|
# Download, checksum and fwupd's device check; the app then shows its
|
|
# two warnings and calls bios-flash.
|
|
local rc
|
|
backend_sudo || return 1
|
|
backend_event start '"id":"bios","action":"prepare","label":"Update BIOS"'
|
|
bios_prepare > >(while IFS= read -r line; do backend_event log "\"id\":\"bios\",\"line\":$(json_str "$line")"; done) 2>&1
|
|
rc=$?; wait $! 2>/dev/null
|
|
case $rc in
|
|
0) backend_event bios-ready "\"current\":$(json_str "$(bios_current)"),\"newest\":$(json_str "$BIOS_NEWEST"),\"checksum\":true,\"compatible\":$(json_str "$BIOS_COMPATIBLE")" ;;
|
|
2) backend_event finished '"failed":[],"restart":false,"nothing":true' ;;
|
|
*) backend_event finished '"failed":["bios"],"restart":false' ;;
|
|
esac
|
|
}
|
|
|
|
backend_bios_flash() {
|
|
backend_sudo || return 1
|
|
local rc
|
|
backend_run_component bios flash_only; rc=$?
|
|
backend_event finished "\"failed\":$([[ $rc -eq 0 ]] && echo '[]' || echo '["bios"]'),\"restart\":$(restart_needed && echo true || echo false),\"bios\":true"
|
|
}
|
|
|
|
backend_apply() {
|
|
local reapply=false boot="" id c
|
|
local -a want=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--reapply) reapply=true ;;
|
|
--boot) boot="$2"; shift ;;
|
|
*) want+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
detect_components
|
|
# What the app ticked: exactly these on, every other component off.
|
|
for c in "${COMPONENTS[@]}"; do
|
|
component_available "$c" || continue
|
|
is_action "$c" && { WANTED[$c]=0; continue; }
|
|
WANTED[$c]=0
|
|
for id in "${want[@]}"; do [[ "$id" == "$c" ]] && WANTED[$c]=1; done
|
|
done
|
|
# The same rules as the menu's toggles.
|
|
# (Single user mode only needs the conversion where it is offered: not on NVIDIA PCs.)
|
|
[[ "${WANTED[single]:-0}" == 1 ]] && component_available gaming && WANTED[gaming]=1
|
|
if component_available gaming; then
|
|
[[ "${WANTED[gaming]:-0}" == 0 ]] && { WANTED[single]=0; WANTED[boot]=0; }
|
|
else
|
|
WANTED[boot]=0
|
|
fi
|
|
[[ "${WANTED[bigpicture]:-0}" == 1 ]] && WANTED[nvidia]=1
|
|
[[ "${WANTED[nvsilent]:-0}" == 1 ]] && WANTED[nvidia]=1
|
|
[[ "${WANTED[bigpicture]:-0}" == 1 ]] && WANTED[nvsilent]=0
|
|
[[ "${WANTED[nvidia]:-0}" == 0 ]] && { WANTED[bigpicture]=0; WANTED[nvsilent]=0; }
|
|
[[ "${WANTED[silent]:-0}" == 1 ]] && component_available gaming && WANTED[gaming]=1
|
|
[[ "${WANTED[gaming]:-0}" == 0 ]] && WANTED[silent]=0
|
|
[[ "${WANTED[poweroff]:-0}" == 1 ]] && WANTED[machine]=1
|
|
[[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[poweroff]=0
|
|
[[ "${WANTED[kpin]:-0}" == 1 ]] && WANTED[machine]=1
|
|
[[ "${WANTED[machine]:-0}" == 0 ]] && WANTED[kpin]=0
|
|
# Retired: only ever removed.
|
|
WANTED[hdmi]=0
|
|
case "$boot" in desktop) WANTED[boot]=1; WANTED[gaming]=1 ;; gamescope) WANTED[boot]=0 ;; esac
|
|
silent_normalize
|
|
|
|
REAPPLY=$reapply
|
|
plan_changes
|
|
backend_event plan "\"disable\":$(json_list "${TO_DISABLE[@]}"),\"enable\":$(json_list "${TO_ENABLE[@]}")"
|
|
if [[ ${#TO_DISABLE[@]} -eq 0 && ${#TO_ENABLE[@]} -eq 0 ]]; then
|
|
feature_record_unticked
|
|
backend_event finished '"failed":[],"restart":false,"nothing":true'
|
|
return 0
|
|
fi
|
|
|
|
backend_sudo || return 1
|
|
|
|
LOGIN_MANAGER=plasmalogin
|
|
[[ "${WANTED[single]}" == 1 ]] && LOGIN_MANAGER=sddm
|
|
local -a failed=()
|
|
for c in "${TO_DISABLE[@]}"; do
|
|
if backend_run_component "$c" disable; then feature_record "$c" disable; else failed+=("$c"); fi
|
|
done
|
|
for c in "${TO_ENABLE[@]}"; do
|
|
if backend_run_component "$c" enable; then is_action "$c" || feature_record "$c" enable; else failed+=("$c"); fi
|
|
done
|
|
feature_record_unticked
|
|
os_version_refresh
|
|
[[ " ${TO_DISABLE[*]} ${TO_ENABLE[*]} " =~ \ (gaming|single|boot|kpin)\ ]] && RESTART_FOR_LOGIN=true
|
|
backend_event finished "\"failed\":$(json_list "${failed[@]}"),\"restart\":$(restart_needed && echo true || echo false)"
|
|
}
|
|
|
|
backend_main() {
|
|
case "${1:-}" in
|
|
status) backend_status ;;
|
|
apply) shift; backend_apply "$@" ;;
|
|
bios-prepare) backend_bios_prepare ;;
|
|
bios-flash) backend_bios_flash ;;
|
|
*) err "Usage: steamify.sh --backend status | apply [--reapply] [--boot gamescope|desktop] <id>..."; return 2 ;;
|
|
esac
|
|
}
|