diff --git a/install.ps1 b/install.ps1 index b09c5836..70f6b4e5 100644 --- a/install.ps1 +++ b/install.ps1 @@ -11,7 +11,7 @@ $ErrorActionPreference = "Stop" $defaultInstallUrl = "https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" - $defaultInstallSha256 = "ea2777500114b796bb4a23ea9e0dd0a15bc4499bb1289e6e426943beece48865" + $defaultInstallSha256 = "e2cc5f073a8226fe26b0513ebd25f00de6f3f613d8c9851473c2d30b53356f48" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index ad61b4cf..b0eca90e 100644 --- a/install.py +++ b/install.py @@ -9675,6 +9675,9 @@ class TuiState: def __init__(self, os_type: str = "linux", home: Path | None = None) -> None: self.os_type = os_type self.home = home or Path("/home/user") + # The buttons the bar names: a pad route (TUI_PAD_ROUTES), "keys" + # for a keyboard alone, "both" where the code cannot tell. + self.pad: str = "both" self.stack: list = ["setup"] self.focus: dict = {} self.filters: dict = {} @@ -9778,6 +9781,36 @@ def plain_requested(args: argparse.Namespace, env: Mapping[str, str] | None = No return bool(args.plain) or _env_on(env, "RETROBIOS_PLAIN") or _env_on(env, "ACCESSIBLE") +# Each handheld launcher names the buttons that reach the interface +# (ports/shared/tui.txt exports RB_PAD as this), so the bar names those +# buttons and no others. +TUI_PAD_ENV = "RETROBIOS_PAD" +TUI_PAD_ROUTES = ("gptokeyb", "muxterm", "evmapy", "miyoo") +# Set by Steam for what it starts, and by gamescope: a controller may be +# all there is, mapped by Steam Input to keys nobody here can name. +_STEAM_MARKERS = ("SteamGamepadUI", "SteamDeck", "SteamGameId", "GAMESCOPE_WAYLAND_DISPLAY") + + +def tui_pad_route(os_type: str, env: Mapping[str, str] | None = None) -> str: + """The keys the hints name: a launcher's pad route, "keys" at a desktop, + "both" (button and key) where nothing says which one is held.""" + env = os.environ if env is None else env + named = env.get(TUI_PAD_ENV, "").strip().lower() + if named: + return named if named in TUI_PAD_ROUTES else "both" + if any(env.get(name) for name in _STEAM_MARKERS): + return "both" + if os_type in ("windows", "darwin", "wsl"): + return "keys" + # A Linux console is where a handheld draws: a desktop or a remote + # shell is a keyboard's. + if os_type == "linux" and env.get("TERM") != "linux" and any( + env.get(name) for name in ("DISPLAY", "WAYLAND_DISPLAY", "SSH_CONNECTION", + "SSH_TTY")): + return "keys" + return "both" + + def _terminal_size(stream: TextIO) -> tuple[int, int]: try: size = os.get_terminal_size(stream.fileno()) @@ -9939,7 +9972,10 @@ def _filter_row(state: TuiState) -> TuiRow: return TuiRow("filter", f"Filter: {text}_") if text: return TuiRow("filter", f"Filter: {text}") - return TuiRow("filter", "Filter (/ to type)", style="dim") + # A pad has no / and no letters: muxterm's own keyboard types. + how = {"muxterm": "Select shows a keyboard", "gptokeyb": "with a keyboard", + "evmapy": "with a keyboard", "miyoo": "with a keyboard"}.get(state.pad, "/ to type") + return TuiRow("filter", f"Filter ({how})", style="dim") def _system_titles(state: TuiState) -> dict: @@ -10696,6 +10732,8 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: return _path_key(state, key) if state.editing: return _filter_key(state, key) + if state.pad == "miyoo": + key = _miyoo_key(state, key) char = key.text if key.name == "char" else "" if key.name == "f1" or char == "?": state.help = True @@ -10777,6 +10815,29 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: return [] +# A Miyoo's L1 and R1 are the keys E and T (codes 18 and 20). +_MIYOO_PAGES = {"e": "pgup", "t": "pgdn"} + + +def _miyoo_key(state: TuiState, key: TuiKey) -> TuiKey: + """A Miyoo's buttons as the keys they stand for, outside a text field: + its A is Space, the button that chooses, so it acts as Enter but where + it ticks a system or a core.""" + if key.name != "char": + return key + if key.text in _MIYOO_PAGES: + return TuiKey(_MIYOO_PAGES[key.text]) + if key.text != " ": + return key + if state.screen in _CHECKLISTS: + rows = tui_rows(state) + row = rows[max(0, min(state.focus.get(state.screen, 0), len(rows) - 1))] \ + if rows else None + if row is not None and row.action in ("system", "core"): + return key + return TuiKey("enter") + + # Rows Space leaves alone: it is X (tick) on PortMaster and Y on muxterm, a # key pressed for something else, and these end the run or write. _SPACE_INERT = ("install", "close", "quit") @@ -10891,6 +10952,55 @@ _SHORT_KEYS = (("A/Enter ", "A "), ("B/Esc ", "B "), ("X/Space ", "X "), ("Y/o " ("Left/Right ", "<> "), ("Up/Down ", "^v ")) +# The key of a hint as each route names it, None for a key it lacks. The +# bar is written in the "both" form ("A/Enter Install"), the route's own +# names replace it: a PC user read "A/Enter", a muOS player was told a +# Start+Select that muxterm does not have. +_ROUTE_KEYS: dict[str, dict[str, str | None]] = { + "keys": {"A/Enter": "Enter", "B/Esc": "Esc", "X/Space": "Space", "Y/o": "o"}, + # retrobios.gptk: A Enter, B Esc, X Space, Y Tab, L1 and R1 a page, + # Start Enter, Select Esc, Start+Select the kill. + "gptokeyb": {"A/Enter": "A", "B/Esc": "B", "X/Space": "X", "Y/o": "Y", "?": None, + "/": None, "Ctrl-C": "Start+Select", "Enter": "A", "Esc": "B", + "Ctrl-U": None}, + # RetroBIOS.sh.keys: the same buttons, and no combo that ends the run. + "evmapy": {"A/Enter": "A", "B/Esc": "B", "X/Space": "X", "Y/o": "Y", "?": None, + "/": None, "Ctrl-C": None, "Enter": "A", "Esc": "B", "Ctrl-U": None}, + # muxterm's own keys: Start Enter, B Backspace, Y Space, Menu closes the + # terminal. A, X, L1 and R1 send nothing, Select shows its keyboard. + "muxterm": {"A/Enter": "Start", "B/Esc": "B", "X/Space": "Y", "Y/o": None, "?": None, + "/": None, "Ctrl-C": "Menu", "Enter": "Start", "Esc": None, + "Ctrl-U": None}, + # Buttons that are keys already: A Space, Start Enter, L2 Tab, R2 + # Backspace. B is Left Ctrl, a modifier: alone it sends no byte to a + # terminal (the console's keyboard driver only records its state), so + # the interface never sees it, nor X, Y and Select (Shift, Alt, Ctrl). + "miyoo": {"A/Enter": "A", "B/Esc": "R2", "X/Space": "A", "Y/o": "L2", "?": None, + "/": None, "Ctrl-C": None, "Enter": "Start", "Esc": None, "Ctrl-U": None}, +} + + +def _route_hint(state: TuiState, parts: tuple) -> tuple: + """The bar's entries named as the run's input names them.""" + names = _ROUTE_KEYS.get(state.pad) + if names is None or state.help: + return parts + named = [] + for part in parts: + key, sep, what = part.partition(" ") + if key in names: + # On a Miyoo A is Space, which ticks in a checklist: Start continues. + mine = ("Start" if state.pad == "miyoo" and key == "A/Enter" + and state.screen in _CHECKLISTS else names[key]) + if mine is None: + continue + key = mine + named.append(key + sep + what) + if state.pad == "muxterm" and state.screen == "path": + named.append("Select Keyboard") + return tuple(named) + + def _hint(width: int, *parts: str) -> str: """The keys that matter now, short names first given up, then whole entries from the right.""" @@ -11407,6 +11517,17 @@ def _bar(share: float, width: int) -> str: return f"[{'#' * filled}{'-' * (room - filled)}] {int(share * 100):3d}%" +# What stops an install a second time, and the first time, per route. +_STOP_KEYS = { + "both": ("B or Ctrl-C", "B, Ctrl-C or Start+Select"), + "keys": ("Esc or Ctrl-C", "Esc or Ctrl-C"), + "gptokeyb": ("B", "B or Start+Select"), + "evmapy": ("B", "B"), + "muxterm": ("B", "B"), + "miyoo": ("R2", "R2"), +} + + def _view_installing(state: TuiState, width: int, height: int) -> tuple: lines: list = [] if state.download is None: @@ -11429,10 +11550,10 @@ def _view_installing(state: TuiState, width: int, height: int) -> tuple: lines.append(f"{files_done} of {_plural(state.download['files'], 'file')}") if files_done >= state.download["files"] and state.summary is None: lines.append("Finishing...") - lines.append(_styled("Stopping, finishing the files in flight. Press B or Ctrl-C " + stops = _STOP_KEYS.get(state.pad, _STOP_KEYS["both"]) + lines.append(_styled(f"Stopping, finishing the files in flight. Press {stops[0]} " "again to stop now." if state.stopping - else "B, Ctrl-C or Start+Select stops. What is done stays done.", - "dim")) + else f"{stops[1]} stops. What is done stays done.", "dim")) room = height - len(lines) - 1 failed = [item for item in reversed(state.finished) if not item[1]] good = [item for item in reversed(state.finished) if item[1]] @@ -11637,21 +11758,68 @@ _TUI_VIEWS: dict[str, Callable[[TuiState, int, int], tuple]] = { } +# The help's names for each action, per route: the buttons first, then the +# keys a keyboard plugged in also has. None for an action the route lacks. +_HELP_KEYS: dict[str, dict[str, str | None]] = { + "both": {"move": "Up/Down, k/j", "page": "PgUp/PgDn, L1/R1", "ok": "A/Enter, Start", + "tick": "X, Right/Left", "options": "Y, o, Tab", "back": "B/Esc, Backspace", + "stop": "B/Esc, Ctrl-C", "kill": "Start+Select", "enter": "Enter", + "esc": "Esc"}, + "keys": {"move": "Up/Down, k/j", "page": "PgUp/PgDn", "ok": "Enter", + "tick": "Space, Right/Left", "options": "o, Tab", "back": "Esc, Backspace", + "stop": "Esc, Ctrl-C", "kill": None, "enter": "Enter", "esc": "Esc"}, + "gptokeyb": {"move": "D-pad, Up/Down", "page": "L1/R1, PgUp/PgDn", + "ok": "A, Start, Enter", "tick": "X, Space, Right/Left", + "options": "Y, Tab, o", "back": "B, Select, Esc", "stop": "B, Esc, Ctrl-C", + "kill": "Select, then Start", "enter": "A, Enter", "esc": "B, Esc"}, + "evmapy": {"move": "D-pad, Up/Down", "page": "L1/R1, PgUp/PgDn", "ok": "A, Start, Enter", + "tick": "X, Space, Right/Left", "options": "Y, Tab, o", + "back": "B, Select, Esc", "stop": "B, Esc, Ctrl-C", "kill": None, + "enter": "A, Enter", "esc": "B, Esc"}, + "muxterm": {"move": "D-pad, Up/Down", "page": "PgUp/PgDn", "ok": "Start, Enter", + "tick": "Y, Space, Right/Left", "options": "o, Tab", + "back": "B, Esc, Backspace", "stop": "B, Esc, Ctrl-C", "kill": None, + "enter": "Start, Enter", "esc": "Esc"}, + "miyoo": {"move": "D-pad, Up/Down", "page": "L1/R1, PgUp/PgDn", "ok": "A, Start, Enter", + "tick": "A, Space, Right/Left", "options": "L2, Tab, o", + "back": "R2, Esc, Backspace", "stop": "R2, Esc, Ctrl-C", "kill": None, + "enter": "Start, Enter", "esc": "Esc"}, +} + + +def _route_extras(state: TuiState) -> list[tuple[str, str]]: + """What a route's own buttons do that no key of the table says.""" + if state.pad == "muxterm": + return [("Select", "Keyboard on or off"), ("Menu", "Close the terminal")] + if state.pad == "miyoo": + return [("B", "Sends no key here")] + return [] + + def _help_lines(state: TuiState) -> list[tuple[str, str]]: screen = state.screen + names = _HELP_KEYS.get(state.pad, _HELP_KEYS["both"]) + # gptokeyb's kill: Start pressed first also chooses the row in focus. + kill = ([(names["kill"], "Quit at once")] + if state.pad == "gptokeyb" else []) if screen == "installing": - return [("B/Esc, Ctrl-C", "Stop: what is done stays done"), - ("Start+Select", "Stop"), ("?, F1", "This help")] + return [(names["stop"], "Stop: what is done stays done"), + *([(names["kill"], "Stop")] if names["kill"] else []), + *_route_extras(state), ("?, F1", "This help")] if screen == "path": - return [("Enter", "Use this folder"), ("Backspace", "Delete a character"), - ("Ctrl-U", "Clear"), ("Esc", "Back"), ("Ctrl-C", "Quit")] - keys = [("Up/Down, k/j", "Move")] + return [(names["enter"], "Use this folder"), ("Backspace", "Delete a character"), + ("Ctrl-U", "Clear"), (names["esc"], "Back"), ("Ctrl-C", "Quit"), + *_route_extras(state)] + keys = [(names["move"], "Move")] if len(tui_rows(state)) > 9 or screen in _PAGERS: - keys += [("PgUp/PgDn, L1/R1", "Page"), ("Home/End, g/G", "First, last")] + keys += [(names["page"], "Page"), ("Home/End, g/G", "First, last")] primary = _primary(state) or _TUI_VIEWS[screen](state, 80, 24)[2][0] - keys.append(("A/Enter, Start", primary.split(" ", 1)[-1])) + ok = names["ok"] + if state.pad == "miyoo" and screen in _CHECKLISTS: + ok = names["enter"] + keys.append((ok, primary.split(" ", 1)[-1])) if screen in _CHECKLISTS: - keys.append(("X, Right/Left", "Tick, untick")) + keys.append((names["tick"], "Tick, untick")) if screen == "systems": keys.append(("a, n", "All, none")) if screen in ("platforms", "systems", "cores"): @@ -11661,9 +11829,10 @@ def _help_lines(state: TuiState) -> list[tuple[str, str]]: if screen == "options": keys.append(("Left/Right", "Change a value")) if screen in ("plan", "systems", "confirm"): - keys.append(("Y, o, Tab", "Options")) + keys.append((names["options"], "Options")) back = "Quit" if len(state.stack) == 1 else "Back" - keys += [("B/Esc, Backspace", back), ("q, Ctrl-C", "Quit"), ("?, F1", "This help")] + keys += [(names["back"], back), ("q, Ctrl-C", "Quit"), *kill, *_route_extras(state), + ("?, F1", "This help")] return keys @@ -11705,6 +11874,7 @@ def tui_view(state: TuiState, cols: int, rows: int) -> list[str]: primary = _primary(state) if not state.help and hint[0].startswith("A/Enter") else None if primary: hint = (primary, *hint[1:]) + hint = _route_hint(state, hint) lines = [_styled(fit(f" RetroBIOS: {title}", cols), "title"), _styled("")] lines += [_styled(fit(" " + line, cols), line.style) for line in body[:height]] lines += [_styled("")] * (rows - 1 - len(lines)) @@ -11771,6 +11941,7 @@ def tui_start( ) -> tuple[TuiState, list[tuple]]: """The first screen, from what is found here and what the command named.""" state = TuiState(os_type=os_type, home=Path.home()) + state.pad = tui_pad_route(os_type) state.cli_systems = list(args.system or []) state.regions = resolve_regions(args.region) if args.region else [] state.cli_regions = list(state.regions) @@ -11986,6 +12157,7 @@ class _TuiTerminal: self.ended = False self.heard = 0.0 self.frame: list[tuple[str, str]] = [] + self.entered = False def enter(self) -> None: if os.name != "nt": @@ -12002,13 +12174,18 @@ class _TuiTerminal: mode[6][termios.VMIN] = 1 mode[6][termios.VTIME] = 0 termios.tcsetattr(self.fd, termios.TCSANOW, mode) + self.entered = True self.write(_SCREEN_ON) def leave(self) -> None: """The main screen and the mode found, the mode even when the write fails: a closed window answers EIO.""" try: - self.write(_SCREEN_OFF) + # Never entered (stopped while it started): the main screen and + # the cursor are as they were, nothing is written over them. + if getattr(self, "entered", True): + self.entered = False + self.write(_SCREEN_OFF) finally: if self.saved is not None: import termios # noqa: PLC0415 @@ -12173,24 +12350,31 @@ def _farewell_lines(state: TuiState) -> list[str]: def run_tui(args: argparse.Namespace) -> int: """The full-screen interface, until it closes: the exit code it ends with.""" - os_type = _INVOCATION.os_type or detect_os() - if os_type == "wsl": - _enter_wsl() - # The --from folders are read by each check, not here: indexing a large - # card held the first frame back. Only whether each can be read is asked. - folders = getattr(args, "from") or [] - setattr(args, "from", None) + flags = {"interrupt": False, "resized": False, "suspend": False, "resume": False, + "term": 0} + handlers: dict[int, object] = {} + + def on_stop(signum: int, _frame: object) -> None: + flags["term"] = flags["term"] or signum + + # Before the start, whose detection and pack opening take seconds on a + # handheld: a Start+Select then ended on the default action, without a + # word or the terminal put back. + for name in ("SIGTERM", "SIGHUP"): + number = getattr(signal, name, None) + if number is not None: + handlers[number] = signal.signal(number, on_stop) try: - _open_local_sources(args) - finally: - setattr(args, "from", folders) - for folder in folders: - problem = from_folder_problem(str(folder)) - if problem is not None: - print(f"Error: {folder} {problem[1]}: --from names the folder to copy BIOS " - "files from.", file=sys.stderr) - return 1 - state, effects = tui_start(args, os_type) + started = _tui_startup(args) + except BaseException: + _restore_handlers(handlers) + raise + if started is None: + _restore_handlers(handlers) + return 1 + state, effects = started + if flags["term"]: + state.exit_code = 128 + flags["term"] work = Path(tempfile.mkdtemp(prefix=TUI_WORK_PREFIX)) (work / TUI_OWNER).write_text(str(os.getpid()), encoding="utf-8") _prune_work_folders(work.parent, time.time()) @@ -12198,8 +12382,6 @@ def run_tui(args: argparse.Namespace) -> int: inbox: queue.Queue = queue.Queue() current: list[_EngineRun] = [] written: dict[str, Path] = {} - flags = {"interrupt": False, "resized": False, "suspend": False, "resume": False, - "term": 0} # Start+Select on a handheld is gptokeyb's pkill: SIGTERM, then SIGKILL # 3 s later. The install gets 2 s to say what it wrote. terminating = {"signal": 0, "deadline": 0.0} @@ -12207,7 +12389,6 @@ def run_tui(args: argparse.Namespace) -> int: terminal = _TuiTerminal(sys.stdin, sys.stdout) real_out, real_err = sys.stdout, sys.stderr buffer = io.StringIO() - handlers: dict[int, object] = {} def perform(todo: list[tuple], from_key: bool = True) -> None: for effect in todo: @@ -12251,9 +12432,6 @@ def run_tui(args: argparse.Namespace) -> int: def on_resize(_signum: int, _frame: object) -> None: flags["resized"] = True - def on_stop(signum: int, _frame: object) -> None: - flags["term"] = flags["term"] or signum - def on_suspend(_signum: int, _frame: object) -> None: flags["suspend"] = True @@ -12262,14 +12440,15 @@ def run_tui(args: argparse.Namespace) -> int: try: for name, handler in (("SIGINT", on_interrupt), ("SIGWINCH", on_resize), - ("SIGTERM", on_stop), ("SIGHUP", on_stop), ("SIGTSTP", on_suspend), ("SIGCONT", on_resume)): number = getattr(signal, name, None) if number is not None: handlers[number] = signal.signal(number, handler) sys.stdout = sys.stderr = buffer - terminal.enter() - perform(effects) + if state.exit_code is None: + # Stopped while it started: the screen never opens. + terminal.enter() + perform(effects) while state.exit_code is None: if flags["suspend"]: # Stopped from outside (kill -TSTP): the shell gets its @@ -12349,12 +12528,64 @@ def run_tui(args: argparse.Namespace) -> int: try: for line in lines: print(line, file=stream) + stream.flush() except OSError: - # The terminal is gone: the exit code is all that still reaches anyone. - return state.exit_code if state.exit_code is not None else 1 + # The terminal is gone (a closed window answers EIO, a closed pipe + # EPIPE): the exit code is all that still reaches anyone, and the + # flush at exit would replace it with 120. + _drop_closed_terminal() return state.exit_code if state.exit_code is not None else 1 +def _tui_startup(args: argparse.Namespace) -> tuple[TuiState, list[tuple]] | None: + """The local sources opened and the first screen chosen, None when a + --from folder cannot be read (said on stderr).""" + os_type = _INVOCATION.os_type or detect_os() + if os_type == "wsl": + _enter_wsl() + # The --from folders are read by each check, not here: indexing a large + # card held the first frame back. Only whether each can be read is asked. + folders = getattr(args, "from") or [] + setattr(args, "from", None) + try: + _open_local_sources(args) + finally: + setattr(args, "from", folders) + for folder in folders: + problem = from_folder_problem(str(folder)) + if problem is not None: + print(f"Error: {folder} {problem[1]}: --from names the folder to copy BIOS " + "files from.", file=sys.stderr) + return None + return tui_start(args, os_type) + + +def _restore_handlers(handlers: dict) -> None: + for number, previous in handlers.items(): + signal.signal(number, previous) + + +def _drop_closed_terminal() -> None: + """Point stdout and stderr at nothing once the terminal is gone, so what + they still hold is flushed somewhere at exit.""" + try: + null = os.open(os.devnull, os.O_WRONLY) + except OSError: + # No null device to point them at: the flush at exit fails as before. + return + try: + for stream in (sys.stdout, sys.stderr): + try: + fd = stream.fileno() + except (ValueError, AttributeError, io.UnsupportedOperation): + # A stream with no descriptor (a test's StringIO) has no + # terminal behind it to lose. + continue + os.dup2(null, fd) + finally: + os.close(null) + + def _tui_cleanup( terminal: _TuiTerminal, runs: list[_EngineRun], handlers: dict, state: TuiState, ) -> list[str]: diff --git a/install.sh b/install.sh index 0d16300b..539267b4 100755 --- a/install.sh +++ b/install.sh @@ -18,7 +18,7 @@ esac TEMP_INSTALLER="" TEMP_DIRECTORY="" DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/install.py" -DEFAULT_INSTALL_SHA256="ea2777500114b796bb4a23ea9e0dd0a15bc4499bb1289e6e426943beece48865" +DEFAULT_INSTALL_SHA256="e2cc5f073a8226fe26b0513ebd25f00de6f3f613d8c9851473c2d30b53356f48" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/tests/fixtures/tui/route-evmapy.txt b/tests/fixtures/tui/route-evmapy.txt new file mode 100644 index 00000000..ef4a7dee --- /dev/null +++ b/tests/fixtures/tui/route-evmapy.txt @@ -0,0 +1,1188 @@ +=== setup 40x12 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + A Continue B Quit +=== setup 40x15 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + emulator, not to ES-DE itself. + + + A Continue B Quit +=== setup 80x24 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each emulator, not to ES-DE itself. + + + + + + + + + + + + + + A Continue B Quit +=== platforms 40x12 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + 1-7 of 18 + A Choose B Back +=== platforms 40x15 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + 1-10 of 18 + A Choose B Back +=== platforms 80x24 + RetroBIOS: Which setup? + + Choose the setup that will use the BIOS files. + + Filter (with a keyboard) + > RetroArch muOS, JELOS, KOLOS...nic stock firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + MiSTer FPGA + RetroPie + AmberELEC PAN4ELEC + EmuELEC + KNULLI + MinUI + NextUI + MyMinUI + A Choose B Back +=== folder 40x12 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + A Use this folder B Back +=== folder 40x15 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + A Use this folder B Back +=== folder 80x24 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + + + + + + + + + + A Use this folder B Back +=== folder-card 40x12 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + A Browse B Back +=== folder-card 40x15 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + + + + A Browse B Back +=== folder-card 80x24 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the + one holding .system. + + > Browse for a folder + Type a path + + + + + + + + + + + + + + + + + A Browse B Back +=== browse 40x12 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + A Use this folder B Back +=== browse 40x15 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + A Use this folder B Back +=== browse 80x24 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + + + + + + + + + + A Use this folder B Back +=== path 40x12 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + A Use this folder B Back +=== path 40x15 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + + + + A Use this folder B Back +=== path 80x24 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ and variables are expanded. + + + + + + + + + + + + + + + + + + A Use this folder B Back +=== create 40x12 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + A Create B Back +=== create 40x15 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + + + + A Create B Back +=== create 80x24 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: /home/user + + > Create it and continue + Choose another folder + + + + + + + + + + + + + + + + + A Create B Back +=== checking 40x12 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + B Back +=== checking 40x15 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + + + + B Back +=== checking 80x24 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the folder... 0:04 + + + + + + + + + + + + + + + + + + B Back +=== plan 40x12 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install B Back Y Options +=== plan 40x15 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies + are kept in a backup folder. + + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install B Back Y Options +=== plan 80x24 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.config/retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies are kept in a backup folder. + + > Install + Choose systems + Copy the BIOS files from your old card or a folder + Options + Quit + + + + + + + + + + + A Install B Back Y Options +=== plan-in-place 40x12 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + A Close B Back Y Options +=== plan-in-place 40x15 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + A Close B Back Y Options +=== plan-in-place 80x24 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.config/retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + + + + + + + + + + A Close B Back Y Options +=== systems 40x12 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== systems 40x15 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== systems 80x24 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 1 file, 16.0 KB + [x] sony-playstation 3 files, 1.0 MB + [ ] nintendo-famicom-disk (ファミコン) in place + + + + + + + + + + + + + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== options 40x12 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + A Open B Back Left/Right Set +=== options 40x15 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + + Sets --region + + A Open B Back Left/Right Set +=== options 80x24 + RetroBIOS: Options + + Each row is an option of the command line, named on the right. + + > Preferred regions: North America, Japan --region + Game data: kept --no-game-data + Installed cores only: no (1 found) --installed-cores + Cores: all --core + Parallel downloads: 8 --jobs + BIOS folder: ~/.config/retroarch/system --dest + Copy from: none --from + Preview the file list --dry-run + Show the command line + + + + + + + + + + + A Open B Back Left/Right Set +=== regions 40x12 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + A Tick B Done Left/Right Order +=== regions 40x15 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + + + + A Tick B Done Left/Right Order +=== regions 80x24 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. Where a system has one BIOS per region, + the best one found is installed. + + > [1] North America + [ ] Japan + + + + + + + + + + + + + + + + + A Tick B Done Left/Right Order +=== cores 40x12 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + A Filter B Back X Tick +=== cores 40x15 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + A Filter B Back X Tick +=== cores 80x24 + RetroBIOS: Cores + + Tick the cores you use. With none ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + + + + + + + + + + + A Filter B Back X Tick +=== preview 40x12 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + A Back Up/Down Scroll +=== preview 40x15 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + A Back Up/Down Scroll +=== preview 80x24 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + + + + + + + + + + + A Back Up/Down Scroll +=== command 40x12 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + A Back +=== command 40x15 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + + + + A Back +=== command 80x24 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system --system nintendo-gba + + + + + + + + + + + + + + + + + + A Back +=== confirm 40x12 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + A Install B Back +=== confirm 40x15 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + A Install B Back +=== confirm 80x24 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.config/retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + + + + + + + + + + A Install B Back +=== installing 40x12 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + B Stop +=== installing 40x15 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + B Stop +=== installing 80x24 + RetroBIOS: Installing + + [####################################-----------------------------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + + + + + + + + + + B Stop +=== result 40x12 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + 1-4 of 8 + A Exit Up/Down Scroll +=== result 40x15 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + --restore + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + 1-5 of 8 + A Exit Up/Down Scroll +=== result 80x24 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py --restore + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + Same choice without questions: + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system + Details: /tmp/retrobios-x/install.log + + + + + + + + + + + A Exit Up/Down Scroll +=== error 40x12 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + > Choose another setup or folder + Quit + A Back B Back +=== error 40x15 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + A Back B Back +=== error 80x24 + RetroBIOS: Something went wrong + + Could not fetch the file list for retroarch after 3 attempts: [Errno -2] + Name or service not known + Address: https://raw.githubusercontent.com/Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + + + + + + + + + + + + + A Back B Back +=== quit 40x12 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + A Quit B Stay +=== quit 40x15 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + + + + A Quit B Stay +=== quit 80x24 + RetroBIOS: Quit? + + Quit without installing? Nothing was written. + + > Quit + Stay + + + + + + + + + + + + + + + + + + A Quit B Stay +=== help 40x12 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + Any key closes this help +=== help 40x15 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + + + + Any key closes this help +=== help 80x24 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: /home/user/.config/retroarch/system + + + + + + + + + + + + + + Any key closes this help diff --git a/tests/fixtures/tui/route-gptokeyb.txt b/tests/fixtures/tui/route-gptokeyb.txt new file mode 100644 index 00000000..43ada349 --- /dev/null +++ b/tests/fixtures/tui/route-gptokeyb.txt @@ -0,0 +1,1188 @@ +=== setup 40x12 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + A Continue B Quit +=== setup 40x15 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + emulator, not to ES-DE itself. + + + A Continue B Quit +=== setup 80x24 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each emulator, not to ES-DE itself. + + + + + + + + + + + + + + A Continue B Quit +=== platforms 40x12 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + 1-7 of 18 + A Choose B Back +=== platforms 40x15 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + 1-10 of 18 + A Choose B Back +=== platforms 80x24 + RetroBIOS: Which setup? + + Choose the setup that will use the BIOS files. + + Filter (with a keyboard) + > RetroArch muOS, JELOS, KOLOS...nic stock firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + MiSTer FPGA + RetroPie + AmberELEC PAN4ELEC + EmuELEC + KNULLI + MinUI + NextUI + MyMinUI + A Choose B Back +=== folder 40x12 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + A Use this folder B Back +=== folder 40x15 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + A Use this folder B Back +=== folder 80x24 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + + + + + + + + + + A Use this folder B Back +=== folder-card 40x12 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + A Browse B Back +=== folder-card 40x15 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + + + + A Browse B Back +=== folder-card 80x24 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the + one holding .system. + + > Browse for a folder + Type a path + + + + + + + + + + + + + + + + + A Browse B Back +=== browse 40x12 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + A Use this folder B Back +=== browse 40x15 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + A Use this folder B Back +=== browse 80x24 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + + + + + + + + + + A Use this folder B Back +=== path 40x12 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + A Use this folder B Back +=== path 40x15 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + + + + A Use this folder B Back +=== path 80x24 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ and variables are expanded. + + + + + + + + + + + + + + + + + + A Use this folder B Back +=== create 40x12 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + A Create B Back +=== create 40x15 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + + + + A Create B Back +=== create 80x24 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: /home/user + + > Create it and continue + Choose another folder + + + + + + + + + + + + + + + + + A Create B Back +=== checking 40x12 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + Start+Select Quit B Back +=== checking 40x15 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + + + + Start+Select Quit B Back +=== checking 80x24 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the folder... 0:04 + + + + + + + + + + + + + + + + + + Start+Select Quit B Back +=== plan 40x12 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install B Back Y Options +=== plan 40x15 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies + are kept in a backup folder. + + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install B Back Y Options +=== plan 80x24 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.config/retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies are kept in a backup folder. + + > Install + Choose systems + Copy the BIOS files from your old card or a folder + Options + Quit + + + + + + + + + + + A Install B Back Y Options +=== plan-in-place 40x12 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + A Close B Back Y Options +=== plan-in-place 40x15 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + A Close B Back Y Options +=== plan-in-place 80x24 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.config/retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + + + + + + + + + + A Close B Back Y Options +=== systems 40x12 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== systems 40x15 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== systems 80x24 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 1 file, 16.0 KB + [x] sony-playstation 3 files, 1.0 MB + [ ] nintendo-famicom-disk (ファミコン) in place + + + + + + + + + + + + + + + 2 of 3 systems: 4 files, 1.0 MB + A Continue B Back X Tick +=== options 40x12 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + A Open B Back Left/Right Set +=== options 40x15 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + + Sets --region + + A Open B Back Left/Right Set +=== options 80x24 + RetroBIOS: Options + + Each row is an option of the command line, named on the right. + + > Preferred regions: North America, Japan --region + Game data: kept --no-game-data + Installed cores only: no (1 found) --installed-cores + Cores: all --core + Parallel downloads: 8 --jobs + BIOS folder: ~/.config/retroarch/system --dest + Copy from: none --from + Preview the file list --dry-run + Show the command line + + + + + + + + + + + A Open B Back Left/Right Set +=== regions 40x12 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + A Tick B Done Left/Right Order +=== regions 40x15 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + + + + A Tick B Done Left/Right Order +=== regions 80x24 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. Where a system has one BIOS per region, + the best one found is installed. + + > [1] North America + [ ] Japan + + + + + + + + + + + + + + + + + A Tick B Done Left/Right Order +=== cores 40x12 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + A Filter B Back X Tick +=== cores 40x15 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + A Filter B Back X Tick +=== cores 80x24 + RetroBIOS: Cores + + Tick the cores you use. With none ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + + + + + + + + + + + A Filter B Back X Tick +=== preview 40x12 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + A Back Up/Down Scroll +=== preview 40x15 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + A Back Up/Down Scroll +=== preview 80x24 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + + + + + + + + + + + A Back Up/Down Scroll +=== command 40x12 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + A Back +=== command 40x15 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + + + + A Back +=== command 80x24 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system --system nintendo-gba + + + + + + + + + + + + + + + + + + A Back +=== confirm 40x12 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + A Install B Back +=== confirm 40x15 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + A Install B Back +=== confirm 80x24 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.config/retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + + + + + + + + + + A Install B Back +=== installing 40x12 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B or Start+Select stops. What is done + stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + B Stop +=== installing 40x15 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B or Start+Select stops. What is done + stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + B Stop +=== installing 80x24 + RetroBIOS: Installing + + [####################################-----------------------------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B or Start+Select stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + + + + + + + + + + B Stop +=== result 40x12 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + 1-4 of 8 + A Exit Up/Down Scroll +=== result 40x15 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + --restore + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + 1-5 of 8 + A Exit Up/Down Scroll +=== result 80x24 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py --restore + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + Same choice without questions: + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system + Details: /tmp/retrobios-x/install.log + + + + + + + + + + + A Exit Up/Down Scroll +=== error 40x12 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + > Choose another setup or folder + Quit + A Back B Back +=== error 40x15 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + A Back B Back +=== error 80x24 + RetroBIOS: Something went wrong + + Could not fetch the file list for retroarch after 3 attempts: [Errno -2] + Name or service not known + Address: https://raw.githubusercontent.com/Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + + + + + + + + + + + + + A Back B Back +=== quit 40x12 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + A Quit B Stay +=== quit 40x15 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + + + + A Quit B Stay +=== quit 80x24 + RetroBIOS: Quit? + + Quit without installing? Nothing was written. + + > Quit + Stay + + + + + + + + + + + + + + + + + + A Quit B Stay +=== help 40x12 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + Select, then Start Quit at once + ?, F1 This help + + + Any key closes this help +=== help 40x15 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + Select, then Start Quit at once + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + + + Any key closes this help +=== help 80x24 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + Y, Tab, o Options + B, Select, Esc Back + q, Ctrl-C Quit + Select, then Start Quit at once + ?, F1 This help + + BIOS folder: /home/user/.config/retroarch/system + + + + + + + + + + + + + Any key closes this help diff --git a/tests/fixtures/tui/route-keys.txt b/tests/fixtures/tui/route-keys.txt new file mode 100644 index 00000000..9c2e5336 --- /dev/null +++ b/tests/fixtures/tui/route-keys.txt @@ -0,0 +1,1134 @@ +=== setup 40x12 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + Enter Continue Esc Quit ? Help +=== setup 40x15 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + emulator, not to ES-DE itself. + + + Enter Continue Esc Quit ? Help +=== setup 80x24 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each emulator, not to ES-DE itself. + + + + + + + + + + + + + + Enter Continue Esc Quit ? Help +=== platforms 40x12 + RetroBIOS: Which setup? + + Filter (/ to type) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + 1-7 of 18 + Enter Choose Esc Back / Filter +=== platforms 40x15 + RetroBIOS: Which setup? + + Filter (/ to type) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + 1-10 of 18 + Enter Choose Esc Back / Filter +=== platforms 80x24 + RetroBIOS: Which setup? + + Choose the setup that will use the BIOS files. + + Filter (/ to type) + > RetroArch muOS, JELOS, KOLOS...nic stock firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + MiSTer FPGA + RetroPie + AmberELEC PAN4ELEC + EmuELEC + KNULLI + MinUI + NextUI + MyMinUI + Enter Choose Esc Back / Filter ? Help +=== folder 40x12 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + Enter Use this folder Esc Back +=== folder 40x15 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + Enter Use this folder Esc Back +=== folder 80x24 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + + + + + + + + + + Enter Use this folder Esc Back ? Help +=== folder-card 40x12 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + Enter Browse Esc Back ? Help +=== folder-card 40x15 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + + + + Enter Browse Esc Back ? Help +=== folder-card 80x24 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the + one holding .system. + + > Browse for a folder + Type a path + + + + + + + + + + + + + + + + + Enter Browse Esc Back ? Help +=== browse 40x12 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + Enter Use this folder Esc Back +=== browse 40x15 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + Enter Use this folder Esc Back +=== browse 80x24 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + + + + + + + + + + Enter Use this folder Esc Back ? Help +=== create 40x12 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + Enter Create Esc Back +=== create 40x15 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + + + + Enter Create Esc Back +=== create 80x24 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: /home/user + + > Create it and continue + Choose another folder + + + + + + + + + + + + + + + + + Enter Create Esc Back +=== checking 40x12 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + Ctrl-C Quit Esc Back +=== checking 40x15 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + + + + Ctrl-C Quit Esc Back +=== checking 80x24 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the folder... 0:04 + + + + + + + + + + + + + + + + + + Ctrl-C Quit Esc Back +=== plan 40x12 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + Enter Install Esc Back o Options +=== plan 40x15 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies + are kept in a backup folder. + + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + Enter Install Esc Back o Options +=== plan 80x24 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.config/retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies are kept in a backup folder. + + > Install + Choose systems + Copy the BIOS files from your old card or a folder + Options + Quit + + + + + + + + + + + Enter Install Esc Back o Options ? Help +=== plan-in-place 40x12 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + Enter Close Esc Back o Options +=== plan-in-place 40x15 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + Enter Close Esc Back o Options +=== plan-in-place 80x24 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.config/retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + + + + + + + + + + Enter Close Esc Back o Options ? Help +=== systems 40x12 + RetroBIOS: Choose systems + + All + None + Filter (/ to type) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + 2 of 3 systems: 4 files, 1.0 MB + Enter Continue Esc Back Space Tick +=== systems 40x15 + RetroBIOS: Choose systems + + All + None + Filter (/ to type) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Enter Continue Esc Back Space Tick +=== systems 80x24 + RetroBIOS: Choose systems + + All + None + Filter (/ to type) + > [x] nintendo-gba (gba) 1 file, 16.0 KB + [x] sony-playstation 3 files, 1.0 MB + [ ] nintendo-famicom-disk (ファミコン) in place + + + + + + + + + + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Enter Continue Esc Back Space Tick ? Help +=== options 40x12 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + Enter Open Esc Back <> Set ? Help +=== options 40x15 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + + Sets --region + + Enter Open Esc Back <> Set ? Help +=== options 80x24 + RetroBIOS: Options + + Each row is an option of the command line, named on the right. + + > Preferred regions: North America, Japan --region + Game data: kept --no-game-data + Installed cores only: no (1 found) --installed-cores + Cores: all --core + Parallel downloads: 8 --jobs + BIOS folder: ~/.config/retroarch/system --dest + Copy from: none --from + Preview the file list --dry-run + Show the command line + + + + + + + + + + + Enter Open Esc Back Left/Right Set ? Help +=== regions 40x12 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + Enter Tick Esc Done <> Order ? Help +=== regions 40x15 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + + + + Enter Tick Esc Done <> Order ? Help +=== regions 80x24 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. Where a system has one BIOS per region, + the best one found is installed. + + > [1] North America + [ ] Japan + + + + + + + + + + + + + + + + + Enter Tick Esc Done Left/Right Order ? Help +=== cores 40x12 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (/ to type) + [ ] mesen + [x] pcsx_rearmed + + + + Enter Filter Esc Back Space Tick +=== cores 40x15 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (/ to type) + [ ] mesen + [x] pcsx_rearmed + + + + + + + Enter Filter Esc Back Space Tick +=== cores 80x24 + RetroBIOS: Cores + + Tick the cores you use. With none ticked, every core's files are kept. + + > Filter (/ to type) + [ ] mesen + [x] pcsx_rearmed + + + + + + + + + + + + + + + + + Enter Filter Esc Back Space Tick ? Help +=== preview 40x12 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + Enter Back Up/Down Scroll +=== preview 40x15 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + Enter Back Up/Down Scroll +=== preview 80x24 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + + + + + + + + + + + Enter Back Up/Down Scroll +=== command 40x12 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + Enter Back +=== command 40x15 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + + + + Enter Back +=== command 80x24 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system --system nintendo-gba + + + + + + + + + + + + + + + + + + Enter Back +=== confirm 40x12 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + Enter Install Esc Back ? Help +=== confirm 40x15 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + Enter Install Esc Back ? Help +=== confirm 80x24 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.config/retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + + + + + + + + + + Enter Install Esc Back ? Help +=== installing 40x12 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + Esc or Ctrl-C stops. What is done + stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + Esc Stop ? Help +=== installing 40x15 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + Esc or Ctrl-C stops. What is done + stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + Esc Stop ? Help +=== installing 80x24 + RetroBIOS: Installing + + [####################################-----------------------------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + Esc or Ctrl-C stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + + + + + + + + + + Esc Stop ? Help +=== result 40x12 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + 1-4 of 8 + Enter Exit Up/Down Scroll +=== result 40x15 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + --restore + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + 1-5 of 8 + Enter Exit Up/Down Scroll +=== result 80x24 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py --restore + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + Same choice without questions: + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system + Details: /tmp/retrobios-x/install.log + + + + + + + + + + + Enter Exit Up/Down Scroll +=== error 40x12 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + > Choose another setup or folder + Quit + Enter Back Esc Back +=== error 40x15 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + Enter Back Esc Back +=== error 80x24 + RetroBIOS: Something went wrong + + Could not fetch the file list for retroarch after 3 attempts: [Errno -2] + Name or service not known + Address: https://raw.githubusercontent.com/Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + + + + + + + + + + + + + Enter Back Esc Back +=== quit 40x12 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + Enter Quit Esc Stay +=== quit 40x15 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + + + + Enter Quit Esc Stay +=== quit 80x24 + RetroBIOS: Quit? + + Quit without installing? Nothing was written. + + > Quit + Stay + + + + + + + + + + + + + + + + + + Enter Quit Esc Stay +=== help 40x12 + RetroBIOS: Keys + + Up/Down, k/j Move + Enter Install + o, Tab Options + Esc, Backspace Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + Any key closes this help +=== help 40x15 + RetroBIOS: Keys + + Up/Down, k/j Move + Enter Install + o, Tab Options + Esc, Backspace Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + + + + Any key closes this help +=== help 80x24 + RetroBIOS: Keys + + Up/Down, k/j Move + Enter Install + o, Tab Options + Esc, Backspace Back + q, Ctrl-C Quit + ?, F1 This help + + BIOS folder: /home/user/.config/retroarch/system + + + + + + + + + + + + + + Any key closes this help diff --git a/tests/fixtures/tui/route-miyoo.txt b/tests/fixtures/tui/route-miyoo.txt new file mode 100644 index 00000000..259b7828 --- /dev/null +++ b/tests/fixtures/tui/route-miyoo.txt @@ -0,0 +1,1188 @@ +=== setup 40x12 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + A Continue R2 Quit +=== setup 40x15 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + emulator, not to ES-DE itself. + + + A Continue R2 Quit +=== setup 80x24 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each emulator, not to ES-DE itself. + + + + + + + + + + + + + + A Continue R2 Quit +=== platforms 40x12 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + 1-7 of 18 + A Choose R2 Back +=== platforms 40x15 + RetroBIOS: Which setup? + + Filter (with a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + 1-10 of 18 + A Choose R2 Back +=== platforms 80x24 + RetroBIOS: Which setup? + + Choose the setup that will use the BIOS files. + + Filter (with a keyboard) + > RetroArch muOS, JELOS, KOLOS...nic stock firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + MiSTer FPGA + RetroPie + AmberELEC PAN4ELEC + EmuELEC + KNULLI + MinUI + NextUI + MyMinUI + A Choose R2 Back +=== folder 40x12 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + A Use this folder R2 Back +=== folder 40x15 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + A Use this folder R2 Back +=== folder 80x24 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + + + + + + + + + + A Use this folder R2 Back +=== folder-card 40x12 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + A Browse R2 Back +=== folder-card 40x15 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + + + + A Browse R2 Back +=== folder-card 80x24 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the + one holding .system. + + > Browse for a folder + Type a path + + + + + + + + + + + + + + + + + A Browse R2 Back +=== browse 40x12 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + A Use this folder R2 Back +=== browse 40x15 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + A Use this folder R2 Back +=== browse 80x24 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + + + + + + + + + + A Use this folder R2 Back +=== path 40x12 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + Start Use this folder +=== path 40x15 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + + + + Start Use this folder +=== path 80x24 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ and variables are expanded. + + + + + + + + + + + + + + + + + + Start Use this folder +=== create 40x12 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + A Create R2 Back +=== create 40x15 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + + + + A Create R2 Back +=== create 80x24 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: /home/user + + > Create it and continue + Choose another folder + + + + + + + + + + + + + + + + + A Create R2 Back +=== checking 40x12 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + R2 Back +=== checking 40x15 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + + + + R2 Back +=== checking 80x24 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the folder... 0:04 + + + + + + + + + + + + + + + + + + R2 Back +=== plan 40x12 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install R2 Back L2 Options +=== plan 40x15 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies + are kept in a backup folder. + + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + A Install R2 Back L2 Options +=== plan 80x24 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.config/retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies are kept in a backup folder. + + > Install + Choose systems + Copy the BIOS files from your old card or a folder + Options + Quit + + + + + + + + + + + A Install R2 Back L2 Options +=== plan-in-place 40x12 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + A Close R2 Back L2 Options +=== plan-in-place 40x15 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + A Close R2 Back L2 Options +=== plan-in-place 80x24 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.config/retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + + + + + + + + + + A Close R2 Back L2 Options +=== systems 40x12 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue R2 Back A Tick +=== systems 40x15 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue R2 Back A Tick +=== systems 80x24 + RetroBIOS: Choose systems + + All + None + Filter (with a keyboard) + > [x] nintendo-gba (gba) 1 file, 16.0 KB + [x] sony-playstation 3 files, 1.0 MB + [ ] nintendo-famicom-disk (ファミコン) in place + + + + + + + + + + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue R2 Back A Tick +=== options 40x12 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + A Open R2 Back Left/Right Set +=== options 40x15 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + + Sets --region + + A Open R2 Back Left/Right Set +=== options 80x24 + RetroBIOS: Options + + Each row is an option of the command line, named on the right. + + > Preferred regions: North America, Japan --region + Game data: kept --no-game-data + Installed cores only: no (1 found) --installed-cores + Cores: all --core + Parallel downloads: 8 --jobs + BIOS folder: ~/.config/retroarch/system --dest + Copy from: none --from + Preview the file list --dry-run + Show the command line + + + + + + + + + + + A Open R2 Back Left/Right Set +=== regions 40x12 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + A Tick R2 Done Left/Right Order +=== regions 40x15 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + + + + A Tick R2 Done Left/Right Order +=== regions 80x24 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. Where a system has one BIOS per region, + the best one found is installed. + + > [1] North America + [ ] Japan + + + + + + + + + + + + + + + + + A Tick R2 Done Left/Right Order +=== cores 40x12 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + Start Filter R2 Back A Tick +=== cores 40x15 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + Start Filter R2 Back A Tick +=== cores 80x24 + RetroBIOS: Cores + + Tick the cores you use. With none ticked, every core's files are kept. + + > Filter (with a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + + + + + + + + + + + Start Filter R2 Back A Tick +=== preview 40x12 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + A Back Up/Down Scroll +=== preview 40x15 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + A Back Up/Down Scroll +=== preview 80x24 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + + + + + + + + + + + A Back Up/Down Scroll +=== command 40x12 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + A Back +=== command 40x15 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + + + + A Back +=== command 80x24 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system --system nintendo-gba + + + + + + + + + + + + + + + + + + A Back +=== confirm 40x12 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + A Install R2 Back +=== confirm 40x15 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + A Install R2 Back +=== confirm 80x24 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.config/retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + + + + + + + + + + A Install R2 Back +=== installing 40x12 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + R2 stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + R2 Stop +=== installing 40x15 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + R2 stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + R2 Stop +=== installing 80x24 + RetroBIOS: Installing + + [####################################-----------------------------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + R2 stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + + + + + + + + + + R2 Stop +=== result 40x12 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + 1-4 of 8 + A Exit Up/Down Scroll +=== result 40x15 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + --restore + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + 1-5 of 8 + A Exit Up/Down Scroll +=== result 80x24 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py --restore + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + Same choice without questions: + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system + Details: /tmp/retrobios-x/install.log + + + + + + + + + + + A Exit Up/Down Scroll +=== error 40x12 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + > Choose another setup or folder + Quit + A Back R2 Back +=== error 40x15 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + A Back R2 Back +=== error 80x24 + RetroBIOS: Something went wrong + + Could not fetch the file list for retroarch after 3 attempts: [Errno -2] + Name or service not known + Address: https://raw.githubusercontent.com/Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + + + + + + + + + + + + + A Back R2 Back +=== quit 40x12 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + A Quit R2 Stay +=== quit 40x15 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + + + + A Quit R2 Stay +=== quit 80x24 + RetroBIOS: Quit? + + Quit without installing? Nothing was written. + + > Quit + Stay + + + + + + + + + + + + + + + + + + A Quit R2 Stay +=== help 40x12 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + L2, Tab, o Options + R2, Esc, Backspace Back + q, Ctrl-C Quit + B Sends no key here + ?, F1 This help + + + Any key closes this help +=== help 40x15 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + L2, Tab, o Options + R2, Esc, Backspace Back + q, Ctrl-C Quit + B Sends no key here + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + + + Any key closes this help +=== help 80x24 + RetroBIOS: Keys + + D-pad, Up/Down Move + A, Start, Enter Install + L2, Tab, o Options + R2, Esc, Backspace Back + q, Ctrl-C Quit + B Sends no key here + ?, F1 This help + + BIOS folder: /home/user/.config/retroarch/system + + + + + + + + + + + + + Any key closes this help diff --git a/tests/fixtures/tui/route-muxterm.txt b/tests/fixtures/tui/route-muxterm.txt new file mode 100644 index 00000000..f23f8568 --- /dev/null +++ b/tests/fixtures/tui/route-muxterm.txt @@ -0,0 +1,1188 @@ +=== setup 40x12 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + Start Continue B Quit +=== setup 40x15 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: + /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each + emulator, not to ES-DE itself. + + + Start Continue B Quit +=== setup 80x24 + RetroBIOS: Where do you play? + + Found on this machine: + + > RetroArch + KNULLI (Batocera files) + Another setup or folder + + BIOS folder: /home/user/.config/retroarch/system + ES-DE found: its BIOS files go to each emulator, not to ES-DE itself. + + + + + + + + + + + + + + Start Continue B Quit +=== platforms 40x12 + RetroBIOS: Which setup? + + Filter (Select shows a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + 1-7 of 18 + Start Choose B Back +=== platforms 40x15 + RetroBIOS: Which setup? + + Filter (Select shows a keyboard) + > RetroArch muOS, JE...firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + 1-10 of 18 + Start Choose B Back +=== platforms 80x24 + RetroBIOS: Which setup? + + Choose the setup that will use the BIOS files. + + Filter (Select shows a keyboard) + > RetroArch muOS, JELOS, KOLOS...nic stock firmware + Batocera + Recalbox + RetroBat + EmuDeck + Lakka + RetroDECK + ROCKNIX + RomM + BizHawk + MiSTer FPGA + RetroPie + AmberELEC PAN4ELEC + EmuELEC + KNULLI + MinUI + NextUI + MyMinUI + Start Choose B Back +=== folder 40x12 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + Start Use this folder B Back +=== folder 40x15 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + Start Use this folder B Back +=== folder 80x24 + RetroBIOS: Which folder? + + RetroArch reads its BIOS files from: + /home/user/.config/retroarch/system + + > Use this folder + Browse for a folder + Type a path + + + + + + + + + + + + + + + + Start Use this folder B Back +=== folder-card 40x12 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + Start Browse B Back +=== folder-card 40x15 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD + card. Choose the card's root folder, + the one holding .system. + + > Browse for a folder + Type a path + + + + + + + Start Browse B Back +=== folder-card 80x24 + RetroBIOS: Where is the SD card? + + NextUI keeps its BIOS files on its SD card. Choose the card's root folder, the + one holding .system. + + > Browse for a folder + Type a path + + + + + + + + + + + + + + + + + Start Browse B Back +=== browse 40x12 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + Start Use this folder B Back +=== browse 40x15 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + Start Use this folder B Back +=== browse 80x24 + RetroBIOS: Browse for a folder + + /home/user/.config/retroarch + + > Use this folder + .. + /media/user/SDCARD (NextUI card) + cores/ + system/ + + + + + + + + + + + + + + + Start Use this folder B Back +=== path 40x12 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + Start Use this folder Select Keyboard +=== path 40x15 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ + and variables are expanded. + + + + + + + + Start Use this folder Select Keyboard +=== path 80x24 + RetroBIOS: Type a path + + The folder for RetroArch: + > ~/bios_ + + Ctrl-U clears. Quotes are removed, ~ and variables are expanded. + + + + + + + + + + + + + + + + + + Start Use this folder Select Keyboard +=== create 40x12 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + Start Create B Back +=== create 40x15 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: + /home/user + + > Create it and continue + Choose another folder + + + + + + + Start Create B Back +=== create 80x24 + RetroBIOS: Create the folder? + + /home/user/bios/new does not exist. + The nearest folder that does: /home/user + + > Create it and continue + Choose another folder + + + + + + + + + + + + + + + + + Start Create B Back +=== checking 40x12 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + Menu Quit B Back +=== checking 40x15 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the + folder... 0:04 + + + + + + + + Menu Quit B Back +=== checking 80x24 + RetroBIOS: Checking + + RetroArch: ~/.config/retroarch/system + + File list: 5 files. + Reading the files already in the folder... 0:04 + + + + + + + + + + + + + + + + + + Menu Quit B Back +=== plan 40x12 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + Start Install B Back +=== plan 40x15 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.co.../retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies + are kept in a backup folder. + + > Install + Choose systems + Copy from your old card or a folder + Options + Quit + Start Install B Back +=== plan 80x24 + RetroBIOS: RetroArch + + 4 files to download, 1.0 MB + BIOS folder: ~/.config/retroarch/system + 1 in place, 3 missing, 1 wrong copy + Free space: 50.0 GB + They are replaced, and the old copies are kept in a backup folder. + + > Install + Choose systems + Copy the BIOS files from your old card or a folder + Options + Quit + + + + + + + + + + + Start Install B Back +=== plan-in-place 40x12 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + Start Close B Back +=== plan-in-place 40x15 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.co.../retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + Start Close B Back +=== plan-in-place 80x24 + RetroBIOS: RetroArch + + Everything is in place + BIOS folder: ~/.config/retroarch/system + 5 in place, 0 missing, 0 wrong copies + Free space: 50.0 GB + + > Close + Options + + + + + + + + + + + + + + + Start Close B Back +=== systems 40x12 + RetroBIOS: Choose systems + + All + None + Filter (Select shows a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue B Back Y Tick +=== systems 40x15 + RetroBIOS: Choose systems + + All + None + Filter (Select shows a keyboard) + > [x] nintendo-gba (gba) 16.0 KB + [x] sony-playstation 1.0 MB + [ ] nintendo-famicom-disk in place + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue B Back Y Tick +=== systems 80x24 + RetroBIOS: Choose systems + + All + None + Filter (Select shows a keyboard) + > [x] nintendo-gba (gba) 1 file, 16.0 KB + [x] sony-playstation 3 files, 1.0 MB + [ ] nintendo-famicom-disk (ファミコン) in place + + + + + + + + + + + + + + + 2 of 3 systems: 4 files, 1.0 MB + Start Continue B Back Y Tick +=== options 40x12 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + Start Open B Back Left/Right Set +=== options 40x15 + RetroBIOS: Options + + > Preferred regions: North America, Ja + Game data: kept + Installed cores only: no (1 found) + Cores: all + Parallel downloads: 8 + BIOS folder: ~/.config...ch/system + Copy from: none + Preview the file list + Show the command line + + Sets --region + + Start Open B Back Left/Right Set +=== options 80x24 + RetroBIOS: Options + + Each row is an option of the command line, named on the right. + + > Preferred regions: North America, Japan --region + Game data: kept --no-game-data + Installed cores only: no (1 found) --installed-cores + Cores: all --core + Parallel downloads: 8 --jobs + BIOS folder: ~/.config/retroarch/system --dest + Copy from: none --from + Preview the file list --dry-run + Show the command line + + + + + + + + + + + Start Open B Back Left/Right Set +=== regions 40x12 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + Start Tick B Done Left/Right Order +=== regions 40x15 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. + Where a system has one BIOS per + region, the best one found is + installed. + + > [1] North America + [ ] Japan + + + + + + Start Tick B Done Left/Right Order +=== regions 80x24 + RetroBIOS: Preferred regions + + Tick the regions to keep, best first. Where a system has one BIOS per region, + the best one found is installed. + + > [1] North America + [ ] Japan + + + + + + + + + + + + + + + + + Start Tick B Done Left/Right Order +=== cores 40x12 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (Select shows a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + Start Filter B Back Y Tick +=== cores 40x15 + RetroBIOS: Cores + + Tick the cores you use. With none + ticked, every core's files are kept. + + > Filter (Select shows a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + Start Filter B Back Y Tick +=== cores 80x24 + RetroBIOS: Cores + + Tick the cores you use. With none ticked, every core's files are kept. + + > Filter (Select shows a keyboard) + [ ] mesen + [x] pcsx_rearmed + + + + + + + + + + + + + + + + + Start Filter B Back Y Tick +=== preview 40x12 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + Start Back Up/Down Scroll +=== preview 40x15 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing + is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + Start Back Up/Down Scroll +=== preview 80x24 + RetroBIOS: Preview + + 4 files to download, 1.0 MB. Nothing is written. + + missing 512.0 KB scph5500.bin + wrong copy 512.0 KB scph5501.bin + missing 4 B pcsx/extra.bin + missing 16.0 KB gba_bios.bin + + + + + + + + + + + + + + + + Start Back Up/Down Scroll +=== command 40x12 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + Start Back +=== command 40x15 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform + retroarch --dest + /home/user/.config/retroarch/system + --system nintendo-gba + + + + + + + Start Back +=== command 80x24 + RetroBIOS: Command line + + The same choice without questions: + + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system --system nintendo-gba + + + + + + + + + + + + + + + + + + Start Back +=== confirm 40x12 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + Start Install B Back +=== confirm 40x15 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.co.../retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + Start Install B Back +=== confirm 80x24 + RetroBIOS: Confirm + + 1 file to download, 16.0 KB + BIOS folder: ~/.config/retroarch/system + 0 in place, 1 missing, 0 wrong copies + Free space: 50.0 GB + + > Install + Back + + + + + + + + + + + + + + + Start Install B Back +=== installing 40x12 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + B Stop +=== installing 40x15 + RetroBIOS: Installing + + [###############----------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + B Stop +=== installing 80x24 + RetroBIOS: Installing + + [####################################-----------------------------------] 50% + 528.0 KB of 1.0 MB + 128.9 KB/s, 0:04 left + 2 of 4 files + B stops. What is done stays done. + + FAILED scph5500.bin (HTTP 404) + ok gba_bios.bin + + + + + + + + + + + + + + B Stop +=== result 40x12 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + 1-4 of 8 + Start Exit Up/Down Scroll +=== result 40x15 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: + /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py + --restore + /home/user/.config/retroarch/ + retrobios-backup/2026-10-23_1200 + 1-5 of 8 + Start Exit Up/Down Scroll +=== result 80x24 + RetroBIOS: Finished with errors + + 3 installed, 1 failed. + BIOS folder: /home/user/.config/retroarch/system + failed: scph5500.bin (HTTP 404) + The files it replaced are kept in + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + To put them back: python3 install.py --restore + /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200 + Same choice without questions: + python3 install.py --platform retroarch --dest + /home/user/.config/retroarch/system + Details: /tmp/retrobios-x/install.log + + + + + + + + + + + Start Exit Up/Down Scroll +=== error 40x12 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + > Choose another setup or folder + Quit + Start Back B Back +=== error 40x15 + RetroBIOS: Something went wrong + + Could not fetch the file list for + retroarch after 3 attempts: [Errno + -2] Name or service not known + Address: + https://raw.githubusercontent.com/ + Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + Start Back B Back +=== error 80x24 + RetroBIOS: Something went wrong + + Could not fetch the file list for retroarch after 3 attempts: [Errno -2] + Name or service not known + Address: https://raw.githubusercontent.com/Abdess/retrobios/main/install/ + retroarch.json + + > Choose another setup or folder + Quit + + + + + + + + + + + + + + + Start Back B Back +=== quit 40x12 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + Start Quit B Stay +=== quit 40x15 + RetroBIOS: Quit? + + Quit without installing? Nothing was + written. + + > Quit + Stay + + + + + + + + Start Quit B Stay +=== quit 80x24 + RetroBIOS: Quit? + + Quit without installing? Nothing was written. + + > Quit + Stay + + + + + + + + + + + + + + + + + + Start Quit B Stay +=== help 40x12 + RetroBIOS: Keys + + D-pad, Up/Down Move + Start, Enter Install + o, Tab Options + B, Esc, Backspace Back + q, Ctrl-C Quit + Select Keyboard on or off + Menu Close the terminal + ?, F1 This help + + Any key closes this help +=== help 40x15 + RetroBIOS: Keys + + D-pad, Up/Down Move + Start, Enter Install + o, Tab Options + B, Esc, Backspace Back + q, Ctrl-C Quit + Select Keyboard on or off + Menu Close the terminal + ?, F1 This help + + BIOS folder: + /home/user/.config/retroarch/system + + Any key closes this help +=== help 80x24 + RetroBIOS: Keys + + D-pad, Up/Down Move + Start, Enter Install + o, Tab Options + B, Esc, Backspace Back + q, Ctrl-C Quit + Select Keyboard on or off + Menu Close the terminal + ?, F1 This help + + BIOS folder: /home/user/.config/retroarch/system + + + + + + + + + + + + Any key closes this help diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index 6a907996..392ff1b9 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -56,6 +56,8 @@ except ImportError: GOLDEN = REPO_ROOT / "tests" / "fixtures" / "tui" SIZES = ((40, 12), (40, 15), (80, 24), (120, 40)) +# RETROBIOS_PAD's values, and "keys" for a desktop that sets none. +ROUTES = ("keys", "gptokeyb", "evmapy", "muxterm", "miyoo") UP, DOWN, RIGHT, LEFT = "\x1b[A", "\x1b[B", "\x1b[C", "\x1b[D" Key = install.TuiKey @@ -940,6 +942,31 @@ class Views(unittest.TestCase): path.write_text(text, encoding="utf-8") self.assertEqual(text, path.read_text(encoding="utf-8")) + def test_goldens_per_route(self): + """Each input route at the handheld sizes and 80x24, kept only where a + screen reads differently from the both-names form.""" + update = os.environ.get("RETROBIOS_UPDATE_GOLDENS") == "1" + for route in ROUTES: + sections = [] + for name, state in golden_states().items(): + state.pad = route + for cols, rows in SIZES[:3]: + with self.subTest(route=route, screen=name, size=f"{cols}x{rows}"): + state.cols, state.rows = cols, rows + lines = install.tui_view(state, cols, rows) + for line in lines: + self.assertLessEqual(install.cell_width(line), cols, line) + self.assertLess(install.cell_width(lines[-1]), cols) + text = "\n".join(line.rstrip() for line in lines) + "\n" + both = (GOLDEN / f"{name}-{cols}x{rows}.txt").read_text(encoding="utf-8") + if text != both: + sections.append(f"=== {name} {cols}x{rows}\n{text}") + path = GOLDEN / f"route-{route}.txt" + if update: + path.write_text("".join(sections), encoding="utf-8") + with self.subTest(route=route): + self.assertEqual("".join(sections), path.read_text(encoding="utf-8")) + def test_the_hint_bar_names_the_primary_action_first(self): for name, state in golden_states().items(): with self.subTest(screen=name): @@ -954,6 +981,163 @@ class Views(unittest.TestCase): self.assertTrue(focused[0].lstrip().startswith("> RetroArch")) +def _bar_keys(line: str) -> set: + """The key each entry of a hint bar names: its first word.""" + return {part.split(" ", 1)[0] for part in line.strip().split(" ") if part} + + +# The keys each route has, as the bar may name them. "Any" opens "Any key +# closes this help", <> and ^v are the narrow forms of Left/Right, Up/Down. +ROUTE_KEYS = { + "keys": {"Enter", "Esc", "Space", "o", "?", "/", "Ctrl-C", "Ctrl-U"}, + "gptokeyb": {"A", "B", "X", "Y", "Start+Select"}, + "evmapy": {"A", "B", "X", "Y"}, + "muxterm": {"Start", "B", "Y", "Menu", "Select"}, + "miyoo": {"A", "Start", "R2", "L2"}, +} +EVERY_ROUTE_HAS = {"Left/Right", "Up/Down", "<>", "^v", "Any"} + + +class PadRoutes(unittest.TestCase): + """The bar names the buttons the launcher maps, and a desktop's keys.""" + + def test_the_route_from_the_environment(self): + route = install.tui_pad_route + for value in install.TUI_PAD_ROUTES: + self.assertEqual(route("linux", {"RETROBIOS_PAD": value, "DISPLAY": ":0"}), value) + self.assertEqual(route("linux", {"RETROBIOS_PAD": " MiYoo "}), "miyoo") + self.assertEqual(route("linux", {"RETROBIOS_PAD": "dualshock"}), "both") + for os_type in ("windows", "darwin", "wsl"): + self.assertEqual(route(os_type, {}), "keys", os_type) + for env in ({"DISPLAY": ":0", "TERM": "xterm-256color"}, {"WAYLAND_DISPLAY": "w-0"}, + {"SSH_CONNECTION": "10.0.0.2 5 10.0.0.3 22"}, {"SSH_TTY": "/dev/pts/1"}): + self.assertEqual(route("linux", env), "keys", env) + # SteamOS game mode and anything Steam starts: Steam Input may be + # all there is. A console with nothing named, or Android: unknown. + for os_type, env in (("linux", {"SteamGamepadUI": "1", "DISPLAY": ":0"}), + ("linux", {"GAMESCOPE_WAYLAND_DISPLAY": "gamescope-0", + "WAYLAND_DISPLAY": "w-0"}), + ("windows", {"SteamGameId": "123"}), + ("linux", {"SteamDeck": "1", "DISPLAY": ":0"}), + ("linux", {"TERM": "linux", "SSH_TTY": "/dev/pts/0"}), + ("linux", {"TERM": "linux"}), ("linux", {}), + ("android", {"DISPLAY": ":0"})): + self.assertEqual(route(os_type, env), "both", env) + + def test_the_launchers_values_are_the_routes(self): + tui = (REPO_ROOT / "ports" / "shared" / "tui.txt").read_text(encoding="utf-8") + named = set(re.findall(r'RB_PAD="(\w+)"', tui)) + for launcher in REPO_ROOT.glob("ports/*/*.sh"): + named |= set(re.findall(r'RB_PAD="(\w+)"', launcher.read_text(encoding="utf-8"))) + self.assertLessEqual(named, set(install.TUI_PAD_ROUTES)) + self.assertEqual(set(ROUTES) - {"keys"}, set(install.TUI_PAD_ROUTES)) + + def test_the_plan_bar_of_each_route(self): + expected = { + "both": "A/Enter Install B/Esc Back Y/o Options ? Help", + "keys": "Enter Install Esc Back o Options ? Help", + "gptokeyb": "A Install B Back Y Options", + "evmapy": "A Install B Back Y Options", + "muxterm": "Start Install B Back", + "miyoo": "A Install R2 Back L2 Options", + } + for route, bar in expected.items(): + with self.subTest(route=route): + state = checked_state() + state.pad = route + self.assertEqual(install.tui_view(state, 80, 24)[-1].strip(), bar) + + def test_no_bar_names_a_key_its_route_lacks(self): + for route, have in ROUTE_KEYS.items(): + for name, state in golden_states().items(): + state.pad = route + for cols, rows in SIZES: + with self.subTest(route=route, screen=name, size=f"{cols}x{rows}"): + state.cols, state.rows = cols, rows + bar = install.tui_view(state, cols, rows)[-1] + self.assertLessEqual(_bar_keys(bar) - EVERY_ROUTE_HAS, have, bar) + + def test_the_install_says_what_stops_it_there(self): + expected = { + "both": "B, Ctrl-C or Start+Select stops.", "keys": "Esc or Ctrl-C stops.", + "gptokeyb": "B or Start+Select stops.", "evmapy": "B stops.", + "muxterm": "B stops.", "miyoo": "R2 stops.", + } + for route, words in expected.items(): + with self.subTest(route=route): + state = golden_states()["installing"] + state.pad = route + text = "\n".join(install.tui_view(state, 80, 24)) + self.assertIn(f"{words} What is done stays done.", text) + state.stopping = True + again = {"both": "B or Ctrl-C", "keys": "Esc or Ctrl-C", "miyoo": "R2"} + self.assertIn(f"Press {again.get(route, 'B')} again to stop now.", + " ".join(" ".join(install.tui_view(state, 80, 24)).split())) + + def test_the_help_lists_the_buttons_and_the_keys(self): + def help_text(route: str, screen: str = "plan") -> str: + state = golden_states()[screen] + state.pad, state.help = route, True + return "\n".join(line.rstrip() for line in install.tui_view(state, 80, 24)) + + gptk = help_text("gptokeyb") + for words in ("D-pad, Up/Down", "A, Start, Enter", "B, Select, Esc", "Y, Tab, o", + "Select, then Start", "Quit at once", "q, Ctrl-C"): + self.assertIn(words, gptk) + self.assertNotIn("Start+Select", help_text("evmapy")) + keys = help_text("keys") + for words in ("Enter", "Esc, Backspace", "o, Tab"): + self.assertIn(words, keys) + for button in ("A/Enter", "B/Esc", "L1", "Start", "Y,"): + self.assertNotIn(button, keys) + muxterm = help_text("muxterm") + for words in ("Start, Enter", "B, Esc, Backspace", "Keyboard on or off", + "Close the terminal"): + self.assertIn(words, muxterm) + self.assertNotIn("Start+Select", muxterm) + miyoo = help_text("miyoo", "systems") + for words in ("Start, Enter", "A, Space, Right/Left", "R2, Esc, Backspace", + "L2, Tab, o", "Sends no key here"): + self.assertIn(words, miyoo) + # The both-names form is the one it was. + self.assertEqual(help_text("both"), (GOLDEN / "help-80x24.txt").read_text().rstrip("\n")) + + def test_a_miyoo_a_chooses_and_ticks(self): + """A is Space on a Miyoo: it chooses where Enter does, ticks where X + would, and L1 and R1 (E and T) page.""" + state = checked_state() + state.pad = "miyoo" + self.assertEqual(press(state, " "), [("run", "install")]) + state = checked_state() + state.pad = "miyoo" + state.stack.append("systems") + rows = install.tui_rows(state) + first = next(i for i, row in enumerate(rows) if row.action == "system") + state.focus["systems"] = first + before = set(state.ticked) + press(state, " ") + self.assertNotEqual(state.ticked, before) + state.focus["systems"] = next(i for i, row in enumerate(rows) if row.action == "all") + press(state, " ") + self.assertEqual(state.ticked, {row["system"] for row in state.systems}) + state.focus["systems"] = first + press(state, "t") + self.assertGreater(state.focus["systems"], first) + press(state, "e") + self.assertEqual(state.focus["systems"], 0) + # In a text field the letters are letters. + state.editing = "systems" + press(state, "t") + self.assertEqual(state.filters["systems"], "t") + + def test_space_stays_x_on_the_other_routes(self): + for route in ("both", "keys", "gptokeyb", "evmapy", "muxterm"): + with self.subTest(route=route): + state = checked_state() + state.pad = route + self.assertEqual(press(state, " "), [("run", "confirm")]) + + class Rendering(unittest.TestCase): def test_only_changed_lines_are_written(self): out = io.StringIO() @@ -1234,8 +1418,13 @@ class CleanupSurvivesABrokenTerminal(unittest.TestCase): self.addCleanup(os.close, slave) fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", 24, 80, 0, 0)) before = termios.tcgetattr(slave) - handlers = {n: signal.getsignal(n) for n in (signal.SIGINT, signal.SIGTERM)} - tty = self.Tty(slave) + handlers = {n: signal.getsignal(n) for n in (signal.SIGINT, signal.SIGTERM, + signal.SIGHUP)} + # Keys come in on the terminal itself, the screen goes out on a + # descriptor of its own, as fd 0 and fd 1 are. + tty, screen_fd = self.Tty(slave), os.dup(slave) + self.addCleanup(os.close, screen_fd) + out = self.Tty(screen_fd) real_draw = install._TuiTerminal.draw def breaking_draw(terminal, lines, cols): @@ -1251,11 +1440,14 @@ class CleanupSurvivesABrokenTerminal(unittest.TestCase): mock.patch.object(install, "tui_start", return_value=(fresh_state(), [])), \ mock.patch.object(install, "_open_local_sources"), \ mock.patch.object(install, "detect_os", return_value="linux"), \ - mock.patch.object(sys, "stdin", tty), mock.patch.object(sys, "stdout", tty), \ - mock.patch.object(sys, "stderr", tty): + mock.patch.object(sys, "stdin", tty), mock.patch.object(sys, "stdout", out), \ + mock.patch.object(sys, "stderr", out): code = install.run_tui(_args()) - self.assertIs(sys.stdout, tty) + self.assertIs(sys.stdout, out) self.assertEqual(code, 1) + # The farewell found no terminal: what still flushes at exit goes to + # the null device, and the exit code stays the one returned. + self.assertEqual(os.fstat(screen_fd).st_rdev, os.stat(os.devnull).st_rdev) # The crash keeps its log, marked kept, and nothing else of the run. self.assertEqual(sorted(path.name for path in work.iterdir()), ["error.log", "kept", "owner"]) @@ -1608,6 +1800,50 @@ class WorkFolders(unittest.TestCase): work, out = self.run_to(130) self.assertFalse(work.exists()) + def test_a_stop_while_it_starts_ends_on_the_farewell(self): + """Start+Select during the detection: the signal is the interface's, + never the default action that ended Python without a word.""" + import signal + + for number in (signal.SIGTERM, signal.SIGHUP): + with self.subTest(signal=number): + heard = [] + + def recorder(signum, _frame, heard=heard): + heard.append(signum) + + previous = signal.signal(number, recorder) + self.addCleanup(signal.signal, number, previous) + interrupt = signal.getsignal(signal.SIGINT) + + def starting(*_args, **_kwargs): + os.kill(os.getpid(), number) + return fresh_state(), [("fetch", "retroarch")] + + entered = [] + WorkFolders.Terminal.state_to = lambda _t: None + out = io.StringIO() + work = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, work, True) + with mock.patch.object(install, "_TuiTerminal", self.Terminal), \ + mock.patch.object(install.tempfile, "mkdtemp", return_value=str(work)), \ + mock.patch.object(self.Terminal, "enter", + lambda _t: entered.append(True)), \ + mock.patch.object(install, "tui_start", side_effect=starting), \ + mock.patch.object(install, "_open_local_sources"), \ + mock.patch.object(install, "_tui_fetch") as fetch, \ + mock.patch.object(install, "detect_os", return_value="linux"), \ + mock.patch.object(sys, "stdout", out), \ + mock.patch.object(sys, "stderr", out): + code = install.run_tui(_args()) + self.assertEqual(code, 128 + number) + self.assertEqual(heard, []) + self.assertEqual(entered, []) + fetch.assert_not_called() + self.assertEqual(out.getvalue(), "RetroBIOS: stopped. Nothing was installed.\n") + self.assertIs(signal.getsignal(number), recorder) + self.assertIs(signal.getsignal(signal.SIGINT), interrupt) + def found(dest: str, folder: str, reason: str = "") -> dict: return {"event": "found", "platform": "retroarch", "dest": dest, "folder": folder, @@ -2402,6 +2638,11 @@ class EndToEnd(unittest.TestCase): self.env = {**repo.env(), "HOME": str(home), "TERM": "xterm", "RETROBIOS_OS": "linux", "XDG_CONFIG_HOME": str(home / ".config")} self.env.pop("NO_COLOR", None) + # Whatever the shell running the tests has, the input is not known: + # the bar names both the button and the key. Each test names its own. + for name in ("DISPLAY", "WAYLAND_DISPLAY", "SSH_CONNECTION", "SSH_TTY", + install.TUI_PAD_ENV, *install._STEAM_MARKERS): + self.env.pop(name, None) return repo def start(self, *args: str, cols: int = 80, rows: int = 24) -> Pty: @@ -2592,6 +2833,49 @@ class EndToEnd(unittest.TestCase): term.send("q") self.assertEqual(term.close(), 130) + def test_closing_the_window_ends_with_its_signal(self): + """The terminal gone, the farewell has nowhere to go: the code is + 129 (SIGHUP), not 120 from the flush failing at exit.""" + self.repo() + for screen_name, keys_first in (("Where do you play?", ()), + ("files to download", ("\r",))): + with self.subTest(screen=screen_name): + term = self.start() + term.wait("Where do you play?") + term.send(*keys_first) + term.wait(screen_name) + os.close(term.fd) + end = time.monotonic() + 20 + status = None + while time.monotonic() < end: + pid, status = os.waitpid(term.pid, os.WNOHANG) + if pid: + break + time.sleep(0.05) + else: + os.kill(term.pid, 9) + self.fail("still running once its window closed") + term.status = os.WEXITSTATUS(status) + self.assertTrue(os.WIFEXITED(status)) + self.assertEqual(term.status, 129) + + def test_a_desktop_names_its_keys_and_a_pad_its_buttons(self): + self.repo() + for env, bar in (({"DISPLAY": ":0"}, "Enter Continue Esc Quit ? Help"), + ({"RETROBIOS_PAD": "gptokeyb", "DISPLAY": ":0"}, + "A Continue B Quit"), + ({"RETROBIOS_PAD": "muxterm"}, "Start Continue B Quit")): + with self.subTest(env=env): + self.env.update(env) + term = self.start(cols=40, rows=12) + term.wait("Where do you play?") + screen = term.wait(bar) + self.assertEqual(screen.splitlines()[-1].strip(), bar) + term.send("q") + self.assertEqual(term.close(), 130) + for name in env: + self.env.pop(name) + def test_resize_redraws(self): self.repo() term = self.start(cols=40, rows=15) diff --git a/wiki/installer.md b/wiki/installer.md index 7426fd13..826692fc 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -169,8 +169,12 @@ install starts. | Quit | `q`, Ctrl+C | B on the first screen | The bottom line of each screen names what A does on the row under the cursor, -with both spellings (`A/Enter`, `B/Esc`), since A and B swap places between -controller layouts. A narrow screen shortens them to `A`, `B` and `X`. On the +in the names of what is held. On a computer it names the keys (`Enter Install`, +`Esc Back`). Started by a handheld launcher, it names the buttons of that +launcher (`A Install`, `B Back`, see `RETROBIOS_PAD` below), and `?` shows +both the buttons and the keys. Where neither is known, in SteamOS game mode or +on a console no launcher named, it gives both spellings (`A/Enter`, `B/Esc`), +and a narrow screen shortens them to `A`, `B` and `X`. On the regions screen, Left and Right move the region under the cursor up and down the order of preference. During the download, a stop waits for the files in progress, and a second one stops at once. Start+Select on a handheld ends the @@ -1072,9 +1076,12 @@ Each launcher tells the interface which buttons reach it, in | `gptokeyb` | gptokeyb or gptokeyb2 with `retrobios.gptk`, the table above | the PortMaster port, the Tools entry, the muOS app with PortMaster | | `muxterm` | muxterm's own keys: D-pad, Start as Enter, B as Backspace, Y as Space | the muOS app without PortMaster | | `evmapy` | `RetroBIOS.sh.keys`, the same buttons as the table | the Batocera and Knulli port | -| `miyoo` | buttons that are already keys, A as Space and B as Left Ctrl | no launcher yet | +| `miyoo` | buttons that are already keys: A as Space, Start as Enter, L1 and R1 as E and T, L2 as Tab, R2 as Backspace | no launcher yet | -A launcher with no mapper at all (a keyboard) leaves it unset. +A launcher with no mapper at all (a keyboard) leaves it unset. On a Miyoo, B, +X, Y and Select are Left Ctrl, Shift, Alt and Right Ctrl: pressed alone, a +modifier sends nothing to a terminal, so the interface goes back with R2 +instead, and the bottom line says so. **Without Wi-Fi.** On a computer, download the BIOS pack that serves your system (every part, for a pack in parts): RetroArch's on muOS, ArkOS,