Files
libretro/ports/RetroBIOS/retrobios/screens.txt
T
Abdessamad Derraz 801a0150e6 feat: run the handheld launchers in the tui
The PortMaster port now starts install.py --tui on the console (or in
foot or xterm under a compositor), with gptokeyb in kill mode on the
installer's path, retrobios.gptk, TERM=linux and a Terminus font, and
puts the console, font and mapper back on exit. pugwash screens stay as
the fallback where no terminal can show, and start in no-check mode so
PortMaster's A/B fix applies. Without a network, every launcher installs
from a pack found on the card or names the pack and the folder.

Adds launchers for Batocera and Knulli (Ports, evmapy .keys), muOS
(.muxapp in muxterm with gptokeyb2) and the Tools menu of ArkOS,
dArkOS, ROCKNIX, JELOS and AmberELEC, built by build_port.py with the
pinned installer and tested on a simulated device.
2026-10-23 16:26:47 +02:00

419 lines
15 KiB
Plaintext

#!/bin/bash
# shellcheck shell=bash
# RetroBIOS screens, drawn by PortMaster's own GUI (pugwash fifo_control)
# through the functions of PortMasterDialog.txt. Sourced by RetroBIOS.sh
# after PortMasterDialog.txt and tui.txt.
#
# They are the port's fallback: RetroBIOS.sh opens them only when no terminal
# can show install.py --tui, that is under a compositor that has neither foot
# nor xterm, or when the interface refuses the console it was given.
#
# Only what PortMaster 2024.02.26 and the 8.5.23 copy JELOS and KOLOS ship
# is used: PortMasterDialogInit, PortMasterDialog, PortMasterDialogResult,
# PortMasterDialogExit, PortMasterIPCheck, and the commands
# register_set_info, register_clear, selection_list, messages_begin,
# message, messages_end, progress, progress_clear and check_runtime.
# Those releases ignore --want-cancel, so every list ends on its own Back
# or Quit entry. They also read "\n" literally, so no text has a newline,
# and their wait for an answer gives up after 30 seconds: rb_answer keeps
# waiting while pugwash is alive.
#
# pugwash starts in "no-check" mode, never "no-harbour": only with
# HarbourMaster loaded does it apply the A/B fix PortMaster keeps for Miyoo,
# TrimUI, Knulli and the Xbox-layout ArkOS and AmberELEC pads.
#
# Inputs: GAMEDIR, controlfolder, ESUDO, and what tui.txt defines
# (RB_INSTALL, RB_STATE, RB_LOG, rb_py, rb_online, rb_find_pack).
RB_EVENTS="${RB_EVENTS:-$GAMEDIR/rbevents.py}"
RB_SOURCE_ARGS=()
# Read by tui.txt's rb_python_text.
# shellcheck disable=SC2034
RB_PYTHON_PROBLEM="${RB_PYTHON_PROBLEM:-}"
# --- Python runtime -------------------------------------------------------
# tui.txt's rb_runtime calls this when the runtime is not on the card yet:
# PortMaster downloads it the way its own ports get theirs.
rb_fetch_runtime() {
local squashfs="$1" answer
if [ -z "$(PortMasterIPCheck)" ]; then
RB_PYTHON_PROBLEM="runtime_offline"
return 1
fi
PortMasterDialogInit "no-check"
PortMasterDialog "messages_begin"
PortMasterDialog "message" "This system's Python is older than 3.8. Downloading PortMaster's $RB_RUNTIME runtime."
answer=$(rb_answer "check_runtime" "$RB_RUNTIME.squashfs")
PortMasterDialog "messages_end"
PortMasterDialogExit
if [ "$answer" != "OKAY" ] || [ ! -f "$squashfs" ]; then
RB_PYTHON_PROBLEM="runtime_failed"
return 1
fi
return 0
}
# --- Dialog primitives ----------------------------------------------------
# A command that waits for the user, and its answer. PortMasterDialogResult
# stops waiting after 30 seconds on the older releases and then returns
# WAIT: the answer is read again until pugwash writes one or exits.
rb_answer() {
local answer tries=0
answer=$(PortMasterDialogResult "$@")
while [ "$answer" = "WAIT" ] || [ -z "$answer" ]; do
if [ -n "$PM_PID" ] && [ ! -d "/proc/$PM_PID" ]; then
break
fi
if [ "$tries" -ge 6000 ]; then
break
fi
tries=$((tries + 1))
sleep 0.3
answer=$(cat "$PM_DONE" 2>/dev/null)
done
printf '%s\n' "$answer"
}
rb_plural() {
if [ "$1" = "1" ]; then
printf '%s %s\n' "$1" "$2"
else
printf '%s %ss\n' "$1" "$2"
fi
}
rb_entry() {
PortMasterDialog "register_set_info" "$1" "$2" "title:$3" "description:$4"
}
# Show a list and print the chosen key. B, where the release reads it,
# answers like the Back or Quit entry ($2).
rb_choose() {
local answer
answer=$(rb_answer "selection_list" "--register=$1" "--want-description" "--want-cancel")
case "$answer" in
CANCEL|FAIL|WAIT|"") printf '%s\n' "$2" ;;
*) printf '%s\n' "$answer" ;;
esac
}
# A screen of text with one or two ways out: rb_notice TITLE TEXT KEY LABEL [KEY LABEL].
# B answers like the last one, which leaves.
rb_notice() {
local title="$1" text="$2"
shift 2
local last="$1" answer
PortMasterDialog "register_clear" "notice"
# The list has no heading of its own: the first entry is the heading.
rb_entry "notice" "info" "$title" "$text"
rb_entry "notice" "$1" "$2" "$text"
if [ $# -ge 4 ]; then
rb_entry "notice" "$3" "$4" "$text"
last="$3"
fi
while true; do
answer=$(rb_choose "notice" "$last")
if [ "$answer" != "info" ]; then
printf '%s\n' "$answer"
return
fi
done
}
# --- Reading install.py ---------------------------------------------------
# Run a --check and read its records into RB_* variables. Without a network
# the files come from a pack on the card, when there is one.
rb_check() {
mkdir -p "$RB_STATE"
PortMasterDialog "messages_begin"
PortMasterDialog "message" "Reading the BIOS folder and the file list."
RB_SOURCE_ARGS=()
if ! rb_online; then
local pack
pack=$(rb_find_pack)
[ -n "$pack" ] && RB_SOURCE_ARGS=(--from-pack "$pack")
fi
rb_py "$RB_INSTALL" --check --events "${RB_SOURCE_ARGS[@]}" "${RB_PLATFORM_ARGS[@]}" > "$RB_STATE/check.jsonl" 2>> "$RB_LOG"
PortMasterDialog "messages_end"
rb_py "$RB_EVENTS" status "$RB_STATE/check.jsonl" > "$RB_STATE/status.tsv" 2>> "$RB_LOG"
rb_read_status "$RB_STATE/status.tsv"
}
rb_read_status() {
RB_OS_NAME="this system"
RB_DETECTED=""
RB_PLATFORM=""
RB_PLATFORM_NAME=""
RB_BIOS_DIR=""
RB_ERROR=""
RB_ERROR_TEXT=""
RB_CHECKED=""
RB_END="none"
RB_SYSTEMS=()
RB_COMPLETE=0
local kind a b c d e f g h
while IFS=$'\t' read -r kind a b c d e f g h; do
case "$kind" in
detect) RB_OS_NAME="$a"; RB_DETECTED="$b" ;;
platform) RB_PLATFORM="$a"; RB_PLATFORM_NAME="$b"; RB_BIOS_DIR="$c" ;;
error) RB_ERROR="$a"; RB_ERROR_TEXT="$b" ;;
check)
RB_CHECKED=1
RB_FILES="$a"; RB_PRESENT="$b"; RB_MISSING="$c"; RB_WRONG="$d"
RB_NEEDED_SIZE="$e"; RB_DATA_MISSING="$f"; RB_NOT_INSTALLABLE="$g"
;;
system)
if [ "$h" = "1" ]; then
RB_SYSTEMS+=("$a"$'\t'"$b"$'\t'"$c"$'\t'"$d"$'\t'"$e"$'\t'"$f"$'\t'"$g")
else
RB_COMPLETE=$((RB_COMPLETE + 1))
fi
;;
end) RB_END="$a" ;;
esac
done < "$1"
}
rb_status_text() {
local text="$RB_OS_NAME, $RB_PLATFORM_NAME file list: $RB_PRESENT/$RB_FILES BIOS files, $RB_MISSING missing, $RB_WRONG wrong hash."
if [ "${RB_NOT_INSTALLABLE:-0}" != "0" ]; then
text="$text $RB_NOT_INSTALLABLE not in the collection."
fi
printf '%s\n' "$text"
}
rb_pending() {
printf '%s\n' $((RB_MISSING + RB_WRONG + RB_DATA_MISSING))
}
# --- Screens --------------------------------------------------------------
rb_screen_no_python() {
rb_notice "Python 3.8 or newer is needed" "$(rb_python_text)" "quit" "Quit" > /dev/null
}
# What the check found when it found nothing to show: no platform, no
# connection, an unreadable list. Prints check or quit.
rb_screen_problem() {
if [ "$RB_END" = "none" ]; then
rb_notice "The installer stopped before checking" "install.py ended without a result. Its messages are in $RB_LOG." "check" "Check again" "quit" "Quit"
elif [ "$RB_ERROR" = "conflict" ]; then
rb_notice "Two systems found" "$RB_ERROR_TEXT, so the BIOS folder is not known. $RB_PC_ROUTE" "check" "Check again" "quit" "Quit"
elif [ "$RB_DETECTED" = "0" ]; then
rb_notice "No BIOS folder found" "install.py knows no BIOS folder on $RB_OS_NAME. $RB_PC_ROUTE" "check" "Check again" "quit" "Quit"
elif [ "$RB_ERROR" = "network" ]; then
rb_notice "No connection" "$RB_OS_NAME, BIOS folder $RB_BIOS_DIR. $(rb_pack_text)" "check" "Check again" "quit" "Quit"
elif [ "$RB_ERROR" = "invalid" ]; then
rb_notice "The file list is newer than this port" "$RB_ERROR_TEXT. Update RetroBIOS from PortMaster, or use the installer on a computer. $RB_PC_ROUTE" "check" "Check again" "quit" "Quit"
else
rb_notice "The file list could not be read" "${RB_ERROR_TEXT:-install.py ended with code $RB_END}. Its messages are in $RB_LOG." "check" "Check again" "quit" "Quit"
fi
}
# The menu after a check. Prints update, choose, check or quit.
rb_screen_menu() {
local status pending
status=$(rb_status_text)
pending=$(rb_pending)
PortMasterDialog "register_clear" "menu"
# The list has no heading of its own: the first entry is the status.
if [ "$pending" -gt 0 ]; then
local data=""
if [ "${RB_DATA_MISSING:-0}" != "0" ]; then
data=" and $RB_DATA_MISSING data files"
fi
rb_entry "menu" "status" "$RB_OS_NAME: $RB_PRESENT/$RB_FILES BIOS files, $RB_MISSING missing, $RB_WRONG wrong hash" "$status BIOS folder: $RB_BIOS_DIR."
rb_entry "menu" "update" "Update everything missing" "$status Downloads $(rb_plural $((RB_MISSING + RB_WRONG)) file)$data, $RB_NEEDED_SIZE, to $RB_BIOS_DIR."
rb_entry "menu" "choose" "Choose systems" "$status Pick the systems to update."
else
rb_entry "menu" "status" "$RB_OS_NAME: all $RB_FILES BIOS files present" "$status Nothing to download. BIOS folder: $RB_BIOS_DIR."
fi
rb_entry "menu" "check" "Check again" "$status Reads the BIOS folder and the file list again."
rb_entry "menu" "quit" "Quit" "$status"
local answer
while true; do
answer=$(rb_choose "menu" "quit")
if [ "$answer" != "status" ]; then
printf '%s\n' "$answer"
return
fi
done
}
# Systems with files to fetch, toggled with A. Prints the chosen system
# ids, one per line, or nothing for Back.
rb_screen_systems() {
local -A chosen=()
local line id name files present missing wrong size answer count
while true; do
count=0
for id in "${!chosen[@]}"; do
count=$((count + 1))
done
PortMasterDialog "register_clear" "systems"
if [ "$count" -gt 0 ]; then
rb_entry "systems" "update" "Update the $count selected" "Downloads what the selected systems lack to $RB_BIOS_DIR."
else
rb_entry "systems" "update" "Update the selected" "Nothing selected yet: pick systems below with A."
fi
rb_entry "systems" "back" "Back" "Back to the menu. $(rb_plural "$RB_COMPLETE" system) complete, not listed."
for line in "${RB_SYSTEMS[@]}"; do
IFS=$'\t' read -r id name files present missing wrong size <<< "$line"
local mark=""
if [ -n "${chosen[$id]}" ]; then
mark=" (selected)"
fi
rb_entry "systems" "sys:$id" "$name$mark" "$present/$files present, $missing missing, $wrong wrong hash, $size to download. A selects or clears it."
done
answer=$(rb_choose "systems" "back")
case "$answer" in
back) return ;;
update)
if [ "$count" -gt 0 ]; then
printf '%s\n' "${!chosen[@]}" | sort
return
fi
;;
sys:*)
id="${answer#sys:}"
# Quoted literally so unset expands the subscript once: a double-quoted
# "chosen[$id]" is expanded again inside unset.
[[ "$id" =~ ^[A-Za-z0-9._-]+$ ]] || continue
if [ -n "${chosen[$id]}" ]; then
unset 'chosen[$id]'
else
chosen[$id]=1
fi
;;
esac
done
}
# Download with a progress bar fed by the event stream: rb_update [SYSTEM...]
rb_update() {
local args=() system
for system in "$@"; do
args+=(--system "$system")
done
mkdir -p "$RB_STATE"
PortMasterDialog "messages_begin"
PortMasterDialog "message" "Downloading to $RB_BIOS_DIR."
PortMasterDialog "progress" "Starting" 0 1
local kind a b c d e f
rb_py "$RB_INSTALL" --events "${RB_SOURCE_ARGS[@]}" "${RB_PLATFORM_ARGS[@]}" "${args[@]}" 2>> "$RB_LOG" \
| rb_py "$RB_EVENTS" follow "$RB_STATE/summary.tsv" 2>> "$RB_LOG" \
| while IFS=$'\t' read -r kind a b c d e f; do
case "$kind" in
download) PortMasterDialog "message" "$(rb_plural "$a" file), $b." ;;
file) PortMasterDialog "progress" "$d, $e of $f" "$a" "$b" ;;
progress) PortMasterDialog "progress" "$c of $d" "$a" "$b" ;;
error) PortMasterDialog "message" "$b" ;;
esac
done
PortMasterDialog "progress_clear"
PortMasterDialog "messages_end"
}
# What the update did. Prints back or quit.
rb_screen_summary() {
local kind a b c d shown=0 failed=0 kept=0
local end="none" installed=0 up_to_date=0 pending=0 interrupted=0 error=""
while IFS=$'\t' read -r kind a b c d; do
case "$kind" in
summary) installed="$a"; up_to_date="$b"; pending="$c"; interrupted="$d" ;;
failed) failed=$((failed + 1)) ;;
error) error="$b" ;;
end) end="$a" ;;
esac
done < "$RB_STATE/summary.tsv"
PortMasterDialog "register_clear" "summary"
# The first entry is the outcome, the heading the list does not have.
if [ "$end" = "none" ]; then
rb_entry "summary" "info:result" "The installer stopped" "install.py ended without a result. Its messages are in $RB_LOG."
elif [ -n "$error" ]; then
rb_entry "summary" "info:result" "Nothing downloaded" "$error."
else
rb_entry "summary" "info:result" "Installed: $(rb_plural "$installed" file)" "$up_to_date already up to date, $failed failed, $pending still to fetch."
fi
rb_entry "summary" "back" "Back to the menu" "Checks the BIOS folder again."
rb_entry "summary" "quit" "Quit" "Leaves RetroBIOS."
if [ "$interrupted" = "1" ]; then
rb_entry "summary" "info:stopped" "Stopped before the end" "Update again to carry on. Files already installed are kept."
fi
while IFS=$'\t' read -r kind a b c d; do
case "$kind" in
failed)
shown=$((shown + 1))
if [ "$shown" -le 50 ]; then
rb_entry "summary" "info:failed:$shown" "Failed: $a" "$b"
fi
;;
kept)
kept=$((kept + 1))
rb_entry "summary" "info:kept:$kept" "Kept $(rb_plural "$b" file) you had ($c)" "The collection's copies replaced them. Yours are in $a. Copy what that folder holds into the folder above retrobios-backup to put them back."
;;
skipped)
rb_entry "summary" "info:skipped:$a" "Not in the collection ($a): $(rb_plural "$b" file)" "Required: $c. The RetroBIOS collection does not hold these files, so they cannot be downloaded."
;;
esac
done < "$RB_STATE/summary.tsv"
if [ "$shown" -gt 50 ]; then
rb_entry "summary" "info:more" "And $((shown - 50)) more failed" "The full list is in $RB_LOG."
fi
local answer
while true; do
answer=$(rb_choose "summary" "back")
case "$answer" in
back|quit) printf '%s\n' "$answer"; return ;;
esac
done
}
# --- Flow -----------------------------------------------------------------
# The screens, once rb_select_python has run.
rb_main() {
PortMasterDialogInit "no-check"
if [ -z "$RB_PYTHON" ]; then
rb_screen_no_python
PortMasterDialogExit
return 1
fi
RB_PLATFORM_ARGS=()
local action systems chosen
rb_check
while true; do
if [ -z "$RB_CHECKED" ]; then
action=$(rb_screen_problem)
else
# Later runs name the platform the first one found, so a second
# detected platform never changes what the screens describe.
RB_PLATFORM_ARGS=(--platform "$RB_PLATFORM")
action=$(rb_screen_menu)
fi
case "$action" in
update)
rb_update
[ "$(rb_screen_summary)" = "quit" ] && break
rb_check
;;
choose)
systems=$(rb_screen_systems)
if [ -n "$systems" ]; then
mapfile -t chosen <<< "$systems"
rb_update "${chosen[@]}"
[ "$(rb_screen_summary)" = "quit" ] && break
rb_check
fi
;;
check) rb_check ;;
*) break ;;
esac
done
PortMasterDialogExit
return 0
}