From b9481977e1805150ee796eff10a2c18ce54630bc Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 11 Nov 2026 10:44:02 +0100 Subject: [PATCH 1/3] feat: add device and emulator screens Change replaces Options and groups every option by intent. Device chooses --target or --every-board, the board read on the device or named by the card first and chosen. Emulators ticks --emulator. From a pack and Without the internet are rows of their own. Parity tests install the screen's argv and the flag's into two folders and compare dest and sha1. --- install.ps1 | 2 +- install.py | 472 ++++++++++++--- install.sh | 2 +- install/launchers/index.json | 16 +- .../{options-120x40.txt => change-120x40.txt} | 26 +- .../{options-40x12.txt => change-40x12.txt} | 12 +- .../{options-40x15.txt => change-40x15.txt} | 18 +- .../{options-80x24.txt => change-80x24.txt} | 26 +- tests/fixtures/tui/colour-120x40.txt | 128 ++++- tests/fixtures/tui/colour-40x12.txt | 54 +- tests/fixtures/tui/colour-40x15.txt | 70 ++- tests/fixtures/tui/colour-80x24.txt | 96 +++- tests/fixtures/tui/device-120x40.txt | 40 ++ tests/fixtures/tui/device-40x12.txt | 12 + tests/fixtures/tui/device-40x15.txt | 15 + tests/fixtures/tui/device-80x24.txt | 24 + tests/fixtures/tui/emulators-120x40.txt | 40 ++ tests/fixtures/tui/emulators-40x12.txt | 12 + tests/fixtures/tui/emulators-40x15.txt | 15 + tests/fixtures/tui/emulators-80x24.txt | 24 + tests/fixtures/tui/help-120x40.txt | 2 +- tests/fixtures/tui/help-40x12.txt | 2 +- tests/fixtures/tui/help-40x15.txt | 2 +- tests/fixtures/tui/help-80x24.txt | 2 +- tests/fixtures/tui/plan-120x40.txt | 4 +- tests/fixtures/tui/plan-40x12.txt | 4 +- tests/fixtures/tui/plan-40x15.txt | 4 +- tests/fixtures/tui/plan-80x24.txt | 4 +- tests/fixtures/tui/plan-games-120x40.txt | 4 +- tests/fixtures/tui/plan-games-40x12.txt | 2 +- tests/fixtures/tui/plan-games-40x15.txt | 4 +- tests/fixtures/tui/plan-games-80x24.txt | 4 +- tests/fixtures/tui/plan-in-place-120x40.txt | 4 +- tests/fixtures/tui/plan-in-place-40x12.txt | 4 +- tests/fixtures/tui/plan-in-place-40x15.txt | 4 +- tests/fixtures/tui/plan-in-place-80x24.txt | 4 +- tests/fixtures/tui/plan-record-120x40.txt | 4 +- tests/fixtures/tui/plan-record-40x12.txt | 2 +- tests/fixtures/tui/plan-record-40x15.txt | 4 +- tests/fixtures/tui/plan-record-80x24.txt | 4 +- tests/fixtures/tui/route-evmapy.txt | 220 +++++-- tests/fixtures/tui/route-gptokeyb.txt | 220 +++++-- tests/fixtures/tui/route-gptokeyb_muxterm.txt | 220 +++++-- tests/fixtures/tui/route-keys.txt | 220 +++++-- tests/fixtures/tui/route-miyoo.txt | 220 +++++-- tests/fixtures/tui/route-muxterm.txt | 196 +++++-- tests/test_install_journeys.py | 8 +- tests/test_install_tui.py | 536 +++++++++++++++++- 48 files changed, 2433 insertions(+), 579 deletions(-) rename tests/fixtures/tui/{options-120x40.txt => change-120x40.txt} (65%) rename tests/fixtures/tui/{options-40x12.txt => change-40x12.txt} (55%) rename tests/fixtures/tui/{options-40x15.txt => change-40x15.txt} (52%) rename tests/fixtures/tui/{options-80x24.txt => change-80x24.txt} (64%) create mode 100644 tests/fixtures/tui/device-120x40.txt create mode 100644 tests/fixtures/tui/device-40x12.txt create mode 100644 tests/fixtures/tui/device-40x15.txt create mode 100644 tests/fixtures/tui/device-80x24.txt create mode 100644 tests/fixtures/tui/emulators-120x40.txt create mode 100644 tests/fixtures/tui/emulators-40x12.txt create mode 100644 tests/fixtures/tui/emulators-40x15.txt create mode 100644 tests/fixtures/tui/emulators-80x24.txt diff --git a/install.ps1 b/install.ps1 index 07014e7cc..dcde7205f 100644 --- a/install.ps1 +++ b/install.ps1 @@ -16,7 +16,7 @@ # and Invoke-WebRequest throws on a cut transfer before the hash is read: # the site's build rewrites this line there (scripts/sitebootstrap.py). $mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." - $defaultInstallSha256 = "37442a7c0d307b0b95a40d7630cf57025a6498fea6223076892371b2d6363147" + $defaultInstallSha256 = "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. $maximumInstallerBytes = 8MB diff --git a/install.py b/install.py index 0d0031846..8adbcc425 100644 --- a/install.py +++ b/install.py @@ -18622,6 +18622,22 @@ class TuiState: # Opened on the plan of the one setup found: its Change where row # goes back to the setups. self.opened_on_plan: bool = False + # The device chosen on Device (--target, "" for the board the run + # reads itself) or every board (--every-board), the emulators + # ticked on Emulators (--emulator), and the packs read in place of + # the network (--from-pack). + self.target: str = "" + self.every_board: bool = False + self.emulators: list[str] = [] + self.packs: list[Path] = [] + # The platform's board list, as fetched for Device: None until it + # comes or when it could not be read, and why. + self.targets: dict | None = None + self.targets_for: str = "" + self.targets_problem: str = "" + # (platform, folder, board, applied, here) of the last board event: + # kept across checks, a --target chosen since stops the event. + self.board_seen: tuple | None = None @property def screen(self) -> str: @@ -19064,6 +19080,12 @@ def _tui_system_flags(state: TuiState) -> list[tuple[str, str | None]]: def tui_flags(state: TuiState) -> list[tuple[str, str | None]]: """The options that carry the choices made on screen, CLI ones included.""" flags = list(state.passthrough) + if state.target: + flags.append(("--target", state.target)) + if state.every_board: + flags.append(("--every-board", None)) + flags += [("--emulator", name) for name in state.emulators] + flags += [("--from-pack", str(pack)) for pack in state.packs] flags += _tui_system_flags(state) flags += [("--core", name) for name in tui_core_flags(state)] if state.regions: @@ -19202,7 +19224,7 @@ class TuiRow(NamedTuple): # Screens that scroll text instead of listing rows. _PAGERS = ("preview", "command", "result", "misplaced", "unreadable", "games", "details") -_CHECKLISTS = ("systems", "cores") +_CHECKLISTS = ("systems", "cores", "emulators") def _needed(row: dict) -> int: @@ -19476,6 +19498,14 @@ def tui_rows(state: TuiState) -> list[TuiRow]: return _system_rows(state) if screen == "options": return _option_rows(state) + if screen == "device": + return _device_rows(state) + if screen == "emulators": + needle = state.filters.get(screen, "") + return [_filter_row(state)] + [ + TuiRow("emulator", f"[{'x' if name in state.emulators else ' '}] {name}", + value=name) for name in tui_emulators(state) if _matches(name, needle) + ] + _REGIONS_END if screen == "regions": known = sorted(set(tui_choices(state)["regions"]) | set(state.cli_regions), key=region_title) @@ -19580,10 +19610,10 @@ def _plan_rows(state: TuiState) -> list[TuiRow]: rows = [TuiRow("systems", TUI_SYSTEMS_ROW)] game_data = _game_data_row(state) return rows + ([game_data] if game_data is not None else []) + [ - *_details_row(state), TuiRow("options", "Options"), TuiRow("quit", "Quit")] + *_details_row(state), TuiRow("options", "Change"), TuiRow("quit", "Quit")] if not (_needed(check) if check else 0) and not check.get("data_missing"): return [TuiRow("close", "Close"), *_games_rows(state), *_record_rows(state), - *_where_rows(state), *_details_row(state), TuiRow("options", "Options")] + *_where_rows(state), *_details_row(state), TuiRow("options", "Change")] # The size the headline says: "Install 6.6 GB" stood under "14930 files # to download, 6.8 GB", the copies counted in one and the data files in # the other. @@ -19639,7 +19669,7 @@ def _plan_rows(state: TuiState) -> list[TuiRow]: rows.append(TuiRow("misplaced", "Show the files in the wrong place")) if state.unreadable: rows.append(TuiRow("unreadable", "Show the files that could not be read")) - return rows + [*_where_rows(state), *_details_row(state), TuiRow("options", "Options"), + return rows + [*_where_rows(state), *_details_row(state), TuiRow("options", "Change"), TuiRow("quit", "Quit")] @@ -19904,8 +19934,11 @@ _REGIONS_END = [TuiRow("none", "None"), TuiRow("done", "Done")] def _first_item(state: TuiState) -> int: - """The row a list of ticks opens on: its first item.""" - return next((i for i, row in enumerate(tui_rows(state)) if row.action in TUI_TICK_ROWS), 0) + """The row a list of ticks opens on: its first item. Elsewhere the + first row that is no heading.""" + rows = tui_rows(state) + return next((i for i, row in enumerate(rows) if row.action in TUI_TICK_ROWS), + next((i for i, row in enumerate(rows) if row.action != "heading"), 0)) def _shared_needed(state: TuiState) -> dict[str, tuple[int, int]]: @@ -19930,56 +19963,168 @@ def _yes(value: bool) -> str: return "yes" if value else "no" +# The Change screen's groups, in the order they are drawn: a heading is no +# row Up and Down stop on, and a group with no row is not drawn. +TUI_CHANGE_GROUPS = ("What to install", "Where", "For which device", "How", "See") + + def _option_rows(state: TuiState) -> list[TuiRow]: + """The Change screen: every option one level below the plan, grouped by + what it decides, each row with its value and its flag.""" manifest = state.manifest or {} choices = tui_choices(state) - rows = [] + what: list[TuiRow] = [] if choices["regions"]: - rows.append(TuiRow("regions", "Preferred regions: " + what.append(TuiRow("regions", "Preferred regions: " + (", ".join(map(region_title, state.regions)) or "any"), "--region")) - if manifest.get("categorized"): - # "Leave out game data: no" was a double negative. - rows.append(TuiRow("game_data", "Game data: " - + ("left out" if state.no_game_data else "kept"), - "--no-game-data")) - found = len(state.facts.get("cores", [])) - if _cores_dir_given(state): - rows.append(TuiRow("installed", "Installed cores only: yes, implied by --cores-dir", - "--cores-dir")) - elif state.platform in CORE_PLATFORMS and "system_cores" in manifest and found: - rows.append(TuiRow("installed", f"Installed cores only: " - f"{_yes(state.installed_only)} ({found} found)", - "--installed-cores")) if len(choices["cores"]) > 1: # The plan's words, the noun left to the row's own label. A list # with one core asks nothing, as line mode does not. - rows.append(TuiRow("cores", "Cores: " + cores_words( + what.append(TuiRow("cores", "Cores: " + cores_words( tui_cores_ticked(state), len(_core_base(state))).replace(" cores", ""), "--core")) + if "system_cores" in manifest and tui_emulators(state): + what.append(TuiRow("emulators", f"Emulators: {_emulator_words(state)}", + "--emulator")) + if manifest.get("categorized"): + # "Leave out game data: no" was a double negative. + what.append(TuiRow("game_data", "Game data: " + + ("left out" if state.no_game_data else "kept"), + "--no-game-data")) copied = ", ".join(str(folder) for folder in state.from_folders) or "none" - if manifest.get("save_directory"): - # The interface said "Name it with --save-dir" and had no row for it. - rows.append(TuiRow("savedir", f"Save folder: {_save_folder_words(state)}", - "--save-dir", style="path")) - if not LOCAL.offline: - # From a pack, or offline, nothing is downloaded. - rows.append(TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs")) - rows += [ + where = [ # The plan's folder: AmberELEC's read the games part here and its # bios folder on the plan, two values under one name. --dest stays the # games part, as the command line takes it. TuiRow("folder", "BIOS folder: " + _short_path(_plan_folder(state, state.dest), state.home), "--dest", style="path"), - TuiRow("from", f"Copy from: {copied}", "--from", style="path"), - *([TuiRow("addapp", f"Put RetroBIOS on this card: {_yes(state.add_app)}", - "--add-app")] if _tui_app_ready(state) else []), - TuiRow("preview", "Preview the file list", "--dry-run"), - TuiRow("command", "Show the command line"), ] + if manifest.get("save_directory"): + # The interface said "Name it with --save-dir" and had no row for it. + where.append(TuiRow("savedir", f"Save folder: {_save_folder_words(state)}", + "--save-dir", style="path")) + where += [TuiRow("from", f"Copy from: {copied}", "--from", style="path"), + TuiRow("pack", "From a pack: " + + (state.packs[0].name if state.packs else "none"), "--from-pack")] + device: list[TuiRow] = [] + if state.platform in TARGET_PLATFORMS: + device.append(TuiRow("device", f"Device: {_device_words(state)}", "--target")) + found = len(state.facts.get("cores", [])) + if _cores_dir_given(state): + device.append(TuiRow("installed", "Installed cores only: yes, implied by --cores-dir", + "--cores-dir")) + elif state.platform in CORE_PLATFORMS and "system_cores" in manifest and found: + device.append(TuiRow("installed", f"Installed cores only: " + f"{_yes(state.installed_only)} ({found} found)", + "--installed-cores")) + if _tui_app_ready(state): + device.append(TuiRow("addapp", f"Put RetroBIOS on this card: {_yes(state.add_app)}", + "--add-app")) + # Parallel downloads below: the row goes once nothing is downloaded, + # and the focus stays on the row just changed. + how = [TuiRow("offline", "Without the internet: " + + ("yes, a pack is read" if LOCAL.pack is not None + else _yes(offline_requested())), "--offline")] + if not LOCAL.offline: + # From a pack, or offline, nothing is downloaded. + how.append(TuiRow("jobs", f"Parallel downloads: {state.jobs}", "--jobs")) + see = [TuiRow("preview", "Preview the file list", "--dry-run"), + TuiRow("command", "Show the command line")] + rows: list[TuiRow] = [] + for heading, group in zip(TUI_CHANGE_GROUPS, (what, where, device, how, see)): + if group: + rows += [TuiRow("heading", heading, style="head"), *group] return rows +def tui_emulators(state: TuiState) -> list[str]: + """The emulators Emulators lists: each profile once, by the name the + list gives it, as --list-emulators names them.""" + if state.manifest is None: + return [] + return sorted(set(emulator_names(state.manifest, *tui_run_files(state)).values())) + + +def _emulator_words(state: TuiState) -> str: + """The emulators chosen, as the Change row says them.""" + if not state.emulators: + return "all" + if len(state.emulators) <= 2: + return ", ".join(state.emulators) + return f"{len(state.emulators)} chosen" + + +def _board_seen(state: TuiState) -> tuple[str, bool, bool] | None: + """(board, applied, here) the last check read for this setup and folder.""" + seen = state.board_seen + if seen is None or seen[:2] != (state.platform, state.dest): + return None + return seen[2], seen[3], seen[4] + + +def _board_where(here: bool) -> str: + return "this device" if here else "named by the card" + + +def _device_choice(state: TuiState) -> str: + """The board the runs install for, "" for every board.""" + if state.every_board: + return "" + if state.target: + return state.target + seen = _board_seen(state) + return seen[0] if seen is not None and seen[1] else "" + + +def _device_words(state: TuiState) -> str: + """The device as the Change row says it: the board, where the run read + it when it chose it itself.""" + chosen = _device_choice(state) + if not chosen: + return "every board" + seen = _board_seen(state) + if not state.target and seen is not None: + return f"{chosen}, {_board_where(seen[2])}" + return chosen + + +def _device_rows(state: TuiState) -> list[TuiRow]: + """Device: the platform's boards by the names its list gives them, the + one read here or named by the card first, then every board.""" + targets = state.targets if state.targets_for == state.platform else None + if not targets: + return [] + seen = _board_seen(state) + chosen = _device_choice(state) + names = sorted(targets) + if seen is not None and seen[0] in targets: + names.remove(seen[0]) + names.insert(0, seen[0]) + rows = [] + for name in names: + images = [str(image) for image in targets[name].get("images") or ()] + # The device names an image carries say what a board code means. + right = (_board_where(seen[2]) if seen is not None and name == seen[0] + else ", ".join(images)) + narrow = (images[0] + (f" +{len(images) - 1}" if len(images) > 1 else ""),) \ + if images and right != images[0] and name != (seen or ("",))[0] else () + rows.append(TuiRow("board", f"({'x' if name == chosen else ' '}) {name}", right, + name, narrow=narrow)) + rows.append(TuiRow("board", f"({' ' if chosen else 'x'}) Every board", value="")) + return rows + + +def _choose_board(state: TuiState, name: str) -> list[tuple]: + """A board chosen on Device, back on Change. The board the run reads + itself is no option: the command stays the one with nothing chosen.""" + seen = _board_seen(state) + read = seen[0] if seen is not None and seen[1] else "" + state.target = "" if name == read else name + state.every_board = not name and bool(read) + return _back(state) + + def _save_dir_given(state: TuiState) -> Path | None: return next((Path(value) for flag, value in state.passthrough if flag == "--save-dir" and value), None) @@ -20183,15 +20328,19 @@ def _tui_record_take(state: TuiState) -> None: state.games_folder = parsed.for_games if isinstance(parsed.for_games, Path) else None state.required_only = bool(parsed.required_only) and state.for_games state.games_choice = "record" + # Device and Emulators show these: a choice no row showed was kept apart. + state.target = str(parsed.target or "") + state.every_board = bool(parsed.every_board) + state.emulators = list(parsed.emulator or []) state.record_flags = [(flag, value) for flag, value in record_options(setup["choice"]) - if flag in ("--target", "--emulator", "--every-board", - "--declared-only")] + if flag == "--declared-only"] def _tui_record_drop(state: TuiState) -> None: """The recorded choice given up: the choice nothing narrows.""" state.cli_systems, state.cores, state.regions, state.record_flags = [], [], [], [] state.no_game_data = state.installed_only = state.required_only = False + state.target, state.every_board, state.emulators = "", False, [] state.for_games, state.games_folder, state.games_choice = None, None, "" # The systems of the recorded check go, and with them its ticks. state.systems, state.ticked, state.keep_ticked, state.widened_from = [], set(), None, None @@ -20575,10 +20724,13 @@ def _os_folder(path: Path, folder: Path) -> bool: def _browse_to(state: TuiState, folder: Path | None, came_from: Path | None = None) -> None: state.browse_dir = folder copying = state.browse_for == "from" - state.browse_rows, state.status = _browse_rows(folder, copying, + state.browse_rows, state.status = _browse_rows(folder, copying or state.browse_for == "pack", _dest_card(state) if copying else None) + if state.browse_for == "pack": + # A pack is a ZIP, never a folder. + state.browse_rows = [row for row in state.browse_rows if row.action != "use"] focus = 0 - if (folder is not None and state.browse_for != "from" + if (folder is not None and state.browse_for not in ("from", "pack") and folder in (state.home, Path(folder.anchor))): # No frontend reads its BIOS files from HOME or a drive's root: A # pressed through the screens installed into ~. A goes into a @@ -20791,6 +20943,9 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: state.path_selected = bool(state.path_text) return _push(state, "path") if screen == "browse": + if action == "zip" and state.browse_for == "pack": + # Opened in this process (perform), which answers pack_opened. + return [("pack", row.value)] if action == "use" and state.browse_for == "save": state.passthrough = [flag for flag in state.passthrough if flag[0] != "--save-dir"] @@ -20913,6 +21068,8 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: return _checklist(state, row) if screen == "options": return _option(state, action, 0) + if screen == "device" and action == "board": + return _choose_board(state, str(row.value)) if screen == "confirm": if action == "install": state.stack.pop() @@ -20959,6 +21116,10 @@ def _checklist(state: TuiState, row: TuiRow) -> list[tuple]: state.cores, state.no_cores = [], False elif action == "none" and screen == "regions": state.regions = [] + elif action == "none" and screen == "emulators": + # None ticked installs for every emulator, as Preferred regions keeps + # every region with none ticked. + state.emulators = [] elif action == "none": if screen == "systems": state.ticked = set() @@ -21437,6 +21598,21 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]: state.jobs = max(1, min(32, state.jobs + (step or 1))) elif action == "addapp": state.add_app = step > 0 if step else not state.add_app + elif action == "offline" and LOCAL.pack is None: + # Set in this process by the effect, as --offline sets it: the + # runs started next inherit it, and their command names it. + return [("offline", step > 0 if step else not offline_requested())] + elif action == "device": + _push(state, "device") + state.focus["device"] = _device_focus(state) + if state.targets_for != state.platform: + state.targets, state.targets_problem = None, "" + return [("targets", state.platform)] + elif action == "emulators": + _push(state, action) + state.focus[action] = _first_item(state) + elif action == "pack": + return _open_pack_browser(state) elif action == "cores": # On the first core: the filter row under A opened a field a # controller cannot type into. @@ -21468,6 +21644,54 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]: return [] +def _device_focus(state: TuiState) -> int: + """Device opens on the board chosen: the one read here or named by + the card when nothing else was.""" + rows = _device_rows(state) + chosen = _device_choice(state) + return next((index for index, row in enumerate(rows) if row.value == chosen), 0) + + +def _open_pack_browser(state: TuiState) -> list[tuple]: + """The browser, to name a pack downloaded before: it opens where a + download lands.""" + state.browse_for = "pack" + _push(state, "browse") + downloads = state.home / "Downloads" + start = (state.packs[0].parent if state.packs + else downloads if _is_dir(downloads) else state.home) + _browse_to(state, _nearest_folder(start)) + return [] + + +def open_tui_pack(path: Path, plat: str) -> str: + """The pack the screen chose, opened in this process in place of the + network as --from-pack opens it: "" once it is, else why not.""" + try: + pack = PackSource([path]) + except ValueError as exc: + return str(exc) + spelled = pack.platforms() + if spelled and plat not in spelled: + pack.close() + return (f"{pack.name} is a pack for " + f"{' and '.join(display_name(name) for name in spelled)}, " + f"not {display_name(plat)}.") + if LOCAL.pack is not None: + LOCAL.pack.close() + LOCAL.pack = pack + return "" + + +def set_offline(on: bool) -> None: + """Without the internet on or off for this process and the runs it + starts, as --offline sets it.""" + if on: + os.environ[OFFLINE_VARIABLE] = OFFLINE_FLAG_VALUE + else: + os.environ.pop(OFFLINE_VARIABLE, None) + + def _open_from_browser(state: TuiState) -> list[tuple]: """The browser, to name an old card, a backup or a ZIP to copy from.""" state.browse_for = "from" @@ -21546,10 +21770,18 @@ def _tick(state: TuiState, row: TuiRow, on: bool | None) -> None: chosen = state.ticked elif state.screen == "cores" and row.action == "core": chosen = {name for name in tui_choices(state)["cores"] if core_ticked(state, name)} + elif state.screen == "emulators" and row.action == "emulator": + chosen = set(state.emulators) else: return value = row.value want = (value not in chosen) if on is None else on + if state.screen == "emulators": + if want and value not in state.emulators: + state.emulators.append(str(value)) + elif not want and value in state.emulators: + state.emulators.remove(str(value)) + return if state.screen == "systems": if want: state.ticked.add(value) @@ -21793,7 +22025,9 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: elif name in ("esc", "backspace"): return _back(state) elif (name == "tab" or char == "o") and screen in ("plan", "systems", "confirm"): - return _push(state, "options") + _push(state, "options") + state.focus["options"] = _first_item(state) + return [] elif char == "/" and any(item.action == "filter" for item in rows): # The focus goes to the field typed into: it stayed on None, and the # bar named None's keys while letters went into the filter. @@ -21808,7 +22042,7 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: elif screen == "regions" and name in ("right", "shift-down"): _move_region(state, row.value, 1) return [] - elif screen in _CHECKLISTS and row.action in ("system", "core") and ( + elif screen in _CHECKLISTS and row.action in ("system", "core", "emulator") and ( char == " " or name in ("right", "left")): _tick(state, row, None if char == " " else name == "right") return [] @@ -21822,12 +22056,21 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: return _checklist(state, named[0]) if named else [] elif screen == "options" and name in ("left", "right"): return _option(state, row.action, 1 if name == "right" else -1) \ - if row.action in ("game_data", "installed", "jobs", "addapp") \ + if row.action in ("game_data", "installed", "jobs", "addapp", "offline") \ or (row.action == "from" and name == "left") else [] elif char == " ": return _space(state, row) elif name == "enter": return _activate(state, row) + if rows[focus].action == "heading": + # A heading names the rows under it and is never in focus: the + # move goes on to the next row its way, else stays where it was. + step = -1 if name in ("up", "pgup", "end") or char in ("k", "G") else 1 + order = list(range(focus, -1, -1) if step < 0 else range(focus, len(rows))) + if wrap: + order += range(last, focus, -1) if step < 0 else range(0, focus) + focus = next((index for index in order if rows[index].action != "heading"), + max(0, min(state.focus.get(screen, 0), last))) state.focus[screen] = focus return [] @@ -21906,6 +22149,8 @@ def tui_mouse(state: TuiState, event: TuiMouse) -> list[tuple]: screen = state.screen row = rows[index] seen = (screen, len(state.stack), row.action, row.value) + if row.action == "heading": + return [] if state.editing: state.editing = "" elif row.action in TUI_TICK_ROWS: @@ -21929,7 +22174,7 @@ def tui_mouse(state: TuiState, event: TuiMouse) -> list[tuple]: # The rows a tap ticks or unticks, as Space does: the lists of ticks. -TUI_TICK_ROWS = ("system", "core", "region") +TUI_TICK_ROWS = ("system", "core", "region", "emulator") def _bar_commits(state: TuiState, key: TuiKey) -> bool: @@ -22069,7 +22314,12 @@ def _wheel(state: TuiState, hits: TuiHits, step: int) -> list[tuple]: state.list_top[screen] = top rows = tui_rows(state) focus = max(0, min(state.focus.get(screen, 0), len(rows) - 1)) - state.focus[screen] = max(top, min(focus, top + shown - 1)) + focus = max(top, min(focus, top + shown - 1)) + if rows and rows[focus].action == "heading": + # Never on a heading: the row under it, else the one above. + below = focus + 1 < min(len(rows), top + shown) + focus = focus + 1 if below or focus == 0 else focus - 1 + state.focus[screen] = focus elif hits.scroll == "error": state.focus["error_text"] = max(0, state.focus.get("error_text", 0) + lines) elif hits.scroll == "pager": @@ -22170,6 +22420,10 @@ def _take_event(state: TuiState, event: dict) -> None: state.set_aside = set(event["dests"]) elif kind == "board" and state.run == "check": state.board = str(event["message"]) + # Read on the device itself, or named by the card in a reader: a + # computer preparing a card has no board of its own. + state.board_seen = (state.platform, state.dest, str(event["board"]), + bool(event["applied"]), detect_board(state.platform) is not None) elif kind == "version" and state.run == "check": state.version = str(event["message"]) state.version_short = (f"List for {display_name(state.platform, state.manifest)} " @@ -22239,6 +22493,29 @@ def tui_receive(state: TuiState, message: tuple) -> list[tuple]: if state.games_probed is None: state.games_probed = (plat, state.dest) return _start_run(state, "check") + if kind == "targets": + _kind, plat, targets, problem = message + if plat == state.platform: + state.targets, state.targets_for, state.targets_problem = targets, plat, problem + if state.screen == "device": + state.focus["device"] = _device_focus(state) + return [] + if kind == "pack_opened": + _kind, path, problem = message + if state.screen != "browse" or state.browse_for != "pack": + return [] + if problem: + state.status = _styled(problem, "refusal") + return [] + # The pack's own list, as --from-pack reads it: the list fetched + # before is fetched again from the pack. + state.packs = [path] + state.browse_for = "dest" + state.manifest, state.manifest_for = None, "" + if not state.manifest_given: + state.manifest_path = None + state.targets_for = "" + return _start_check(state) if kind == "app": # Put from the result screen, in this process. state.app, state.app_running = message[1], False @@ -22414,6 +22691,8 @@ _ROW_VERBS = { "cardsystem": "Choose", "rescan": "Look again", "elsewhere": "Choose", "drive": "Choose", "recheck": "Check again", "gamedata": "Change", "share": "Choose", "morecores": "Show", "games": "Change", "startonly": "Change", "gameslist": "Show", "details": "Show", + "device": "Open", "emulators": "Open", "emulator": "Tick", "board": "Choose", + "pack": "Choose", "offline": "Change", } @@ -22423,6 +22702,9 @@ TUI_ROW_SPREAD = 78 def _row_line(row: TuiRow, focused: bool, width: int) -> TuiLine: + if row.action == "heading": + # Flush left of the rows it names, never in focus. + return _styled(fit(row.label, width), row.style) left = ("> " if focused else " ") + row.label if row.align and (row.right or row.narrow or row.cut): # One column for the list: the longest form that fits after it, a @@ -22901,7 +23183,7 @@ def _plan_lines(check: dict, dest: Path | None, state: TuiState) -> list: lines += _unavailable_brief(state) + _details(_unavailable_lines(state)) if check is state.check: lines += _unplaced_lines(_choice_summary(state), state, - "Choose it in Options, Save folder (--save-dir).") + "Choose it in Change, Save folder (--save-dir).") if check is state.check: lines += _found_lines(state) if check is state.check and state.from_folders: @@ -23193,10 +23475,13 @@ def _view_browse(state: TuiState, width: int, height: int) -> tuple: elif state.browse_for == "save": hint = [f"Choose {display_name(state.platform)}'s save directory: some " "cores read files there."] + elif state.browse_for == "pack": + hint = ["Choose the pack ZIP downloaded before: nothing is downloaded then."] # The hint is kept whole under the list, which scrolls: three folders at # 40x12 cut it to "Choose the old card, a backup folder". return ("Copy from" if state.browse_for == "from" - else "Save folder" if state.browse_for == "save" else "Browse for a folder", + else "Save folder" if state.browse_for == "save" + else "From a pack" if state.browse_for == "pack" else "Browse for a folder", _compose(state, width, height, [where], tui_rows(state), _status_after(state), hint), ("A/Enter Open", "B/Esc Back", "? Help")) @@ -23489,7 +23774,7 @@ def _view_plan(state: TuiState, width: int, height: int) -> tuple: name = display_name(state.platform, state.manifest) return (f"{system} ({name} files)" if system and system != name else name, _plan_body(state, width, height, lines, rows), - ("A/Enter Install", "B/Esc Back", "Y/o Options", "? Help")) + ("A/Enter Install", "B/Esc Back", "Y/o Change", "? Help")) def _plan_body(state: TuiState, width: int, height: int, lines: list, @@ -23678,13 +23963,13 @@ def _view_systems(state: TuiState, width: int, height: int) -> tuple: def _view_options(state: TuiState, width: int, height: int) -> tuple: rows = tui_rows(state) - before = ["Each row is an option of the command line, named on the right."] + # The headings say what each group decides, the flags on the right + # which option of the command line each row is. after: list = [] if width < 60: # A value cut to make room for its flag said nothing: the flag of # the focused row is named below instead. focus = rows[min(state.focus.get("options", 0), len(rows) - 1)] - before = [] after = [f"Sets {focus.right}"] if focus.right else [] rows = [row._replace(right="") for row in rows] # A folder is cut in its middle, its last levels being what tells it apart. @@ -23692,10 +23977,35 @@ def _view_options(state: TuiState, width: int, height: int) -> tuple: rows = [_middle_label(row, room) if row.style == "path" else row for row in rows] if state.status: after.append(state.status) - return ("Options", _compose(state, width, height, before, rows, after), + return ("Change", _compose(state, width, height, [], rows, after), ("A/Enter Change", "B/Esc Back", "Left/Right Set", "? Help")) +def _view_device(state: TuiState, width: int, height: int) -> tuple: + # "Board" is the list's word, glossed once: a beginner knows the device. + pinned = ["Choose the board, the chip of the device the games run on. " + "Every board fits any device."] + rows = tui_rows(state) + if state.targets_for != state.platform: + after = ["Reading the board list..."] + elif not rows: + after = [state.targets_problem or f"{display_name(state.platform)} publishes " + "no board list: every board's files are installed."] + else: + after = [] + return ("Device", _compose(state, width, height, [], rows, after, + _status_footer(state), pinned), + ("A/Enter Choose", "B/Esc Back", "? Help") if rows else ("B/Esc Back", "? Help")) + + +def _view_emulators(state: TuiState, width: int, height: int) -> tuple: + pinned = ["Tick the emulators to install for. With none ticked, every " + "emulator's files are installed."] + return ("Emulators", _compose(state, width, height, [], tui_rows(state), [], + _status_footer(state), pinned), + _CHECKLIST_BAR) + + def _middle_label(row: TuiRow, room: int) -> TuiRow: """A row naming a folder, the folder cut in its middle to fit.""" name, _sep, value = row.label.partition(": ") @@ -24399,6 +24709,7 @@ _TUI_VIEWS: dict[str, Callable[[TuiState, int, int], tuple]] = { "installing": _view_installing, "result": _view_result, "error": _view_error, "quit": _view_quit, "cardroot": _view_cardroot, "misplaced": _view_misplaced, "unreadable": _view_unreadable, "games": _view_games, "details": _view_details, + "device": _view_device, "emulators": _view_emulators, } @@ -24446,22 +24757,22 @@ def _help_lines(state: TuiState) -> list[tuple[str, str]]: # How a tap acts, before what the list says elsewhere: last, it was the # line a 40x12 help cut. keys += _tap_help(state) - if screen in _CHECKLISTS: + if screen in ("systems", "cores"): keys.append(("a, n", "All, none")) - elif screen == "regions": + elif screen in ("regions", "emulators"): keys.append(("n", "None")) if screen == "platforms": keys.append(("/", "Filter by setup or device name")) - elif screen in ("systems", "cores"): + elif screen in _CHECKLISTS: keys.append(("/", "Filter")) - if screen in ("platforms", "systems", "cores") and state.filters.get(screen): + if screen in ("platforms", *_CHECKLISTS) and state.filters.get(screen): keys.append((names["esc"], "Clear the filter")) if screen == "regions": keys.append(("Left/Right", "Move a region up or down the order")) if screen == "options": keys.append(("Left/Right", "Change a value")) if screen in ("plan", "systems", "confirm"): - keys.append((names["options"], "Options")) + keys.append((names["options"], "Change")) if screen in ("plan", "confirm"): keys.append(("d", "Details")) elif screen == "result" and not tui_rows(state): @@ -24510,6 +24821,8 @@ def _primary(state: TuiState) -> str | None: verb = _ROW_VERBS.get(row.action) if row.action == "filter" and _only_match(state) is not None: verb = "Choose" + if row.action == "zip" and state.browse_for == "pack": + verb = "Use this pack" return f"A/Enter {verb}" if verb else None @@ -24544,7 +24857,7 @@ def _view_help(state: TuiState, width: int, height: int) -> tuple: # Cut from its end at 40x12, the help lost Quit and This help with no # sign: what a list's keys also say elsewhere goes first. for spare in ("First, last", "All, none", "extra", "Filter by setup or device name", - "Filter", "Options", "Details", "This help", "gap", "definitions"): + "Filter", "Change", "Details", "This help", "gap", "definitions"): if (len(drawn(keys)) + len(extra) + len(defined) + len(_text_lines(more, width)) <= height): break @@ -24752,17 +25065,13 @@ def tell_apart(setups: list[tuple]) -> list[tuple]: def _tui_passthrough(args: argparse.Namespace) -> list[tuple[str, str | None]]: """Options the screens do not change, handed to every run unchanged.""" flags: list[tuple[str, str | None]] = [] - for flag in ("--target", "--romm-firmware"): - value = getattr(args, flag[2:].replace("-", "_")) - if value is not None: - flags.append((flag, str(value))) + if args.romm_firmware is not None: + flags.append(("--romm-firmware", str(args.romm_firmware))) for flag in ("--save-dir", "--cores-dir"): value = getattr(args, flag[2:].replace("-", "_")) if value is not None: flags.append((flag, str(Path(value).absolute()))) - flags += [("--emulator", name) for name in args.emulator or []] - flags += [("--from-pack", str(Path(pack).absolute())) for pack in args.from_pack or []] - for flag in ("--every-board", "--standalone-copies", "--verbose"): + for flag in ("--standalone-copies", "--verbose"): if getattr(args, flag[2:].replace("-", "_")): flags.append((flag, None)) return flags @@ -24843,6 +25152,10 @@ def tui_start( state.jobs = args.jobs state.add_app = bool(getattr(args, "add_app", False)) state.passthrough = _tui_passthrough(args) + state.target = str(args.target or "") + state.every_board = bool(args.every_board) + state.emulators = list(args.emulator or []) + state.packs = [Path(pack).absolute() for pack in args.from_pack or []] if getattr(args, "for_games", None) is not None: state.for_games, state.games_choice = True, "cli" if isinstance(args.for_games, Path): @@ -25102,6 +25415,21 @@ def _tui_fetch(plat: str, inbox: queue.Queue, log: io.StringIO) -> None: inbox.put(("manifest_failed", plat, title, lines, 1)) +def _tui_fetch_targets(plat: str, inbox: queue.Queue, log: io.StringIO) -> None: + """The platform's board list for Device, fetched in this process as a + --target run reads it: the pack's or the list's beside --manifest, else + the network's. None and why when it cannot be read.""" + mark = len(log.getvalue()) + try: + targets = fetch_targets(plat) + except SystemExit: + targets = None + said = [line.strip() for line in log.getvalue()[mark:].splitlines() if line.strip()] + problem = "" if targets is not None else ( + f"The board list could not be read{': ' + said[0] if said else '.'}") + inbox.put(("targets", plat, targets, problem)) + + def _tui_fetch_games(plat: str, inbox: queue.Queue, log: io.StringIO, dest: Path | None, look: bool) -> None: """The file list, then, once it came, a look at the card for games @@ -25405,7 +25733,7 @@ ROMM_SCAN_WORDS = ("RomM lists the firmware its library scan has recorded: start # The result's remedy for the save files left out, as the terminal says it # once the screen is gone: Options are not there to choose from # Line mode's words. -TUI_SAVE_REMEDY = "Choose it in Options, Save folder (--save-dir), next time." +TUI_SAVE_REMEDY = "Choose it in Change, Save folder (--save-dir), next time." LINE_SAVE_REMEDY = "Name it with --save-dir next time." @@ -25533,12 +25861,19 @@ def run_tui(args: argparse.Namespace) -> int: for run in current: run.stop(0) current.clear() - if not state.manifest_given and not args.from_pack and state.manifest: + if not state.manifest_given and not state.packs and state.manifest: if state.platform not in written: path = work / f"{state.platform}.json" path.write_text(json.dumps(state.manifest), encoding="utf-8") written[state.platform] = path state.manifest_path = written[state.platform] + if state.targets_for == state.platform and state.targets: + # Beside the list, where a --target run without the + # internet reads the boards (local_targets). + boards = work / "targets" / f"{state.platform}.json" + boards.parent.mkdir(exist_ok=True) + boards.write_text(json.dumps(state.targets), encoding="utf-8") + written[f"targets:{state.platform}"] = boards log = work / f"{effect[1]}.log" if effect[1] == "install": state.log = str(log) @@ -25561,6 +25896,13 @@ def run_tui(args: argparse.Namespace) -> int: elif kind == "app": threading.Thread(target=_tui_put_app, args=(effect[1], inbox), daemon=True).start() + elif kind == "targets": + threading.Thread(target=_tui_fetch_targets, args=(effect[1], inbox, buffer), + daemon=True).start() + elif kind == "pack": + inbox.put(("pack_opened", effect[1], open_tui_pack(effect[1], state.platform))) + elif kind == "offline": + set_offline(effect[1]) def on_interrupt(_signum: int, _frame: object) -> None: flags["interrupt"] = True diff --git a/install.sh b/install.sh index 461ba515b..77f994921 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins # curl or wget stop on a cut transfer before the hash is read: the site's # build rewrites this line there (scripts/sitebootstrap.py). MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." -DEFAULT_INSTALL_SHA256="37442a7c0d307b0b95a40d7630cf57025a6498fea6223076892371b2d6363147" +DEFAULT_INSTALL_SHA256="18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. MAX_INSTALLER_BYTES=8388608 diff --git a/install/launchers/index.json b/install/launchers/index.json index e6f2a2d46..87c398d14 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1201450, - "sha256": "37442a7c0d307b0b95a40d7630cf57025a6498fea6223076892371b2d6363147", + "size": 1217065, + "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1255075, - "sha256": "23393eb6b88f7a7875bd034e79c02d81e80409c9403290bb96a75166bf7a60b6" + "size": 1270690, + "sha256": "dbda647988b00ef42e70c82165cbbe8ea11fcfeec59ab8d753dfbfe7914e07cb" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1201450, - "sha256": "37442a7c0d307b0b95a40d7630cf57025a6498fea6223076892371b2d6363147", + "size": 1217065, + "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1201450, - "sha256": "37442a7c0d307b0b95a40d7630cf57025a6498fea6223076892371b2d6363147", + "size": 1217065, + "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", "executable": false }, { diff --git a/tests/fixtures/tui/options-120x40.txt b/tests/fixtures/tui/change-120x40.txt similarity index 65% rename from tests/fixtures/tui/options-120x40.txt rename to tests/fixtures/tui/change-120x40.txt index eac6c418f..45d047262 100644 --- a/tests/fixtures/tui/options-120x40.txt +++ b/tests/fixtures/tui/change-120x40.txt @@ -1,14 +1,21 @@ - RetroBIOS: Options - - Each row is an option of the command line, named on the right. + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line @@ -24,13 +31,6 @@ - - - - - - - diff --git a/tests/fixtures/tui/options-40x12.txt b/tests/fixtures/tui/change-40x12.txt similarity index 55% rename from tests/fixtures/tui/options-40x12.txt rename to tests/fixtures/tui/change-40x12.txt index 0aad95e48..8e36af83a 100644 --- a/tests/fixtures/tui/options-40x12.txt +++ b/tests/fixtures/tui/change-40x12.txt @@ -1,12 +1,12 @@ - RetroBIOS: Options + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 A Open B Back <> Set ? Help diff --git a/tests/fixtures/tui/options-40x15.txt b/tests/fixtures/tui/change-40x15.txt similarity index 52% rename from tests/fixtures/tui/options-40x15.txt rename to tests/fixtures/tui/change-40x15.txt index 0d94cbea9..296afa921 100644 --- a/tests/fixtures/tui/options-40x15.txt +++ b/tests/fixtures/tui/change-40x15.txt @@ -1,15 +1,15 @@ - RetroBIOS: Options + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 A Open B Back <> Set ? Help diff --git a/tests/fixtures/tui/options-80x24.txt b/tests/fixtures/tui/change-80x24.txt similarity index 64% rename from tests/fixtures/tui/options-80x24.txt rename to tests/fixtures/tui/change-80x24.txt index b4e6e1da3..5294b052f 100644 --- a/tests/fixtures/tui/options-80x24.txt +++ b/tests/fixtures/tui/change-80x24.txt @@ -1,24 +1,24 @@ - RetroBIOS: Options - - Each row is an option of the command line, named on the right. + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - A/Enter Open B/Esc Back Left/Right Set ? Help diff --git a/tests/fixtures/tui/colour-120x40.txt b/tests/fixtures/tui/colour-120x40.txt index 08b62e689..769b1cf0a 100644 --- a/tests/fixtures/tui/colour-120x40.txt +++ b/tests/fixtures/tui/colour-120x40.txt @@ -336,7 +336,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -366,7 +366,7 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === plan-in-place ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: RetroArch^[[0m @@ -375,7 +375,7 @@ ^[[0;7;35m > Close^[[0m Details - Options + Change @@ -407,7 +407,7 @@ - ^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === systems ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Choose systems^[[0m @@ -449,18 +449,25 @@ ^[[0;1m ^[[0;1;35m2^[[0;1m of ^[[0;1;35m3^[[0;1m systems: ^[[0;1;35m4^[[0;1m files, ^[[0;1;35m1.0 MB^[[0m ^[[0;1;35mA/Enter^[[0m Tick ^[[0;2mB/Esc^[[0m Done ^[[0;2m?^[[0m Help -=== options -^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Options^[[0m +=== change +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Change^[[0m - Each row is an option of the command line, named on the right. - +^[[0;1m What to install^[[0m ^[[0;7;35m > Preferred regions: North America, Japan --region^[[0m - Game data: kept --no-game-data - Installed cores only: no (1 found) --installed-cores Cores: all --core - Parallel downloads: 8 --jobs + Emulators: all --emulator + Game data: kept --no-game-data +^[[0;1m Where^[[0m BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m --dest Copy from: none --from + From a pack: none --from-pack +^[[0;1m For which device^[[0m + Device: every board --target + Installed cores only: no (1 found) --installed-cores +^[[0;1m How^[[0m + Without the internet: no --offline + Parallel downloads: 8 --jobs +^[[0;1m See^[[0m Preview the file list --dry-run Show the command line @@ -476,13 +483,6 @@ - - - - - - - @@ -490,6 +490,88 @@ ^[[0;1;35mA/Enter^[[0m Open ^[[0;2mB/Esc^[[0m Back ^[[0;2mLeft/Right^[[0m Set ^[[0;2m?^[[0m Help +=== device +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Device^[[0m + + Choose the board, the chip of the device the games run on. Every board fits any device. + +^[[0;7;35m > (x) h700 named by the card^[[0m + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help +=== emulators +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Emulators^[[0m + + Tick the emulators to install for. With none ticked, every emulator's files are installed. + + Filter (/ to type) + [ ] mesen +^[[0;7;35m > [x] pcsx_rearmed^[[0m + None + ^[[0;32mDone^[[0m + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ^[[0;1;35mA/Enter^[[0m Tick ^[[0;2mB/Esc^[[0m Done ^[[0;2m?^[[0m Help === regions ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m @@ -864,7 +946,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit @@ -914,7 +996,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -940,7 +1022,7 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === games ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Games on this card^[[0m @@ -1077,7 +1159,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1104,4 +1186,4 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help diff --git a/tests/fixtures/tui/colour-40x12.txt b/tests/fixtures/tui/colour-40x12.txt index dd8c187aa..7feccc9e3 100644 --- a/tests/fixtures/tui/colour-40x12.txt +++ b/tests/fixtures/tui/colour-40x12.txt @@ -112,9 +112,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === plan-in-place ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: RetroArch^[[0m @@ -124,10 +124,10 @@ ^[[0;7;35m > Close^[[0m Details - Options + Change - ^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === systems ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Choose systems^[[0m @@ -141,19 +141,45 @@ place ^[[0;1m ^[[0;1;35m2^[[0;1m of ^[[0;1;35m3^[[0;1m systems: ^[[0;1;35m4^[[0;1m files, ^[[0;1;35m1.0 MB^[[0m ^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m?^[[0m Help -=== options -^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Options^[[0m +=== change +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Change^[[0m +^[[0;1m What to install^[[0m ^[[0;7;35m > Preferred regions: North America, Ja^[[0m - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept +^[[0;1m Where^[[0m BIOS folder: ^[[0;1m~/.config...ch/system^[[0m Copy from: none - Preview the file list - Show the command line +^[[0;2m 1-8 of 18^[[0m ^[[0;1;35mA^[[0m Open ^[[0;2mB^[[0m Back ^[[0;2m<>^[[0m Set ^[[0;2m?^[[0m Help +=== device +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Device^[[0m + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + +^[[0;7;35m > (x) h700 named by the card^[[0m + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + ^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help +=== emulators +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Emulators^[[0m + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen +^[[0;7;35m > [x] pcsx_rearmed^[[0m + None + ^[[0;32mDone^[[0m + ^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m?^[[0m Help === regions ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m @@ -276,7 +302,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit @@ -296,7 +322,7 @@ Only what starts: no Show the games ^[[0;2m 1-4 of 9^[[0m - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === games ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Games on this card^[[0m @@ -348,4 +374,4 @@ Choose systems Copy from an old card or a folder ^[[0;2m 1-4 of 8^[[0m - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help diff --git a/tests/fixtures/tui/colour-40x15.txt b/tests/fixtures/tui/colour-40x15.txt index 981d77e67..6142be474 100644 --- a/tests/fixtures/tui/colour-40x15.txt +++ b/tests/fixtures/tui/colour-40x15.txt @@ -137,11 +137,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === plan-in-place ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: RetroArch^[[0m @@ -151,13 +151,13 @@ ^[[0;7;35m > Close^[[0m Details - Options + Change - ^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Close ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === systems ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Choose systems^[[0m @@ -174,22 +174,54 @@ place ^[[0;1m ^[[0;1;35m2^[[0;1m of ^[[0;1;35m3^[[0;1m systems: ^[[0;1;35m4^[[0;1m files, ^[[0;1;35m1.0 MB^[[0m ^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m?^[[0m Help -=== options -^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Options^[[0m +=== change +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Change^[[0m +^[[0;1m What to install^[[0m ^[[0;7;35m > Preferred regions: North America, Ja^[[0m - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept +^[[0;1m Where^[[0m BIOS folder: ^[[0;1m~/.config...ch/system^[[0m Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none +^[[0;1m For which device^[[0m + Device: every board +^[[0;2m 1-11 of 18^[[0m ^[[0;1;35mA^[[0m Open ^[[0;2mB^[[0m Back ^[[0;2m<>^[[0m Set ^[[0;2m?^[[0m Help +=== device +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Device^[[0m + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + +^[[0;7;35m > (x) h700 named by the card^[[0m + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + ^[[0;1;35mA^[[0m Choose ^[[0;2mB^[[0m Back ^[[0;2m?^[[0m Help +=== emulators +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Emulators^[[0m + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen +^[[0;7;35m > [x] pcsx_rearmed^[[0m + None + ^[[0;32mDone^[[0m + + + + ^[[0;1;35mA^[[0m Tick ^[[0;2mB^[[0m Done ^[[0;2m?^[[0m Help === regions ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m @@ -339,7 +371,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit @@ -363,9 +395,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help === games ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Games on this card^[[0m @@ -427,6 +459,6 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA^[[0m Install ^[[0;2mB^[[0m Back ^[[0;2mY^[[0m Change ^[[0;2m?^[[0m Help diff --git a/tests/fixtures/tui/colour-80x24.txt b/tests/fixtures/tui/colour-80x24.txt index efa50239c..3afb4a9c1 100644 --- a/tests/fixtures/tui/colour-80x24.txt +++ b/tests/fixtures/tui/colour-80x24.txt @@ -208,7 +208,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -222,7 +222,7 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === plan-in-place ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: RetroArch^[[0m @@ -231,7 +231,7 @@ ^[[0;7;35m > Close^[[0m Details - Options + Change @@ -247,7 +247,7 @@ - ^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Close ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === systems ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Choose systems^[[0m @@ -273,31 +273,81 @@ ^[[0;1m ^[[0;1;35m2^[[0;1m of ^[[0;1;35m3^[[0;1m systems: ^[[0;1;35m4^[[0;1m files, ^[[0;1;35m1.0 MB^[[0m ^[[0;1;35mA/Enter^[[0m Tick ^[[0;2mB/Esc^[[0m Done ^[[0;2m?^[[0m Help -=== options -^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Options^[[0m +=== change +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Change^[[0m - Each row is an option of the command line, named on the right. - +^[[0;1m What to install^[[0m ^[[0;7;35m > Preferred regions: North America, Japan --region^[[0m - Game data: kept --no-game-data - Installed cores only: no (1 found) --installed-cores Cores: all --core - Parallel downloads: 8 --jobs + Emulators: all --emulator + Game data: kept --no-game-data +^[[0;1m Where^[[0m BIOS folder: ^[[0;1m~/.config/retroarch/system^[[0m --dest Copy from: none --from + From a pack: none --from-pack +^[[0;1m For which device^[[0m + Device: every board --target + Installed cores only: no (1 found) --installed-cores +^[[0;1m How^[[0m + Without the internet: no --offline + Parallel downloads: 8 --jobs +^[[0;1m See^[[0m Preview the file list --dry-run Show the command line - - - - - - - ^[[0;1;35mA/Enter^[[0m Open ^[[0;2mB/Esc^[[0m Back ^[[0;2mLeft/Right^[[0m Set ^[[0;2m?^[[0m Help +=== device +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Device^[[0m + + Choose the board, the chip of the device the games run on. Every board fits + any device. + +^[[0;7;35m > (x) h700 named by the card^[[0m + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + ^[[0;1;35mA/Enter^[[0m Choose ^[[0;2mB/Esc^[[0m Back ^[[0;2m?^[[0m Help +=== emulators +^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Emulators^[[0m + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (/ to type) + [ ] mesen +^[[0;7;35m > [x] pcsx_rearmed^[[0m + None + ^[[0;32mDone^[[0m + + + + + + + + + + + + + + ^[[0;1;35mA/Enter^[[0m Tick ^[[0;2mB/Esc^[[0m Done ^[[0;2m?^[[0m Help === regions ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Preferred regions^[[0m @@ -528,7 +578,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit @@ -562,7 +612,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -572,7 +622,7 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help === games ^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Games on this card^[[0m @@ -661,7 +711,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -672,4 +722,4 @@ - ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Options ^[[0;2m?^[[0m Help + ^[[0;1;35mA/Enter^[[0m Install ^[[0;2mB/Esc^[[0m Back ^[[0;2mY/o^[[0m Change ^[[0;2m?^[[0m Help diff --git a/tests/fixtures/tui/device-120x40.txt b/tests/fixtures/tui/device-120x40.txt new file mode 100644 index 000000000..cee90bc19 --- /dev/null +++ b/tests/fixtures/tui/device-120x40.txt @@ -0,0 +1,40 @@ + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A/Enter Choose B/Esc Back ? Help diff --git a/tests/fixtures/tui/device-40x12.txt b/tests/fixtures/tui/device-40x12.txt new file mode 100644 index 000000000..d1b269bd3 --- /dev/null +++ b/tests/fixtures/tui/device-40x12.txt @@ -0,0 +1,12 @@ + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + A Choose B Back ? Help diff --git a/tests/fixtures/tui/device-40x15.txt b/tests/fixtures/tui/device-40x15.txt new file mode 100644 index 000000000..0af73258c --- /dev/null +++ b/tests/fixtures/tui/device-40x15.txt @@ -0,0 +1,15 @@ + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + A Choose B Back ? Help diff --git a/tests/fixtures/tui/device-80x24.txt b/tests/fixtures/tui/device-80x24.txt new file mode 100644 index 000000000..69e25a136 --- /dev/null +++ b/tests/fixtures/tui/device-80x24.txt @@ -0,0 +1,24 @@ + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + A/Enter Choose B/Esc Back ? Help diff --git a/tests/fixtures/tui/emulators-120x40.txt b/tests/fixtures/tui/emulators-120x40.txt new file mode 100644 index 000000000..9b22a8daf --- /dev/null +++ b/tests/fixtures/tui/emulators-120x40.txt @@ -0,0 +1,40 @@ + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A/Enter Tick B/Esc Done ? Help diff --git a/tests/fixtures/tui/emulators-40x12.txt b/tests/fixtures/tui/emulators-40x12.txt new file mode 100644 index 000000000..af9e5c9b6 --- /dev/null +++ b/tests/fixtures/tui/emulators-40x12.txt @@ -0,0 +1,12 @@ + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + A Tick B Done ? Help diff --git a/tests/fixtures/tui/emulators-40x15.txt b/tests/fixtures/tui/emulators-40x15.txt new file mode 100644 index 000000000..9ac55937e --- /dev/null +++ b/tests/fixtures/tui/emulators-40x15.txt @@ -0,0 +1,15 @@ + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + A Tick B Done ? Help diff --git a/tests/fixtures/tui/emulators-80x24.txt b/tests/fixtures/tui/emulators-80x24.txt new file mode 100644 index 000000000..50f161fbd --- /dev/null +++ b/tests/fixtures/tui/emulators-80x24.txt @@ -0,0 +1,24 @@ + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + A/Enter Tick B/Esc Done ? Help diff --git a/tests/fixtures/tui/help-120x40.txt b/tests/fixtures/tui/help-120x40.txt index edf48ff0f..79457549b 100644 --- a/tests/fixtures/tui/help-120x40.txt +++ b/tests/fixtures/tui/help-120x40.txt @@ -2,7 +2,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit diff --git a/tests/fixtures/tui/help-40x12.txt b/tests/fixtures/tui/help-40x12.txt index 5362abd16..301e3d15c 100644 --- a/tests/fixtures/tui/help-40x12.txt +++ b/tests/fixtures/tui/help-40x12.txt @@ -2,7 +2,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit diff --git a/tests/fixtures/tui/help-40x15.txt b/tests/fixtures/tui/help-40x15.txt index 4ee7e371c..c65e70924 100644 --- a/tests/fixtures/tui/help-40x15.txt +++ b/tests/fixtures/tui/help-40x15.txt @@ -2,7 +2,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit diff --git a/tests/fixtures/tui/help-80x24.txt b/tests/fixtures/tui/help-80x24.txt index b22ec4987..17cecfb79 100644 --- a/tests/fixtures/tui/help-80x24.txt +++ b/tests/fixtures/tui/help-80x24.txt @@ -2,7 +2,7 @@ Up/Down, k/j Move A/Enter, Start Install - Y, o, Tab Options + Y, o, Tab Change d Details B/Esc, Backspace Back q, Ctrl-C Quit diff --git a/tests/fixtures/tui/plan-120x40.txt b/tests/fixtures/tui/plan-120x40.txt index ca67ddd86..e13a1f79c 100644 --- a/tests/fixtures/tui/plan-120x40.txt +++ b/tests/fixtures/tui/plan-120x40.txt @@ -7,7 +7,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -37,4 +37,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-40x12.txt b/tests/fixtures/tui/plan-40x12.txt index 26f08e468..86f6ecb9e 100644 --- a/tests/fixtures/tui/plan-40x12.txt +++ b/tests/fixtures/tui/plan-40x12.txt @@ -7,6 +7,6 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-40x15.txt b/tests/fixtures/tui/plan-40x15.txt index 5e2e6a6ea..7f1b90d34 100644 --- a/tests/fixtures/tui/plan-40x15.txt +++ b/tests/fixtures/tui/plan-40x15.txt @@ -8,8 +8,8 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-80x24.txt b/tests/fixtures/tui/plan-80x24.txt index 09a860db6..4465455ca 100644 --- a/tests/fixtures/tui/plan-80x24.txt +++ b/tests/fixtures/tui/plan-80x24.txt @@ -7,7 +7,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -21,4 +21,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-games-120x40.txt b/tests/fixtures/tui/plan-games-120x40.txt index 157d4e6e9..abde59b88 100644 --- a/tests/fixtures/tui/plan-games-120x40.txt +++ b/tests/fixtures/tui/plan-games-120x40.txt @@ -11,7 +11,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -37,4 +37,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-games-40x12.txt b/tests/fixtures/tui/plan-games-40x12.txt index 800da31b1..74547d4d2 100644 --- a/tests/fixtures/tui/plan-games-40x12.txt +++ b/tests/fixtures/tui/plan-games-40x12.txt @@ -9,4 +9,4 @@ Only what starts: no Show the games 1-4 of 9 - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-games-40x15.txt b/tests/fixtures/tui/plan-games-40x15.txt index 2d208f0e1..86f7e3feb 100644 --- a/tests/fixtures/tui/plan-games-40x15.txt +++ b/tests/fixtures/tui/plan-games-40x15.txt @@ -10,6 +10,6 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-games-80x24.txt b/tests/fixtures/tui/plan-games-80x24.txt index 81c89f428..38ae53edb 100644 --- a/tests/fixtures/tui/plan-games-80x24.txt +++ b/tests/fixtures/tui/plan-games-80x24.txt @@ -11,7 +11,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -21,4 +21,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-in-place-120x40.txt b/tests/fixtures/tui/plan-in-place-120x40.txt index 7001d8a90..466492ed6 100644 --- a/tests/fixtures/tui/plan-in-place-120x40.txt +++ b/tests/fixtures/tui/plan-in-place-120x40.txt @@ -5,7 +5,7 @@ > Close Details - Options + Change @@ -37,4 +37,4 @@ - A/Enter Close B/Esc Back Y/o Options ? Help + A/Enter Close B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-in-place-40x12.txt b/tests/fixtures/tui/plan-in-place-40x12.txt index 7b46aabc7..360413780 100644 --- a/tests/fixtures/tui/plan-in-place-40x12.txt +++ b/tests/fixtures/tui/plan-in-place-40x12.txt @@ -6,7 +6,7 @@ > Close Details - Options + Change - A Close B Back Y Options ? Help + A Close B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-in-place-40x15.txt b/tests/fixtures/tui/plan-in-place-40x15.txt index 182af92d6..15e3895ac 100644 --- a/tests/fixtures/tui/plan-in-place-40x15.txt +++ b/tests/fixtures/tui/plan-in-place-40x15.txt @@ -6,10 +6,10 @@ > Close Details - Options + Change - A Close B Back Y Options ? Help + A Close B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-in-place-80x24.txt b/tests/fixtures/tui/plan-in-place-80x24.txt index 36673c825..66f1f191e 100644 --- a/tests/fixtures/tui/plan-in-place-80x24.txt +++ b/tests/fixtures/tui/plan-in-place-80x24.txt @@ -5,7 +5,7 @@ > Close Details - Options + Change @@ -21,4 +21,4 @@ - A/Enter Close B/Esc Back Y/o Options ? Help + A/Enter Close B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-record-120x40.txt b/tests/fixtures/tui/plan-record-120x40.txt index acea8e5bc..4a761b638 100644 --- a/tests/fixtures/tui/plan-record-120x40.txt +++ b/tests/fixtures/tui/plan-record-120x40.txt @@ -10,7 +10,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -37,4 +37,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/plan-record-40x12.txt b/tests/fixtures/tui/plan-record-40x12.txt index 439d2a286..ef764f841 100644 --- a/tests/fixtures/tui/plan-record-40x12.txt +++ b/tests/fixtures/tui/plan-record-40x12.txt @@ -9,4 +9,4 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-record-40x15.txt b/tests/fixtures/tui/plan-record-40x15.txt index 0ca0559e4..778f75173 100644 --- a/tests/fixtures/tui/plan-record-40x15.txt +++ b/tests/fixtures/tui/plan-record-40x15.txt @@ -10,6 +10,6 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - A Install B Back Y Options ? Help + A Install B Back Y Change ? Help diff --git a/tests/fixtures/tui/plan-record-80x24.txt b/tests/fixtures/tui/plan-record-80x24.txt index cacafb755..b32d9a48a 100644 --- a/tests/fixtures/tui/plan-record-80x24.txt +++ b/tests/fixtures/tui/plan-record-80x24.txt @@ -10,7 +10,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -21,4 +21,4 @@ - A/Enter Install B/Esc Back Y/o Options ? Help + A/Enter Install B/Esc Back Y/o Change ? Help diff --git a/tests/fixtures/tui/route-evmapy.txt b/tests/fixtures/tui/route-evmapy.txt index c25b7c0e5..087fda8c0 100644 --- a/tests/fixtures/tui/route-evmapy.txt +++ b/tests/fixtures/tui/route-evmapy.txt @@ -411,9 +411,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 40x15 RetroBIOS: RetroArch @@ -425,11 +425,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 80x24 RetroBIOS: RetroArch @@ -440,7 +440,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -454,7 +454,7 @@ - A Install B Back Y Options + A Install B Back Y Change === plan-in-place 40x12 RetroBIOS: RetroArch @@ -464,10 +464,10 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 40x15 RetroBIOS: RetroArch @@ -477,13 +477,13 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 80x24 RetroBIOS: RetroArch @@ -492,7 +492,7 @@ > Close Details - Options + Change @@ -508,7 +508,7 @@ - A Close B Back Y Options + A Close B Back Y Change === systems 40x12 RetroBIOS: Choose systems @@ -563,60 +563,168 @@ 2 of 3 systems: 4 files, 1.0 MB A Tick B Done -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 A Open B Back <> Set -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 A Open B Back <> Set -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - A Open B Back Left/Right Set +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + A Choose B Back +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + A Choose B Back +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + A Choose B Back +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + A Tick B Done +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + A Tick B Done +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + A Tick B Done === regions 40x12 RetroBIOS: Preferred regions @@ -976,7 +1084,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -989,7 +1097,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1005,7 +1113,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1037,7 +1145,7 @@ Only what starts: no Show the games 1-4 of 9 - A Install B Back Y Options + A Install B Back Y Change === plan-games 40x15 RetroBIOS: RetroArch @@ -1051,9 +1159,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-games 80x24 RetroBIOS: RetroArch @@ -1068,7 +1176,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1078,7 +1186,7 @@ - A Install B Back Y Options + A Install B Back Y Change === games 80x24 RetroBIOS: Games on this card @@ -1195,7 +1303,7 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - A Install B Back Y Options + A Install B Back Y Change === plan-record 40x15 RetroBIOS: RetroArch @@ -1209,9 +1317,9 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-record 80x24 RetroBIOS: RetroArch @@ -1225,7 +1333,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1236,4 +1344,4 @@ - A Install B Back Y Options + A Install B Back Y Change diff --git a/tests/fixtures/tui/route-gptokeyb.txt b/tests/fixtures/tui/route-gptokeyb.txt index 5816841a0..8c13bce43 100644 --- a/tests/fixtures/tui/route-gptokeyb.txt +++ b/tests/fixtures/tui/route-gptokeyb.txt @@ -411,9 +411,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 40x15 RetroBIOS: RetroArch @@ -425,11 +425,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 80x24 RetroBIOS: RetroArch @@ -440,7 +440,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -454,7 +454,7 @@ - A Install B Back Y Options + A Install B Back Y Change === plan-in-place 40x12 RetroBIOS: RetroArch @@ -464,10 +464,10 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 40x15 RetroBIOS: RetroArch @@ -477,13 +477,13 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 80x24 RetroBIOS: RetroArch @@ -492,7 +492,7 @@ > Close Details - Options + Change @@ -508,7 +508,7 @@ - A Close B Back Y Options + A Close B Back Y Change === systems 40x12 RetroBIOS: Choose systems @@ -563,60 +563,168 @@ 2 of 3 systems: 4 files, 1.0 MB A Tick B Done -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 A Open B Back <> Set -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 A Open B Back <> Set -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - A Open B Back Left/Right Set +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + A Choose B Back +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + A Choose B Back +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + A Choose B Back +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + A Tick B Done +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + A Tick B Done +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + A Tick B Done === regions 40x12 RetroBIOS: Preferred regions @@ -976,7 +1084,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -989,7 +1097,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1005,7 +1113,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1037,7 +1145,7 @@ Only what starts: no Show the games 1-4 of 9 - A Install B Back Y Options + A Install B Back Y Change === plan-games 40x15 RetroBIOS: RetroArch @@ -1051,9 +1159,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-games 80x24 RetroBIOS: RetroArch @@ -1068,7 +1176,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1078,7 +1186,7 @@ - A Install B Back Y Options + A Install B Back Y Change === games 80x24 RetroBIOS: Games on this card @@ -1195,7 +1303,7 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - A Install B Back Y Options + A Install B Back Y Change === plan-record 40x15 RetroBIOS: RetroArch @@ -1209,9 +1317,9 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-record 80x24 RetroBIOS: RetroArch @@ -1225,7 +1333,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1236,4 +1344,4 @@ - A Install B Back Y Options + A Install B Back Y Change diff --git a/tests/fixtures/tui/route-gptokeyb_muxterm.txt b/tests/fixtures/tui/route-gptokeyb_muxterm.txt index 526fa0b87..5c305378f 100644 --- a/tests/fixtures/tui/route-gptokeyb_muxterm.txt +++ b/tests/fixtures/tui/route-gptokeyb_muxterm.txt @@ -411,9 +411,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 40x15 RetroBIOS: RetroArch @@ -425,11 +425,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan 80x24 RetroBIOS: RetroArch @@ -440,7 +440,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -454,7 +454,7 @@ - A Install B Back Y Options + A Install B Back Y Change === plan-in-place 40x12 RetroBIOS: RetroArch @@ -464,10 +464,10 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 40x15 RetroBIOS: RetroArch @@ -477,13 +477,13 @@ > Close Details - Options + Change - A Close B Back Y Options + A Close B Back Y Change === plan-in-place 80x24 RetroBIOS: RetroArch @@ -492,7 +492,7 @@ > Close Details - Options + Change @@ -508,7 +508,7 @@ - A Close B Back Y Options + A Close B Back Y Change === systems 40x12 RetroBIOS: Choose systems @@ -563,60 +563,168 @@ 2 of 3 systems: 4 files, 1.0 MB A Tick B Done -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 A Open B Back <> Set -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 A Open B Back <> Set -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - A Open B Back Left/Right Set +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + A Choose B Back +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + A Choose B Back +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + A Choose B Back +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + A Tick B Done +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + A Tick B Done +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + A Tick B Done === regions 40x12 RetroBIOS: Preferred regions @@ -976,7 +1084,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -989,7 +1097,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1005,7 +1113,7 @@ D-pad, Up/Down Move A, Start, Enter Install - Y, Tab, o Options + Y, Tab, o Change d Details B, Select, Esc Back q, Ctrl-C Quit @@ -1037,7 +1145,7 @@ Only what starts: no Show the games 1-4 of 9 - A Install B Back Y Options + A Install B Back Y Change === plan-games 40x15 RetroBIOS: RetroArch @@ -1051,9 +1159,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-games 80x24 RetroBIOS: RetroArch @@ -1068,7 +1176,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1078,7 +1186,7 @@ - A Install B Back Y Options + A Install B Back Y Change === games 80x24 RetroBIOS: Games on this card @@ -1195,7 +1303,7 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - A Install B Back Y Options + A Install B Back Y Change === plan-record 40x15 RetroBIOS: RetroArch @@ -1209,9 +1317,9 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - A Install B Back Y Options + A Install B Back Y Change === plan-record 80x24 RetroBIOS: RetroArch @@ -1225,7 +1333,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1236,4 +1344,4 @@ - A Install B Back Y Options + A Install B Back Y Change diff --git a/tests/fixtures/tui/route-keys.txt b/tests/fixtures/tui/route-keys.txt index 7e0713cf4..d00c6633c 100644 --- a/tests/fixtures/tui/route-keys.txt +++ b/tests/fixtures/tui/route-keys.txt @@ -386,9 +386,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan 40x15 RetroBIOS: RetroArch @@ -400,11 +400,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan 80x24 RetroBIOS: RetroArch @@ -415,7 +415,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -429,7 +429,7 @@ - Enter Install Esc Back o Options ? Help + Enter Install Esc Back o Change ? Help === plan-in-place 40x12 RetroBIOS: RetroArch @@ -439,10 +439,10 @@ > Close Details - Options + Change - Enter Close Esc Back o Options + Enter Close Esc Back o Change === plan-in-place 40x15 RetroBIOS: RetroArch @@ -452,13 +452,13 @@ > Close Details - Options + Change - Enter Close Esc Back o Options + Enter Close Esc Back o Change === plan-in-place 80x24 RetroBIOS: RetroArch @@ -467,7 +467,7 @@ > Close Details - Options + Change @@ -483,7 +483,7 @@ - Enter Close Esc Back o Options ? Help + Enter Close Esc Back o Change ? Help === systems 40x12 RetroBIOS: Choose systems @@ -538,60 +538,168 @@ 2 of 3 systems: 4 files, 1.0 MB Enter Tick Esc Done ? Help -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 Enter Open Esc Back <> Set ? Help -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 Enter Open Esc Back <> Set ? Help -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - Enter Open Esc Back Left/Right Set ? Help +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + Enter Choose Esc Back ? Help +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + Enter Choose Esc Back ? Help +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + Enter Choose Esc Back ? Help +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + Enter Tick Esc Done ? Help +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + Enter Tick Esc Done ? Help +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (/ to type) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + Enter Tick Esc Done ? Help === regions 40x12 RetroBIOS: Preferred regions @@ -1083,7 +1191,7 @@ Up/Down, k/j Move Enter Install - o, Tab Options + o, Tab Change d Details Esc, Backspace Back q, Ctrl-C Quit @@ -1096,7 +1204,7 @@ Up/Down, k/j Move Enter Install - o, Tab Options + o, Tab Change d Details Esc, Backspace Back q, Ctrl-C Quit @@ -1112,7 +1220,7 @@ Up/Down, k/j Move Enter Install - o, Tab Options + o, Tab Change d Details Esc, Backspace Back q, Ctrl-C Quit @@ -1144,7 +1252,7 @@ Only what starts: no Show the games 1-4 of 9 - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan-games 40x15 RetroBIOS: RetroArch @@ -1158,9 +1266,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan-games 80x24 RetroBIOS: RetroArch @@ -1175,7 +1283,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1185,7 +1293,7 @@ - Enter Install Esc Back o Options ? Help + Enter Install Esc Back o Change ? Help === games 40x12 RetroBIOS: Games on this card @@ -1360,7 +1468,7 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan-record 40x15 RetroBIOS: RetroArch @@ -1374,9 +1482,9 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - Enter Install Esc Back o Options + Enter Install Esc Back o Change === plan-record 80x24 RetroBIOS: RetroArch @@ -1390,7 +1498,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1401,4 +1509,4 @@ - Enter Install Esc Back o Options ? Help + Enter Install Esc Back o Change ? Help diff --git a/tests/fixtures/tui/route-miyoo.txt b/tests/fixtures/tui/route-miyoo.txt index 96e86e306..d992c8764 100644 --- a/tests/fixtures/tui/route-miyoo.txt +++ b/tests/fixtures/tui/route-miyoo.txt @@ -440,9 +440,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan 40x15 RetroBIOS: RetroArch @@ -454,11 +454,11 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan 80x24 RetroBIOS: RetroArch @@ -469,7 +469,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -483,7 +483,7 @@ - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan-in-place 40x12 RetroBIOS: RetroArch @@ -493,10 +493,10 @@ > Close Details - Options + Change - A Close R2 Back L2 Options + A Close R2 Back L2 Change === plan-in-place 40x15 RetroBIOS: RetroArch @@ -506,13 +506,13 @@ > Close Details - Options + Change - A Close R2 Back L2 Options + A Close R2 Back L2 Change === plan-in-place 80x24 RetroBIOS: RetroArch @@ -521,7 +521,7 @@ > Close Details - Options + Change @@ -537,7 +537,7 @@ - A Close R2 Back L2 Options + A Close R2 Back L2 Change === systems 40x12 RetroBIOS: Choose systems @@ -592,60 +592,168 @@ 2 of 3 systems: 4 files, 1.0 MB A Tick R2 Done -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 A Open R2 Back <> Set -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 A Open R2 Back <> Set -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - A Open R2 Back Left/Right Set +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + A Choose R2 Back +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + A Choose R2 Back +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + A Choose R2 Back +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + A Tick R2 Done +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + A Tick R2 Done +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (with a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + A Tick R2 Done === regions 40x12 RetroBIOS: Preferred regions @@ -1079,7 +1187,7 @@ D-pad, Up/Down Move A, Start, Enter Install - L2, Tab, o Options + L2, Tab, o Change d Details R2, Esc, Backspace Back q, Ctrl-C Quit @@ -1092,7 +1200,7 @@ D-pad, Up/Down Move A, Start, Enter Install - L2, Tab, o Options + L2, Tab, o Change d Details R2, Esc, Backspace Back q, Ctrl-C Quit @@ -1108,7 +1216,7 @@ D-pad, Up/Down Move A, Start, Enter Install - L2, Tab, o Options + L2, Tab, o Change d Details R2, Esc, Backspace Back q, Ctrl-C Quit @@ -1140,7 +1248,7 @@ Only what starts: no Show the games 1-4 of 9 - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan-games 40x15 RetroBIOS: RetroArch @@ -1154,9 +1262,9 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan-games 80x24 RetroBIOS: RetroArch @@ -1171,7 +1279,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1181,7 +1289,7 @@ - A Install R2 Back L2 Options + A Install R2 Back L2 Change === games 80x24 RetroBIOS: Games on this card @@ -1298,7 +1406,7 @@ Choose systems Copy from an old card or a folder 1-4 of 8 - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan-record 40x15 RetroBIOS: RetroArch @@ -1312,9 +1420,9 @@ Copy from an old card or a folder Change where Details - Options + Change Quit - A Install R2 Back L2 Options + A Install R2 Back L2 Change === plan-record 80x24 RetroBIOS: RetroArch @@ -1328,7 +1436,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit @@ -1339,4 +1447,4 @@ - A Install R2 Back L2 Options + A Install R2 Back L2 Change diff --git a/tests/fixtures/tui/route-muxterm.txt b/tests/fixtures/tui/route-muxterm.txt index 47490ee63..15e6e44e9 100644 --- a/tests/fixtures/tui/route-muxterm.txt +++ b/tests/fixtures/tui/route-muxterm.txt @@ -440,7 +440,7 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit Start Install B Back === plan 40x15 @@ -454,7 +454,7 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit @@ -469,7 +469,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -493,7 +493,7 @@ > Close Details - Options + Change Start Close B Back @@ -506,7 +506,7 @@ > Close Details - Options + Change @@ -521,7 +521,7 @@ > Close Details - Options + Change @@ -592,60 +592,168 @@ 2 of 3 systems: 4 files, 1.0 MB Start Tick B Done -=== options 40x12 - RetroBIOS: Options +=== change 40x12 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line + 1-8 of 18 Start Open B Back <> Set -=== options 40x15 - RetroBIOS: Options +=== change 40x15 + RetroBIOS: Change + What to install > Preferred regions: North America, Ja - Game data: kept - Installed cores only: no (1 found) Cores: all - Parallel downloads: 8 + Emulators: all + Game data: kept + Where BIOS folder: ~/.config...ch/system Copy from: none - Preview the file list - Show the command line - - Sets --region - + From a pack: none + For which device + Device: every board + 1-11 of 18 Start Open B Back <> Set -=== options 80x24 - RetroBIOS: Options - - Each row is an option of the command line, named on the right. +=== change 80x24 + RetroBIOS: Change + What to install > 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 + Emulators: all --emulator + Game data: kept --no-game-data + Where BIOS folder: ~/.config/retroarch/system --dest Copy from: none --from + From a pack: none --from-pack + For which device + Device: every board --target + Installed cores only: no (1 found) --installed-cores + How + Without the internet: no --offline + Parallel downloads: 8 --jobs + See Preview the file list --dry-run Show the command line - - - - - - - Start Open B Back Left/Right Set +=== device 40x12 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + Start Choose B Back +=== device 40x15 + RetroBIOS: Device + + Choose the board, the chip of the + device the games run on. Every board + fits any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + Start Choose B Back +=== device 80x24 + RetroBIOS: Device + + Choose the board, the chip of the device the games run on. Every board fits + any device. + + > (x) h700 named by the card + ( ) bcm2711 rpi4 + ( ) x86_64 + ( ) Every board + + + + + + + + + + + + + + + Start Choose B Back +=== emulators 40x12 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (Select shows a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + Start Tick B Done +=== emulators 40x15 + RetroBIOS: Emulators + + Tick the emulators to install for. + With none ticked, every emulator's + files are installed. + + Filter (Select shows a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + Start Tick B Done +=== emulators 80x24 + RetroBIOS: Emulators + + Tick the emulators to install for. With none ticked, every emulator's files + are installed. + + Filter (Select shows a keyboard) + [ ] mesen + > [x] pcsx_rearmed + None + Done + + + + + + + + + + + + + + Start Tick B Done === regions 40x12 RetroBIOS: Preferred regions @@ -1137,7 +1245,7 @@ D-pad, Up/Down Move Start, Enter Install - o, Tab Options + o, Tab Change d Details B, Esc, Backspace Back q, Ctrl-C Quit @@ -1150,7 +1258,7 @@ D-pad, Up/Down Move Start, Enter Install - o, Tab Options + o, Tab Change d Details B, Esc, Backspace Back q, Ctrl-C Quit @@ -1166,7 +1274,7 @@ D-pad, Up/Down Move Start, Enter Install - o, Tab Options + o, Tab Change d Details B, Esc, Backspace Back q, Ctrl-C Quit @@ -1212,7 +1320,7 @@ Choose systems Copy from an old card or a folder Details - Options + Change Quit Start Install B Back === plan-games 80x24 @@ -1229,7 +1337,7 @@ Choose systems Copy the BIOS files from an old card or a folder Details - Options + Change Quit @@ -1428,7 +1536,7 @@ Copy from an old card or a folder Change where Details - Options + Change Quit Start Install B Back === plan-record 80x24 @@ -1444,7 +1552,7 @@ Copy the BIOS files from an old card or a folder Change where Details - Options + Change Quit diff --git a/tests/test_install_journeys.py b/tests/test_install_journeys.py index 5d95df491..08b057989 100644 --- a/tests/test_install_journeys.py +++ b/tests/test_install_journeys.py @@ -66,7 +66,11 @@ JOURNEY_WORDS = { "update": 70, "one_system": 179, "one_system_again": 67, - "what_is_missing": 207, + # Change holds every option one level down now (design 4.4): its + # headings and the rows for Emulators, a pack, the device and the + # internet, the intro sentence dropped, 10 words on each of its two + # frames. + "what_is_missing": 227, # The plan of the setup found comes first now (33 words), the setups # one Esc away, and the last plan names Change where (2 words). "another_device": 265, @@ -319,7 +323,7 @@ class Journeys(unittest.TestCase): term = self.start(repo) term.wait("files to download", timeout=40) self.press(term, "o") - term.wait("Each row is an option") + term.wait("RetroBIOS: Change") self.press(term, END, UP) term.wait("> Preview the file list") self.press(term, "\r") diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index e8e69cf95..f88185bb9 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -450,6 +450,12 @@ class HoldingPack: return (None, entry) +def option_focus(state: install.TuiState, action: str) -> None: + """The focus on the Change row doing action: its rows sit under + headings, which no index of the flat list kept.""" + state.focus["options"] = [row.action for row in install.tui_rows(state)].index(action) + + def press(state: install.TuiState, *pressed) -> list: effects = [] for key in pressed: @@ -495,6 +501,7 @@ class Flow(unittest.TestCase): state = checked_state() state.passthrough = started.passthrough state.from_folders = started.from_folders + state.packs = started.packs for run in ("check", "install"): argv = install.tui_argv(state, run, "py", "i.py") for folder in ("old-card", "backup.zip"): @@ -556,9 +563,12 @@ class Flow(unittest.TestCase): press(state, "o") self.assertEqual(state.screen, "options") rows = install.tui_rows(state) - self.assertEqual([row.right for row in rows], [ - "--region", "--no-game-data", "--installed-cores", "--core", "--jobs", "--dest", - "--from", "--dry-run", ""]) + # Grouped by what each decides, a heading above each group. + self.assertEqual([row.right or row.label for row in rows], [ + "What to install", "--region", "--core", "--emulator", "--no-game-data", + "Where", "--dest", "--from", "--from-pack", + "For which device", "--target", "--installed-cores", + "How", "--offline", "--jobs", "See", "--dry-run", "Show the command line"]) press(state, "enter") self.assertEqual(state.screen, "regions") # Ticked in the order of preference, then moved up. @@ -574,7 +584,10 @@ class Flow(unittest.TestCase): ["[2] Japan", "[1] North America", "None", "Done"]) press(state, "shift-down") self.assertEqual(state.regions, ["japan", "north-america"]) - press(state, "esc", "down", "enter", "down", "right", "down", "down", "right", "right") + # Down passes the headings: Game data, Installed cores only, then + # Parallel downloads. + press(state, "esc", "down", "down", "down", "enter", *["down"] * 5, "right", + "down", "down", "right", "right") self.assertTrue(state.no_game_data) self.assertTrue(state.installed_only) self.assertEqual(state.jobs, 10) @@ -586,7 +599,7 @@ class Flow(unittest.TestCase): def test_cores_checklist(self): state = checked_state() press(state, "o") - state.focus["options"] = 3 + option_focus(state, "cores") press(state, "enter") self.assertEqual(state.screen, "cores") self.assertEqual([row.value for row in install.tui_rows(state) @@ -610,7 +623,7 @@ class Flow(unittest.TestCase): def test_preview_is_a_dry_run(self): state = checked_state() press(state, "o") - state.focus["options"] = 7 + option_focus(state, "preview") self.assertEqual(press(state, "enter"), [("run", "preview")]) self.assertEqual(install.tui_argv(state, "preview", "py", "i.py")[-2:], ["--dry-run", "--events"]) @@ -805,7 +818,7 @@ class Flow(unittest.TestCase): def test_events_of_an_abandoned_run_are_ignored(self): state = checked_state() press(state, "o") - state.focus["options"] = 7 + option_focus(state, "preview") press(state, "enter") old = state.run_id self.assertEqual(press(state, "esc"), [("kill",)]) @@ -983,8 +996,15 @@ def golden_states() -> dict: states["systems"] = systems options = checked_state() options.stack.append("options") + options.focus["options"] = 1 options.regions = ["north-america", "japan"] - states["options"] = options + states["change"] = options + states["device"] = device_state() + emulators = checked_state() + emulators.stack += ["options", "emulators"] + emulators.emulators = ["pcsx_rearmed"] + emulators.focus["emulators"] = 2 + states["emulators"] = emulators regions = checked_state() regions.stack += ["options", "regions"] regions.regions = ["north-america"] @@ -1046,6 +1066,30 @@ def golden_states() -> dict: return states +# A board list as install/targets/ publishes it, read by Device: the boards +# by the names the list gives them, the device images each one runs. +TARGETS = {"bcm2711": {"cores": ["pcsx_rearmed"], "images": ["rpi4"]}, + "h700": {"cores": ["pcsx_rearmed", "mesen"], + "images": ["anbernic-rg35xx-plus", "anbernic-rg40xx-h"]}, + "x86_64": {"cores": None}} + + +def device_state(board: str = "h700") -> install.TuiState: + """Device opened from Change, the board list in, the card in the reader + naming its board (the check's board event).""" + state = checked_state() + if board: + state.board_seen = ("retroarch", state.dest, board, True, False) + state.stack.append("options") + install.tui_update(state, Key("home")) + rows = install.tui_rows(state) + state.focus["options"] = [row.action for row in rows].index("device") + effects = install.tui_update(state, Key("enter")) + assert effects == [("targets", "retroarch")], effects + install.tui_receive(state, ("targets", "retroarch", TARGETS, "")) + return state + + def record_setup(choice: list[str] | None = None) -> dict: """A record kept on 3 November of this year: the plan says 3 Nov in any year it runs.""" @@ -1465,12 +1509,12 @@ class PadRoutes(unittest.TestCase): 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", + "both": "A/Enter Install B/Esc Back Y/o Change ? Help", + "keys": "Enter Install Esc Back o Change ? Help", + "gptokeyb": "A Install B Back Y Change", + "evmapy": "A Install B Back Y Change", "muxterm": "Start Install B Back", - "miyoo": "A Install R2 Back L2 Options", + "miyoo": "A Install R2 Back L2 Change", } for route, bar in expected.items(): with self.subTest(route=route): @@ -2003,7 +2047,7 @@ class TicksBarAndPaths(unittest.TestCase): ticked = set(state.ticked) press(state, "esc", "o") self.assertEqual(state.screen, "options") - state.focus["options"] = 1 + option_focus(state, "game_data") press(state, "enter") self.assertTrue(state.no_game_data) effects = press(state, "esc") @@ -3257,12 +3301,12 @@ class SaveDirectoryLeftOut(unittest.TestCase): plan = " ".join(" ".join(install.tui_view(state, 120, 40)).split()) # The plan names the row that sets it, the result the option a # command carries. - self.assertIn(line + "Choose it in Options, Save folder (--save-dir). Its place is " + self.assertIn(line + "Choose it in Change, Save folder (--save-dir). Its place is " "not known.", plan) state.stack.append("result") state.code = 0 state.summary = {**summary, "mode": "install", "installed": 5} - self.assertIn(line + "Choose it in Options, Save folder (--save-dir), next time.", + self.assertIn(line + "Choose it in Change, Save folder (--save-dir), next time.", install.tui_outcome(state)[1]) @@ -4128,7 +4172,7 @@ class BackTicksAndSaveFolder(unittest.TestCase): state.manifest = {**state.manifest, "save_directory": {"folder": "saves", "by_core": True}} lines = install._unplaced_lines(summary, state) - self.assertIn("Options, Save folder (--save-dir)", lines[0]) + self.assertIn("Change, Save folder (--save-dir)", lines[0]) def test_a_size_keeps_its_unit(self): for width in range(20, 45): @@ -5095,7 +5139,7 @@ class EndToEnd(unittest.TestCase): term = self.start("--platform", "knulli", "--dest", str(root)) term.wait("files to download") term.send("o") - term.wait("Each row is an option") + term.wait("RetroBIOS: Change") for _step in range(20): if "> Put RetroBIOS on this card: no" in term.settle(): break @@ -5231,13 +5275,17 @@ class EndToEnd(unittest.TestCase): term = self.start("--platform", "retroarch", "--dest", str(repo.dest)) term.wait("6 files to download") term.send("o") - screen = term.wait("Each row is an option") + screen = term.wait("RetroBIOS: Change") self.assertIn("--no-game-data", screen) term.send("\r") term.wait("Preferred regions") term.send(DOWN, "\r", "\x1b") screen = term.wait("Preferred regions: North America") - term.send(DOWN, RIGHT, "\x1b") + for _step in range(8): + if "> Game data: kept" in term.settle(): + break + term.send(DOWN) + term.send(RIGHT, "\x1b") # Back on the plan, the check runs again under the new options. screen = term.wait("4 files to download") self.assertIn("> Install", screen) @@ -10688,7 +10736,7 @@ class MouseEnabled(unittest.TestCase): self.assertFalse(install.tui_wants_mouse(state, drawn(state, 80, 24))) # The result takes a tap on its Exit: its text is printed again in # the terminal once it closes, selected there as anywhere. - for name in ("setup", "platforms", "plan", "systems", "options", "error", "quit", + for name in ("setup", "platforms", "plan", "systems", "change", "error", "quit", "result"): with self.subTest(screen=name): state = states[name] @@ -10749,6 +10797,13 @@ class MouseOnEveryScreen(unittest.TestCase): self.assertEqual(lines[line].style == "focus", index == focus) # The whole width, the text's end or not. effects = tap(state, cols - 1, line) + if row_list[index].action == "heading": + # A heading names the rows under it: no + # choice, nothing happens. + self.assertEqual(effects, []) + self.assertEqual((state.stack, state.focus), + (base.stack, base.focus)) + continue if row_list[index].action in install.TUI_TICK_ROWS: # A tick is ticked as Space ticks it, focus # moved there first. @@ -13357,7 +13412,7 @@ class TheSettleRunsFromTheFrameShown(unittest.TestCase): term.wait("> Install") term.settle() term.send("o") - term.wait("RetroBIOS: Options") + term.wait("RetroBIOS: Change") term.settle() time.sleep(1.0) col, line = self.bar_entry(term, "Back") @@ -15119,7 +15174,7 @@ SCREEN_WORDS = { "folder-card": (52, 52, 50, 50), "browse": (17, 17, 15, 15), "path": (23, 23, 25, 25), "create": (23, 23, 21, 21), "checking": (18, 18, 17, 17), "plan": (31, 31, 31, 31), "plan-in-place": (22, 22, 19, 19), "systems": (36, 37, 36, 36), - "options": (40, 42, 59, 59), "regions": (45, 44, 42, 42), "cores": (47, 47, 45, 45), + "regions": (45, 44, 42, 42), "cores": (47, 47, 45, 45), "preview": (29, 29, 28, 28), "command": (22, 22, 16, 16), "confirm": (22, 22, 20, 20), "installing": (32, 32, 31, 31), "result": (33, 33, 31, 31), "error": (37, 34, 32, 31), "quit": (14, 14, 12, 12), "help": (24, 26, 26, 26), "plan-games": (36, 46, 54, 54), @@ -15129,6 +15184,13 @@ SCREEN_WORDS = { # press back to the whole list) and Change where (back to the setups, # the plan having opened on the one found). "plan-record": (38, 42, 42, 42), + # Change holds every option one level below the plan (design 4.4): the + # rows for Emulators, a pack, the device and the internet, and the five + # headings that group them, the intro sentence they replace dropped. + "change": (32, 42, 72, 72), + # A list of boards and one of emulators, each with the line that says + # what the choice does and the gloss of "board". + "device": (35, 35, 33, 33), "emulators": (29, 29, 27, 27), } @@ -15313,7 +15375,7 @@ class WhatToActOnStaysOnTheScreen(unittest.TestCase): state = checked_state() state.manifest = {**MANIFEST, "save_directory": {"folder": "saves", "by_core": True}} state.check_summary = {"skipped": [{"reason": "no_save_directory", "files": 2}]} - self.assertIn("Choose it in Options, Save folder (--save-dir).", self.text(state)) + self.assertIn("Choose it in Change, Save folder (--save-dir).", self.text(state)) def test_files_that_could_not_be_read_are_counted_where_no_row_says_so(self): unread = [{"event": "unreadable", "platform": "retroarch", "folder": "/media/OLD", @@ -15340,5 +15402,431 @@ class WhatToActOnStaysOnTheScreen(unittest.TestCase): self.assertNotIn("Read the first", text) + +class DeviceAndEmulatorScreens(unittest.TestCase): + """Device chooses --target and --every-board, Emulators --emulator, and + Change keeps every option one level below the plan, grouped by intent.""" + + def setUp(self): + patcher = mock.patch.object(install, "_mounted_cards", return_value=[]) + patcher.start() + self.addCleanup(patcher.stop) + + def text(self, state: install.TuiState, cols: int = 80, rows: int = 24) -> str: + return "\n".join(str(line) for line in install.tui_view(state, cols, rows)) + + def focus(self, state: install.TuiState, action: str, value: object = None) -> None: + rows = install.tui_rows(state) + state.focus[state.screen] = next( + index for index, row in enumerate(rows) + if row.action == action and (value is None or row.value == value)) + + def test_change_groups_every_option_under_headings_focus_skips(self): + state = checked_state() + press(state, "o") + rows = install.tui_rows(state) + headings = [row.label for row in rows if row.action == "heading"] + self.assertEqual(headings, list(install.TUI_CHANGE_GROUPS)) + self.assertNotEqual(rows[state.focus["options"]].action, "heading") + flags = {row.right for row in rows if row.action != "heading"} + for flag in ("--region", "--core", "--emulator", "--no-game-data", "--dest", "--from", + "--from-pack", "--target", "--installed-cores", "--jobs", "--offline", + "--dry-run"): + self.assertIn(flag, flags) + # Down and Up never stop on a heading, Home neither. + for key in ("down",) * len(rows) + ("up",) * len(rows) + ("home", "end", "home"): + press(state, key) + self.assertNotEqual(install.tui_rows(state)[state.focus["options"]].action, + "heading", key) + # Up on the first row stays: the list does not wrap past its heading. + press(state, "home", "up") + self.assertEqual(state.focus["options"], 1) + # The plan's row and bar name the screen. + plan = checked_state() + self.assertIn("Change", [row.label for row in install.tui_rows(plan)]) + self.assertIn("Y/o Change", self.text(plan)) + + def test_a_tap_on_a_heading_does_nothing(self): + state = checked_state() + press(state, "o") + before = dict(state.focus) + rows = install.tui_rows(state) + index = next(i for i, row in enumerate(rows) if row.action == "heading") + state.cols, state.rows = 80, 24 + state.hits = install.tui_hits(state, install.tui_view(state, 80, 24)) + number = next(target[0] for target in state.hits.targets + if target[3][:2] == ("row", index)) + state.now = state.hits.shown + 1 + install.tui_mouse(state, install.TuiMouse(0, 3, number + 1, False)) + self.assertEqual(install.tui_mouse(state, install.TuiMouse(0, 3, number + 1, True)), []) + self.assertEqual(state.focus, before) + + def test_device_lists_the_card_board_first_and_chosen(self): + state = device_state() + self.assertEqual(state.screen, "device") + rows = install.tui_rows(state) + self.assertEqual([row.label for row in rows], + ["(x) h700", "( ) bcm2711", "( ) x86_64", "( ) Every board"]) + self.assertEqual(rows[0].right, "named by the card") + self.assertEqual(rows[1].right, "rpi4") + self.assertEqual(state.focus["device"], 0) + # Chosen as it is: nothing to say on the command line. + press(state, "enter") + self.assertEqual(state.screen, "options") + self.assertEqual([flag for flag in install.tui_flags(state) + if flag[0] in ("--target", "--every-board")], []) + self.assertIn("Device: h700, named by the card", self.text(state)) + + def test_on_the_device_itself_it_says_this_device(self): + state = checked_state() + state.board_seen = ("retroarch", state.dest, "bcm2711", True, True) + state.stack.append("options") + self.assertIn("Device: bcm2711, this device", self.text(state)) + + def test_the_board_event_is_kept_across_checks(self): + state = checked_state() + state.run, state.running = "check", True + install.tui_receive(state, ("event", state.run_id, { + "event": "board", "platform": "retroarch", "board": "h700", "applied": True, + "message": "Board h700 found on the card (--every-board installs all boards)"})) + with mock.patch.object(install, "detect_board", return_value=None): + self.assertEqual(install._board_seen(state)[:2], ("h700", True)) + install._start_run(state, "check") + self.assertEqual(install._board_seen(state)[:2], ("h700", True)) + + def test_another_board_and_every_board_set_their_flag(self): + state = device_state() + self.focus(state, "board", "bcm2711") + press(state, "enter") + self.assertIn(("--target", "bcm2711"), install.tui_flags(state)) + self.assertIn("--target=bcm2711", install.tui_argv(state, "install", "py", "i.py")) + self.assertIn("Device: bcm2711", self.text(state)) + # Back on the plan, counted again for the board. + self.assertEqual(press(state, "esc"), [("run", "check")]) + state = device_state() + self.focus(state, "board", "") + press(state, "enter") + self.assertEqual([flag for flag in install.tui_flags(state) + if flag[0] in ("--target", "--every-board")], + [("--every-board", None)]) + self.assertIn("Device: every board", self.text(state)) + + def test_with_no_board_read_every_board_is_the_choice_and_no_flag(self): + state = device_state(board="") + self.assertEqual(install.tui_rows(state)[-1].label, "(x) Every board") + self.assertEqual(state.focus["device"], 3) + press(state, "enter") + self.assertEqual([flag for flag in install.tui_flags(state) + if flag[0] in ("--target", "--every-board")], []) + + def test_a_board_list_that_cannot_be_read_is_said(self): + state = checked_state() + state.stack.append("options") + self.focus(state, "device") + press(state, "enter") + self.assertIn("Reading the board list...", self.text(state)) + install.tui_receive(state, ("targets", "retroarch", None, + "The board list could not be read.")) + self.assertIn("The board list could not be read.", self.text(state)) + self.assertEqual(press(state, "esc"), []) + self.assertEqual(state.screen, "options") + + def test_the_command_line_choice_is_the_one_shown(self): + args = _args(platform="retroarch", dest=SYSTEM_DIR, target="bcm2711", + emulator=["mesen"]) + state, _effects = install.tui_start(args, "linux", detect=False) + self.assertEqual((state.target, state.emulators), ("bcm2711", ["mesen"])) + self.assertNotIn("--target", [flag for flag, _value in state.passthrough]) + flags = install.tui_flags(state) + self.assertIn(("--target", "bcm2711"), flags) + self.assertIn(("--emulator", "mesen"), flags) + + def test_emulators_ticked_are_the_emulator_flags(self): + state = checked_state() + state.stack.append("options") + self.focus(state, "emulators") + press(state, "enter") + self.assertEqual(state.screen, "emulators") + rows = install.tui_rows(state) + self.assertEqual([row.label for row in rows if row.action == "emulator"], + ["[ ] mesen", "[ ] pcsx_rearmed"]) + self.assertEqual(rows[state.focus["emulators"]].value, "mesen") + press(state, " ") + self.assertEqual(state.emulators, ["mesen"]) + self.assertIn(("--emulator", "mesen"), install.tui_flags(state)) + press(state, "n") + self.assertEqual(state.emulators, []) + press(state, " ", "esc") + self.assertEqual(state.screen, "options") + self.assertIn("Emulators: mesen", self.text(state)) + + def test_without_the_internet_sets_offline_for_the_runs(self): + state = checked_state() + state.stack.append("options") + self.focus(state, "offline") + with mock.patch.dict(os.environ, {}, clear=False): + os.environ.pop(install.OFFLINE_VARIABLE, None) + self.assertIn("Without the internet: no", self.text(state)) + self.assertEqual(press(state, "enter"), [("offline", True)]) + install.set_offline(True) + self.assertIn("Without the internet: yes", self.text(state)) + self.assertIn(("--offline", None), install.tui_flags(state)) + self.assertEqual(press(state, "left"), [("offline", False)]) + install.set_offline(False) + self.assertNotIn(("--offline", None), install.tui_flags(state)) + + def test_from_a_pack_opens_the_browser_on_zips_and_reads_the_pack(self): + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + (root / "Downloads").mkdir() + pack = root / "Downloads" / "RetroArch_BIOS_Pack.zip" + pack.write_bytes(b"PK") + (root / "Downloads" / "notes.txt").write_text("x") + state = checked_state() + state.home = root + state.stack.append("options") + self.focus(state, "pack") + press(state, "enter") + self.assertEqual((state.screen, state.browse_for), ("browse", "pack")) + rows = install.tui_rows(state) + self.assertNotIn("use", [row.action for row in rows]) + self.assertEqual([row.label for row in rows if row.action == "zip"], + ["RetroArch_BIOS_Pack.zip"]) + self.focus(state, "zip") + self.assertIn("A/Enter Use this pack", self.text(state)) + self.assertEqual(press(state, "enter"), [("pack", pack)]) + # Refused: said, the browser stays. + install.tui_receive(state, ("pack_opened", pack, "Not a pack.")) + self.assertEqual(state.screen, "browse") + self.assertIn("Not a pack.", self.text(state)) + effects = install.tui_receive(state, ("pack_opened", pack, "")) + self.assertEqual(effects, [("fetch", "retroarch")]) + self.assertEqual(state.packs, [pack]) + self.assertIn(("--from-pack", str(pack)), install.tui_flags(state)) + + def test_open_tui_pack_refuses_another_platforms_pack(self): + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + pack = root / "Batocera_BIOS_Pack.zip" + with zipfile.ZipFile(pack, "w") as archive: + archive.writestr("manifest.json", json.dumps({"platform": "batocera", + "files": []})) + previous = install.LOCAL.pack + self.addCleanup(setattr, install.LOCAL, "pack", previous) + problem = install.open_tui_pack(pack, "retroarch") + self.assertIs(install.LOCAL.pack, previous) + self.assertTrue(problem) + + +def _parity_list(root: Path) -> tuple[Path, Path]: + """A RetroArch list whose cores per board pick other files, a shared + destination two cores want different bytes at, and the copies of every + file in a folder: an install from it needs no network.""" + payloads = root / "copies" + payloads.mkdir() + entries = [] + alternative = {} + for name, cores, systems in (("plat.bin", None, ["sony-playstation"]), + ("pcsx/extra.bin", ["pcsx_rearmed"], ["sony-playstation"]), + ("mesen/nes.bin", ["mesen"], ["nintendo-nes"]), + ("nes_plat.bin", None, ["nintendo-nes"]), + ("shared.bin", ["pcsx_rearmed"], ["sony-playstation"]), + ("shared.bin#mesen", ["mesen"], ["nintendo-nes"])): + payload = name.encode() * 8 + stored = name.replace("/", "_").replace("#", "_") + (payloads / stored).write_bytes(payload) + entry = {"dest": name.split("#")[0], "size": len(payload), + "sha1": hashlib.sha1(payload).hexdigest(), + "sha256": hashlib.sha256(payload).hexdigest(), + "repo_path": f"bios/{stored}", "cores": cores, "systems": systems} + if "#" in name: + alternative = {field: entry[field] for field in + ("sha1", "sha256", "size", "repo_path", "cores")} + continue + entries.append(entry) + next(entry for entry in entries if entry["dest"] == "shared.bin")["alternatives"] = [ + alternative] + listed = root / "list" + (listed / "targets").mkdir(parents=True) + manifest = {"manifest_version": 2, "platform": "retroarch", "files": entries, + "omitted_files": [], + "system_cores": {"sony-playstation": ["pcsx_rearmed"], + "nintendo-nes": ["mesen"]}, + "core_names": {"pcsx_rearmed": ["pcsx_rearmed"], "mesen": ["mesen"]}} + (listed / "retroarch.json").write_text(json.dumps(manifest)) + (listed / "targets" / "retroarch.json").write_text(json.dumps( + {"bcm2711": {"cores": ["pcsx_rearmed"]}, "h700": {"cores": ["mesen"]}})) + return listed / "retroarch.json", payloads + + +class ScreenChoicesInstallWhatTheirFlagInstalls(unittest.TestCase): + """A board chosen on Device and an emulator ticked on Emulators install, + file for file and byte for byte, what --target and --emulator install.""" + + def setUp(self): + patcher = mock.patch.object(install, "_mounted_cards", return_value=[]) + patcher.start() + self.addCleanup(patcher.stop) + self.root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, self.root, True) + self.listed, self.copies = _parity_list(self.root) + self.manifest = json.loads(self.listed.read_text()) + self.env = {**os.environ, "RETROBIOS_OS": "linux", "HOME": str(self.root), + install.OFFLINE_VARIABLE: "1"} + + def run_install(self, argv: list[str]) -> dict[str, str]: + proc = subprocess.run(argv, env=self.env, capture_output=True, text=True, + timeout=120, check=False, stdin=subprocess.DEVNULL) + self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr) + dest = Path(next(arg for arg in argv if arg.startswith("--dest=")).split("=", 1)[1]) + return {path.relative_to(dest).as_posix(): hashlib.sha1(path.read_bytes()).hexdigest() + for path in dest.rglob("*") if path.is_file()} + + def flag_run(self, name: str, *flags: str) -> dict[str, str]: + return self.run_install([sys.executable, str(REPO_ROOT / "install.py"), + "--platform=retroarch", + f"--dest={self.root / name / 'system'}", + f"--manifest={self.listed}", f"--from={self.copies}", + *flags, "--events", "--yes"]) + + def screen(self, name: str) -> install.TuiState: + """Change, over the plan of the list as a check leaves it.""" + state = install.TuiState(os_type="linux", home=self.root) + state.platform, state.dest = "retroarch", self.root / name / "system" + state.manifest, state.manifest_for = self.manifest, "retroarch" + state.manifest_path, state.manifest_given = self.listed, True + state.from_folders = [self.copies] + state.check = dict(CHECK) + state.stack = ["plan", "options"] + return state + + def screen_run(self, state: install.TuiState) -> dict[str, str]: + return self.run_install(install.tui_argv(state, "install", sys.executable, + str(REPO_ROOT / "install.py"))) + + @staticmethod + def choose(state: install.TuiState, action: str, value: object = None) -> None: + rows = install.tui_rows(state) + state.focus[state.screen] = next( + index for index, row in enumerate(rows) + if row.action == action and (value is None or row.value == value)) + + def test_a_board_chosen_on_device_installs_what_target_installs(self): + targets = install._validate_targets( + json.loads((self.listed.parent / "targets" / "retroarch.json").read_text())) + everything = self.flag_run("all") + chosen = {} + for board in ("bcm2711", "h700"): + with self.subTest(board=board): + state = self.screen(f"screen-{board}") + self.choose(state, "device") + self.assertEqual(press(state, "enter"), [("targets", "retroarch")]) + install.tui_receive(state, ("targets", "retroarch", targets, "")) + self.choose(state, "board", board) + press(state, "enter") + self.assertEqual(state.screen, "options") + chosen[board] = self.screen_run(state) + self.assertEqual(chosen[board], + self.flag_run(f"flag-{board}", "--target", board)) + self.assertNotEqual(chosen[board], everything) + # The two boards' cores want other bytes at one destination. + self.assertNotEqual(chosen["bcm2711"]["shared.bin"], chosen["h700"]["shared.bin"]) + + def test_every_board_installs_what_every_board_installs(self): + targets = install._validate_targets( + json.loads((self.listed.parent / "targets" / "retroarch.json").read_text())) + state = self.screen("screen-every") + state.board_seen = ("retroarch", state.dest, "h700", True, True) + self.choose(state, "device") + press(state, "enter") + install.tui_receive(state, ("targets", "retroarch", targets, "")) + self.choose(state, "board", "") + press(state, "enter") + self.assertEqual(self.screen_run(state), + self.flag_run("flag-every", "--every-board")) + + def test_an_emulator_ticked_installs_what_emulator_installs(self): + for emulator in ("mesen", "pcsx_rearmed"): + with self.subTest(emulator=emulator): + state = self.screen(f"screen-{emulator}") + self.choose(state, "emulators") + press(state, "enter") + self.choose(state, "emulator", emulator) + press(state, " ", "esc") + self.assertEqual(state.screen, "options") + chosen = self.screen_run(state) + self.assertEqual(chosen, self.flag_run(f"flag-{emulator}", + "--emulator", emulator)) + # The files of its systems, and its own copy of the shared one. + shared = next(entry for entry in self.manifest["files"] + if entry["dest"] == "shared.bin") + self.assertEqual(chosen["shared.bin"], shared["sha1"] if emulator == + "pcsx_rearmed" else shared["alternatives"][0]["sha1"]) + self.assertNotIn("nes_plat.bin" if emulator == "pcsx_rearmed" + else "plat.bin", chosen) + + +class EmulatorChoiceMatchesTheEmulatorPack(unittest.TestCase): + """An emulator ticked on Emulators against the pack generate_pack.py + --emulator builds for it, on the real lists: every file of the pack at + its destination with its bytes, and beside them only the files the + platform's own list names for the systems the emulator runs.""" + + CASES = (("retroarch", "handy"), ("retroarch", "beetle_psx"), ("retroarch", "mesen"), + ("batocera", "mesen"), ("batocera", "handy")) + + @classmethod + def setUpClass(cls): + db_path = REPO_ROOT / "database.json" + if not db_path.exists(): + raise unittest.SkipTest("database.json is not built") + if sys.version_info < (3, 9): + # The builder's floor: install.py alone keeps 3.8. + raise unittest.SkipTest("generate_pack.py needs Python 3.9 or newer") + sys.path.insert(0, str(REPO_ROOT / "scripts")) + import generate_pack as gp + from common import build_zip_contents_index + + cls.gp = gp + cls.db = json.loads(db_path.read_text(encoding="utf-8")) + cls.zip_contents = build_zip_contents_index(cls.db) + cls.by_path = {entry["path"]: sha1 for sha1, entry in cls.db["files"].items()} + + def pack(self, emulator: str) -> dict[str, str]: + record = self.gp.PackRecord() + with contextlib.redirect_stdout(io.StringIO()): + self.gp.generate_emulator_pack( + [emulator], str(REPO_ROOT / "emulators"), self.db, str(REPO_ROOT / "bios"), + str(REPO_ROOT / "tmp"), zip_contents=self.zip_contents, offline=True, + record=record) + return {member: self.by_path[record.sources[member]] for member in record.files} + + def test_each_case(self): + for plat, emulator in self.CASES: + with self.subTest(platform=plat, emulator=emulator): + manifest = json.loads((REPO_ROOT / "install" / f"{plat}.json").read_text()) + state = install.TuiState(os_type="linux") + state.platform, state.dest = plat, Path("/x/bios") + state.manifest, state.manifest_for = manifest, plat + state.stack = ["plan", "options", "emulators"] + rows = install.tui_rows(state) + state.focus["emulators"] = next(i for i, row in enumerate(rows) + if row.value == emulator) + press(state, " ") + argv = install.tui_argv(state, "install", "py", "install.py") + args = install.build_parser().parse_args(argv[2:]) + self.assertEqual(args.emulator, [emulator]) + with contextlib.redirect_stdout(io.StringIO()): + files, _omitted = install._narrow_to_emulators( + args.emulator, manifest, plat, manifest["files"], + manifest["omitted_files"]) + installed = {entry["dest"]: entry["sha1"] for entry in files} + packed = self.pack(emulator) + self.assertTrue(packed) + self.assertEqual({dest: installed.get(dest) for dest in packed}, packed) + for dest in set(installed) - set(packed): + entry = next(entry for entry in files if entry["dest"] == dest) + self.assertIsNone(entry.get("cores"), dest) + if __name__ == "__main__": unittest.main() From be2b243e1b1d6aed862ca62a4559b7bcce11a133 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 11 Nov 2026 10:44:02 +0100 Subject: [PATCH 2/3] docs: describe the change screen Device, Emulators, From a pack and Without the internet on the installer page, and the key table names Change. --- scripts/generate_installer_doc.py | 2 +- wiki/installer.md | 44 ++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/scripts/generate_installer_doc.py b/scripts/generate_installer_doc.py index 5a1b795e8..d01d61148 100644 --- a/scripts/generate_installer_doc.py +++ b/scripts/generate_installer_doc.py @@ -66,7 +66,7 @@ ACTIONS = { "page": "Page", "ok": "Continue, open, choose", "tick": "Tick, untick", - "options": "Options", + "options": "Change", "back": "Back, and quit on the first screen", "stop": "Stop an install", "kill": "Quit at once", diff --git a/wiki/installer.md b/wiki/installer.md index d14f107ba..180c7e7a8 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -186,18 +186,36 @@ keep, with the total to download under the list. It opens on its first system, every system ticked: A or Enter ticks or unticks the one in focus, and **All** and **None** (`a` and `n`) sit at the end of the list. B or Esc (**Done**) takes the choice back to the plan, which counts it before **Install**: the list never installs -by itself, and Done with nothing ticked is refused. **Options** sets the -preferred regions, game data, the installed cores only, the cores, the parallel -downloads, the folder and, for a list with files read from the frontend's save -directory, that directory. Each row names the option it sets, and **Show the -command line** gives the same choice as one command for a script. **Preview the -file list** shows what would be downloaded and writes nothing. **Copy the BIOS +by itself, and Done with nothing ticked is refused. **Change** holds every +other option, grouped by what it decides. **What to install**: the preferred +regions, the cores, the emulators and the game data. **Where**: the BIOS +folder, the save directory for a list with files read there, a folder to copy +from, and **From a pack**, a pack ZIP downloaded before +([`--from-pack`](#option-from-pack)), whose own list is then read. **For which +device**: **Device** and the installed cores only. **How**: **Without the +internet** ([`--offline`](#option-offline)) and the parallel downloads. +**See**: the preview and the command line. Up and Down pass the headings. Each +row names the option it sets, and **Show the command line** gives the same +choice as one command for a script. **Preview the file list** shows what would +be downloaded and writes nothing. + +**Device** lists the boards of the platform's board list by the names that +list gives them (`bcm2711`, `h700`), each with the devices it runs where the +list names them, then **Every board**. The board read on the device, or named +by the card in the reader (`boot/batocera.board`), comes first, marked +`this device` or `named by the card`, and is already chosen: it is what the +run does with nothing chosen. Another board sets +[`--target`](#option-target), **Every board** sets +[`--every-board`](#option-every-board) where a board was read. **Emulators** +ticks the emulators to install for, each one [`--emulator`](#option-emulator), +and none ticked installs for every emulator. A choice made on either screen +installs the files its option installs. **Copy the BIOS files from an old card or a folder** opens the folder browser on the first mounted card, and adds [`--from`](#copy-from-an-old-card): the plan then says how many files that folder holds and how many are left to download, or that it holds none of them. After an install on a muOS, KNULLI or ROCKNIX card, the result offers **Put RetroBIOS on this -card (update from the console later)**, and **Options** has the same row +card (update from the console later)**, and **Change** has the same row ([On a handheld](#on-a-handheld-the-retrobios-port)). Files the BIOS folder holds in the wrong place are counted on the plan, and **Show the files in the wrong place** lists each one with its reason. Back on the plan @@ -213,7 +231,7 @@ install starts. | Continue, open, choose | Enter, and Space outside a list of ticks | A, Start | | Tick, untick | Space, Right, Left | X, D-pad Right and Left (X alone inside muxterm, Y with muxterm's own keys) | | Back | Esc, Backspace | B, Select (B alone with muxterm's own keys, where Select shows its keyboard) | -| Options | `o`, Tab | Y | +| Change | `o`, Tab | Y | | Details | `d`, and `o` or Tab on the result | the Details row, Y on the result | | Filter a long list | `/` then type, Enter on a lone match chooses it | muxterm: Select shows a keyboard. Elsewhere none (the setup list puts the SD cards after RetroArch) | | Keys of the screen | `?`, F1 (F1 alone while typing a filter, where the help lists the keys that do not type) | | @@ -844,7 +862,7 @@ counts them (`3 copied from the wrong place in the BIOS folder`). By default the installer fetches everything the platform runs. At a terminal, the plan screen of the [full-screen interface](#the-full-screen-interface) -offers **Install**, **Choose systems** and **Options**. With `--plain`, or where +offers **Install**, **Choose systems** and **Change**. With `--plain`, or where the full screen cannot open (a terminal smaller than 40x12, `TERM=dumb`), the same choice comes as questions after detection. A RetroArch run with `--plain` prints this for the RetroArch list, `g` then pressed (written from install.py @@ -889,7 +907,7 @@ game data, and the data files of an emulator's own data folder (Dolphin's `g` leaves the game data of the choice out, as `--no-game-data` does, and counts again (`g` then keeps it). The plan screen has the same choice as a row of its own, **Game data: kept** with its count, one press toggling it to -**Game data: left out**, and Options keeps its **Game data** row too. Its +**Game data: left out**, and Change keeps its **Game data** row too. Its count follows the systems chosen, as `g` counts them, and a choice that holds no game data has no row. Given `--no-game-data`, neither the menu nor the plan offers the game data back. @@ -1591,7 +1609,7 @@ muxterm, as the muOS app does. | A, Start | Choose, continue | | B, Select | Back, and quit on the first screen | | X | Tick or untick a system | -| Y | Options | +| Y | Change | | L1, R1 | Page up, page down (not in muxterm, which keeps them for its own scrollback) | | Hold Select, then Start | Quit at once (PortMaster port, muOS, Tools) | @@ -1643,7 +1661,7 @@ gets the column that names both: | Page | PgUp/PgDn | PgUp/PgDn, L1/R1 | L1/R1, PgUp/PgDn | D-pad Left/Right | D-pad Left/Right | L1/R1, PgUp/PgDn | L1/R1, PgUp/PgDn | | Continue, open, choose | Enter | A/Enter, Start | A, Start, Enter | A, Start, Enter | Start, Enter | A, Start, Enter | A, Start, Enter | | Tick, untick | Space, Right/Left | X, Space, Right/Left | X, Space, Right/Left | X, Space | Y, Space | X, Space, Right/Left | A, Space, Right/Left | -| Options | o, Tab | Y, o, Tab | Y, Tab, o | Y, Tab, o | o, Tab | Y, Tab, o | L2, Tab, o | +| Change | o, Tab | Y, o, Tab | Y, Tab, o | Y, Tab, o | o, Tab | Y, Tab, o | L2, Tab, o | | Back, and quit on the first screen | Esc, Backspace | B/Esc, Backspace | B, Select, Esc | B, Select, Esc | B, Esc, Backspace | B, Select, Esc | R2, Esc, Backspace | | Stop an install | Esc, Ctrl-C | B/Esc, Ctrl-C | B, Esc, Ctrl-C | B, Esc, Ctrl-C | B, Esc, Ctrl-C | B, Esc, Ctrl-C | R2, Esc, Ctrl-C | | Quit at once | none | Select+Start | Select, then Start | Select, then Start | none | none | none | @@ -1701,7 +1719,7 @@ This works for every system above too. **The app, from a computer.** After an install on a muOS, KNULLI or ROCKNIX card, the installer can put RetroBIOS on that card too, so the console checks and updates its BIOS files later. The result screen offers **Put RetroBIOS on -this card (update from the console later)**, **Options** has the same row, the +this card (update from the console later)**, **Change** has the same row, the line questions ask once (`[y/N]`), and a script adds [`--add-app`](#option-add-app). Nothing is written without that choice. From a285be793014bbed7d7eb00bb7978f059a5ba101 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Wed, 11 Nov 2026 11:09:53 +0100 Subject: [PATCH 3/3] fix: hold the device screen choices Every board always sets --every-board. A board list that failed is fetched again, once and with the list's timeout. A pack is read only once the screen takes it, and a ZIP that is no pack is closed. Another setup drops the board and emulators, and names a new list lacks are left out and said. PgUp and Home never rest on a heading. Without the internet is refused for a target whose board list is not held. --- install.ps1 | 2 +- install.py | 140 ++++++++++++++++++++++------- install.sh | 2 +- install/launchers/index.json | 16 ++-- tests/test_install_tui.py | 169 +++++++++++++++++++++++++++++++++-- 5 files changed, 279 insertions(+), 50 deletions(-) diff --git a/install.ps1 b/install.ps1 index dcde7205f..f4d16af0a 100644 --- a/install.ps1 +++ b/install.ps1 @@ -16,7 +16,7 @@ # and Invoke-WebRequest throws on a cut transfer before the hash is read: # the site's build rewrites this line there (scripts/sitebootstrap.py). $mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." - $defaultInstallSha256 = "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b" + $defaultInstallSha256 = "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. $maximumInstallerBytes = 8MB diff --git a/install.py b/install.py index 8adbcc425..529bf6664 100644 --- a/install.py +++ b/install.py @@ -4734,7 +4734,8 @@ def local_targets(plat: str) -> "dict | None": sys.exit(1) -def fetch_targets(plat: str) -> "dict | None": +def fetch_targets(plat: str, attempts: int = MAX_RETRIES, + timeout: float | None = None) -> "dict | None": """The target core list: the one this run has at hand, else downloaded. Empty on 404, where the platform publishes no targets. None when the list @@ -4757,7 +4758,8 @@ def fetch_targets(plat: str) -> "dict | None": sys.exit(2) url = TARGETS_URL.format(platform=plat) try: - return _validate_targets(_fetch_json(url, MAX_TARGETS_BYTES, "target list")) + return _validate_targets(_fetch_json(url, MAX_TARGETS_BYTES, "target list", + attempts=attempts, timeout=timeout)) except urllib.error.HTTPError as exc: if exc.code == 404: return {} @@ -12443,7 +12445,12 @@ class PackSource: self.close() raise ValueError(f"{path.name} cannot be read as a ZIP ({exc}). A download " "cut short gives this: download it again.") from exc - self.listed = self._pack_list() + try: + self.listed = self._pack_list() + except ValueError: + # A ZIP that is no pack: its archives closed, not left open. + self.close() + raise self.by_sha1 = {f["sha1"]: f["path"] for f in self.listed} @staticmethod @@ -20117,11 +20124,13 @@ def _device_rows(state: TuiState) -> list[TuiRow]: def _choose_board(state: TuiState, name: str) -> list[tuple]: """A board chosen on Device, back on Change. The board the run reads - itself is no option: the command stays the one with nothing chosen.""" + itself is no option: the command stays the one with nothing chosen. + Every board is always said, the run otherwise reading a board itself + (one started with --target or --every-board read none).""" seen = _board_seen(state) read = seen[0] if seen is not None and seen[1] else "" - state.target = "" if name == read else name - state.every_board = not name and bool(read) + state.every_board = not name + state.target = "" if not name or name == read else name return _back(state) @@ -20412,6 +20421,37 @@ def _closed_status(state: TuiState, closed: tuple[Path, str]) -> TuiLine: "or give this user access to it.", "refusal") +def _set_platform(state: TuiState, plat: str) -> None: + """Another setup chosen: its list names other boards and emulators, so + the ones chosen for the setup before are not carried over.""" + if state.platform and plat != state.platform: + state.target, state.every_board, state.emulators = "", False, [] + state.targets, state.targets_for, state.targets_problem = None, "", "" + state.platform = plat + + +def _known_choice(state: TuiState, plat: str, manifest: dict) -> None: + """The emulators and the board chosen, kept to those the list now read + names, the others left out and said in one line: a pack's list can + lack what the network's held, and the check then failed.""" + names = emulator_names(manifest, manifest.get("files") or [], + manifest.get("omitted_files") or []) + gone = [f"--emulator {name}" for name in state.emulators if name not in names] + state.emulators = [name for name in state.emulators if name in names] + boards = state.targets if state.targets_for == plat else None + if LOCAL.manifest is not None or LOCAL.pack is not None: + try: + boards = local_targets(plat) + except SystemExit: + # A board list beside the list that is no list: the check says so. + boards = None + if state.target and boards is not None and state.target not in boards: + gone.append(f"--target {state.target}") + state.target = "" + if gone: + state.status = f"Left out, not in this list: {', '.join(gone)}." + + def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> list[tuple]: """dest taken for plat. proposed is a folder the installer offered (its usual or detected folder, a share drive's bios): created without the @@ -20422,7 +20462,8 @@ def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> l if closed is not None: # Nothing can be checked or written there: the folder is asked # again, on the screen that chose it when it can say why. - state.platform, state.status = plat, _closed_status(state, closed) + _set_platform(state, plat) + state.status = _closed_status(state, closed) if state.stack and state.screen in ("folder", "browse", "path"): return [] # The folder found here is the one the frontend reads: the built-in @@ -20434,7 +20475,8 @@ def _choose(state: TuiState, plat: str, dest: Path, proposed: bool = False) -> l if card and card[0] == plat and card[1] != dest: # A muOS or KNULLI card's root: that system reads a folder below it. dest = card[1] - state.platform, state.dest = plat, dest + _set_platform(state, plat) + state.dest = dest if plat in CARD_PLATFORMS and card_platform(dest) is None: # The engine refuses a folder inside the card, the list writing # Bios// below the root: the root is offered instead. @@ -20871,7 +20913,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: # Taken as the system chosen, as --platform with --dest takes it. plat, mount = row.value state.card_bound = card_bound(plat, plat) - state.platform = plat + _set_platform(state, plat) choice = card_choice(plat, plat, mount) if choice.refused: _card_status(state, choice) @@ -20884,7 +20926,7 @@ def _activate(state: TuiState, row: TuiRow) -> list[tuple]: return _choose(state, plat, path, proposed=True) if screen == "platforms": plat, system = row.value if action == "cardsystem" else (row.value, row.value) - state.platform = plat + _set_platform(state, plat) state.card_bound = card_bound(system, plat) if state.dest_given is not None: # Read as --platform with --dest reads it, for the same folder: @@ -21599,9 +21641,18 @@ def _option(state: TuiState, action: str, step: int) -> list[tuple]: elif action == "addapp": state.add_app = step > 0 if step else not state.add_app elif action == "offline" and LOCAL.pack is None: + on = step > 0 if step else not offline_requested() + if (on and state.target and not state.manifest_given and not state.packs + and not (state.targets_for == state.platform and state.targets)): + # The run reads the board list beside the file list it is + # given, written only once Device has read it: refused before + # a check that would stop on it. + state.status = _styled("Open Device once first: without the internet the " + "board list has to be read already.", "refusal") + return [] # Set in this process by the effect, as --offline sets it: the # runs started next inherit it, and their command names it. - return [("offline", step > 0 if step else not offline_requested())] + return [("offline", on)] elif action == "device": _push(state, "device") state.focus["device"] = _device_focus(state) @@ -21664,23 +21715,28 @@ def _open_pack_browser(state: TuiState) -> list[tuple]: return [] -def open_tui_pack(path: Path, plat: str) -> str: - """The pack the screen chose, opened in this process in place of the - network as --from-pack opens it: "" once it is, else why not.""" +def open_tui_pack(path: Path, plat: str) -> tuple[PackSource | None, str]: + """The pack the screen chose, opened as --from-pack opens it, and why + it is refused, "" when it is not. The run reads it only once the screen + takes it (use_tui_pack): a Back before then leaves the run as it was.""" try: pack = PackSource([path]) except ValueError as exc: - return str(exc) + return None, str(exc) spelled = pack.platforms() if spelled and plat not in spelled: pack.close() - return (f"{pack.name} is a pack for " - f"{' and '.join(display_name(name) for name in spelled)}, " - f"not {display_name(plat)}.") - if LOCAL.pack is not None: + return None, (f"{pack.name} is a pack for " + f"{' and '.join(display_name(name) for name in spelled)}, " + f"not {display_name(plat)}.") + return pack, "" + + +def use_tui_pack(pack: PackSource) -> None: + """The pack the screen took, read by this run in place of the network.""" + if LOCAL.pack is not None and LOCAL.pack is not pack: LOCAL.pack.close() LOCAL.pack = pack - return "" def set_offline(on: bool) -> None: @@ -22066,8 +22122,12 @@ def tui_update(state: TuiState, key: TuiKey) -> list[tuple]: # A heading names the rows under it and is never in focus: the # move goes on to the next row its way, else stays where it was. step = -1 if name in ("up", "pgup", "end") or char in ("k", "G") else 1 + moved = name in ("up", "down") or char in ("k", "j") + if not moved and focus in (0, last): + # A page or Home clamped to an end: the first row from that end. + step = 1 if focus == 0 else -1 order = list(range(focus, -1, -1) if step < 0 else range(focus, len(rows))) - if wrap: + if wrap and moved: order += range(last, focus, -1) if step < 0 else range(0, focus) focus = next((index for index in order if rows[index].action != "heading"), max(0, min(state.focus.get(screen, 0), last))) @@ -22490,20 +22550,25 @@ def tui_receive(state: TuiState, message: tuple) -> list[tuple]: if plat != state.platform or state.screen != "checking": return [] state.manifest, state.manifest_for = manifest, plat + _known_choice(state, plat, manifest) if state.games_probed is None: state.games_probed = (plat, state.dest) return _start_run(state, "check") if kind == "targets": _kind, plat, targets, problem = message if plat == state.platform: - state.targets, state.targets_for, state.targets_problem = targets, plat, problem + # Held only once read: a list that could not be read is fetched + # again the next time Device opens. + state.targets, state.targets_problem = targets, problem + state.targets_for = plat if targets is not None else "" if state.screen == "device": state.focus["device"] = _device_focus(state) return [] if kind == "pack_opened": - _kind, path, problem = message + _kind, path, pack, problem = message if state.screen != "browse" or state.browse_for != "pack": - return [] + # Left before it opened: never read, closed. + return [("close_pack", pack)] if pack is not None else [] if problem: state.status = _styled(problem, "refusal") return [] @@ -22515,7 +22580,7 @@ def tui_receive(state: TuiState, message: tuple) -> list[tuple]: if not state.manifest_given: state.manifest_path = None state.targets_for = "" - return _start_check(state) + return [("use_pack", pack), *_start_check(state)] if kind == "app": # Put from the result screen, in this process. state.app, state.app_running = message[1], False @@ -23975,9 +24040,9 @@ def _view_options(state: TuiState, width: int, height: int) -> tuple: # A folder is cut in its middle, its last levels being what tells it apart. room = width - 4 - (0 if width < 60 else 2 + max(cell_width(r.right) for r in rows)) rows = [_middle_label(row, room) if row.style == "path" else row for row in rows] - if state.status: - after.append(state.status) - return ("Change", _compose(state, width, height, [], rows, after), + # In the lines the rows leave: below them it went once they filled + # the screen. + return ("Change", _compose(state, width, height, [], rows, after, _status_footer(state)), ("A/Enter Change", "B/Esc Back", "Left/Right Set", "? Help")) @@ -23986,11 +24051,13 @@ def _view_device(state: TuiState, width: int, height: int) -> tuple: pinned = ["Choose the board, the chip of the device the games run on. " "Every board fits any device."] rows = tui_rows(state) - if state.targets_for != state.platform: + if state.targets_problem: + after = [state.targets_problem] + elif state.targets_for != state.platform: after = ["Reading the board list..."] elif not rows: - after = [state.targets_problem or f"{display_name(state.platform)} publishes " - "no board list: every board's files are installed."] + after = [f"{display_name(state.platform)} publishes no board list: every " + "board's files are installed."] else: after = [] return ("Device", _compose(state, width, height, [], rows, after, @@ -25421,7 +25488,9 @@ def _tui_fetch_targets(plat: str, inbox: queue.Queue, log: io.StringIO) -> None: the network's. None and why when it cannot be read.""" mark = len(log.getvalue()) try: - targets = fetch_targets(plat) + # One attempt and the list's own timeout, as the file list is + # fetched here: Device says Reading while it waits. + targets = fetch_targets(plat, attempts=1, timeout=TUI_FETCH_TIMEOUT) except SystemExit: targets = None said = [line.strip() for line in log.getvalue()[mark:].splitlines() if line.strip()] @@ -25900,7 +25969,12 @@ def run_tui(args: argparse.Namespace) -> int: threading.Thread(target=_tui_fetch_targets, args=(effect[1], inbox, buffer), daemon=True).start() elif kind == "pack": - inbox.put(("pack_opened", effect[1], open_tui_pack(effect[1], state.platform))) + inbox.put(("pack_opened", effect[1], + *open_tui_pack(effect[1], state.platform))) + elif kind == "use_pack": + use_tui_pack(effect[1]) + elif kind == "close_pack": + effect[1].close() elif kind == "offline": set_offline(effect[1]) diff --git a/install.sh b/install.sh index 77f994921..747a6492d 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins # curl or wget stop on a cut transfer before the hash is read: the site's # build rewrites this line there (scripts/sitebootstrap.py). MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." -DEFAULT_INSTALL_SHA256="18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b" +DEFAULT_INSTALL_SHA256="67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. MAX_INSTALLER_BYTES=8388608 diff --git a/install/launchers/index.json b/install/launchers/index.json index 87c398d14..4dc4c488d 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1217065, - "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", + "size": 1220974, + "sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1270690, - "sha256": "dbda647988b00ef42e70c82165cbbe8ea11fcfeec59ab8d753dfbfe7914e07cb" + "size": 1274599, + "sha256": "8ea3d79c9bbcec747555c3970edef0692db5a00ada0ff45892aaf68dcfc38eb6" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1217065, - "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", + "size": 1220974, + "sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1217065, - "sha256": "18fcbf04ff9dcd56e0bb3c61267b8051aa2c2c5ad22bfe73e7f767d4f7fde84b", + "size": 1220974, + "sha256": "67036bbe4db401de213d9a8ab44161b8b64bfde91834b438fff47802b5a461aa", "executable": false }, { diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index f88185bb9..117e00c6d 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -15511,13 +15511,17 @@ class DeviceAndEmulatorScreens(unittest.TestCase): [("--every-board", None)]) self.assertIn("Device: every board", self.text(state)) - def test_with_no_board_read_every_board_is_the_choice_and_no_flag(self): + def test_with_no_board_read_every_board_is_the_choice(self): state = device_state(board="") self.assertEqual(install.tui_rows(state)[-1].label, "(x) Every board") self.assertEqual(state.focus["device"], 3) - press(state, "enter") self.assertEqual([flag for flag in install.tui_flags(state) if flag[0] in ("--target", "--every-board")], []) + # Chosen, it is said: a card put in the reader later names none. + press(state, "enter") + self.assertEqual([flag for flag in install.tui_flags(state) + if flag[0] in ("--target", "--every-board")], + [("--every-board", None)]) def test_a_board_list_that_cannot_be_read_is_said(self): state = checked_state() @@ -15596,11 +15600,12 @@ class DeviceAndEmulatorScreens(unittest.TestCase): self.assertIn("A/Enter Use this pack", self.text(state)) self.assertEqual(press(state, "enter"), [("pack", pack)]) # Refused: said, the browser stays. - install.tui_receive(state, ("pack_opened", pack, "Not a pack.")) + install.tui_receive(state, ("pack_opened", pack, None, "Not a pack.")) self.assertEqual(state.screen, "browse") self.assertIn("Not a pack.", self.text(state)) - effects = install.tui_receive(state, ("pack_opened", pack, "")) - self.assertEqual(effects, [("fetch", "retroarch")]) + opened = mock.Mock() + effects = install.tui_receive(state, ("pack_opened", pack, opened, "")) + self.assertEqual(effects, [("use_pack", opened), ("fetch", "retroarch")]) self.assertEqual(state.packs, [pack]) self.assertIn(("--from-pack", str(pack)), install.tui_flags(state)) @@ -15612,9 +15617,9 @@ class DeviceAndEmulatorScreens(unittest.TestCase): archive.writestr("manifest.json", json.dumps({"platform": "batocera", "files": []})) previous = install.LOCAL.pack - self.addCleanup(setattr, install.LOCAL, "pack", previous) - problem = install.open_tui_pack(pack, "retroarch") + opened, problem = install.open_tui_pack(pack, "retroarch") self.assertIs(install.LOCAL.pack, previous) + self.assertIsNone(opened) self.assertTrue(problem) @@ -15828,5 +15833,155 @@ class EmulatorChoiceMatchesTheEmulatorPack(unittest.TestCase): entry = next(entry for entry in files if entry["dest"] == dest) self.assertIsNone(entry.get("cores"), dest) + +class DeviceAndEmulatorReview(unittest.TestCase): + """The review of 2026-11-11: each finding, held.""" + + def setUp(self): + patcher = mock.patch.object(install, "_mounted_cards", return_value=[]) + patcher.start() + self.addCleanup(patcher.stop) + + @staticmethod + def focus(state: install.TuiState, action: str, value: object = None) -> None: + rows = install.tui_rows(state) + state.focus[state.screen] = next( + index for index, row in enumerate(rows) + if row.action == action and (value is None or row.value == value)) + + def text(self, state: install.TuiState) -> str: + return "\n".join(str(line) for line in install.tui_view(state, 80, 24)) + + def test_every_board_is_said_whatever_the_run_started_with(self): + for start in ({"target": "bcm2711"}, {"every_board": True}, {}): + with self.subTest(start=start): + state = device_state(board="") + state.target = start.get("target", "") + state.every_board = start.get("every_board", False) + self.focus(state, "board", "") + press(state, "enter") + self.assertEqual([flag for flag in install.tui_flags(state) + if flag[0] in ("--target", "--every-board")], + [("--every-board", None)]) + # The board the run reads itself clears both. + state = device_state() + state.every_board = True + self.focus(state, "board", "h700") + press(state, "enter") + self.assertEqual((state.target, state.every_board), ("", False)) + + def test_a_board_list_that_failed_is_fetched_again(self): + state = checked_state() + state.stack.append("options") + self.focus(state, "device") + self.assertEqual(press(state, "enter"), [("targets", "retroarch")]) + install.tui_receive(state, ("targets", "retroarch", None, + "The board list could not be read.")) + self.assertIn("The board list could not be read.", self.text(state)) + press(state, "esc") + self.assertEqual(press(state, "enter"), [("targets", "retroarch")]) + self.assertIn("Reading the board list...", self.text(state)) + + def test_a_pack_is_read_only_once_the_screen_takes_it(self): + state = checked_state() + state.stack += ["options", "browse"] + state.browse_for = "pack" + pack = mock.Mock() + effects = install.tui_receive(state, ("pack_opened", Path("p.zip"), pack, "")) + self.assertEqual(effects[0], ("use_pack", pack)) + # Left before it opened: closed, never read. + state = checked_state() + effects = install.tui_receive(state, ("pack_opened", Path("p.zip"), pack, "")) + self.assertEqual(effects, [("close_pack", pack)]) + self.assertEqual(state.packs, []) + + def test_open_tui_pack_leaves_the_run_s_pack_alone(self): + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + pack = root / "RetroArch_BIOS_Pack.zip" + with zipfile.ZipFile(pack, "w") as archive: + archive.writestr("manifest.json", json.dumps({ + "platform": "retroarch", "files": [ + {"dest": "a.bin", "path": "a.bin", "sha1": "0" * 40, "size": 1}]})) + archive.writestr("a.bin", b"a") + previous = install.LOCAL.pack + opened, problem = install.open_tui_pack(pack, "retroarch") + self.addCleanup(lambda: opened is not None and opened.close()) + self.assertEqual(problem, "", problem) + self.assertIsNotNone(opened) + self.assertIs(install.LOCAL.pack, previous) + + def test_a_zip_that_is_no_pack_is_closed(self): + root = Path(tempfile.mkdtemp(dir=REPO_ROOT / "tmp")) + self.addCleanup(shutil.rmtree, root, True) + notpack = root / "photos.zip" + with zipfile.ZipFile(notpack, "w") as archive: + archive.writestr("a.jpg", b"x") + opened = [] + real = zipfile.ZipFile + + def tracked(*args, **kwargs): + archive = real(*args, **kwargs) + opened.append(archive) + return archive + + with mock.patch.object(install.zipfile, "ZipFile", side_effect=tracked): + with self.assertRaises(ValueError): + install.PackSource([notpack]) + self.assertTrue(opened) + self.assertTrue(all(archive.fp is None for archive in opened)) + + def test_another_setup_drops_the_board_and_emulators(self): + state = checked_state() + state.target, state.every_board, state.emulators = "bcm2711", False, ["mesen"] + install._set_platform(state, "batocera") + self.assertEqual((state.target, state.every_board, state.emulators), + ("", False, [])) + state = checked_state() + state.target, state.emulators = "bcm2711", ["mesen"] + install._set_platform(state, "retroarch") + self.assertEqual((state.target, state.emulators), ("bcm2711", ["mesen"])) + + def test_names_the_new_list_lacks_are_left_out_and_said(self): + state = checked_state() + state.emulators, state.target = ["mesen", "gone_core"], "nowhere" + state.targets, state.targets_for = dict(TARGETS), "retroarch" + install._known_choice(state, "retroarch", MANIFEST) + self.assertEqual((state.emulators, state.target), (["mesen"], "")) + self.assertEqual(state.status, "Left out, not in this list: --emulator gone_core, " + "--target nowhere.") + + def test_page_up_and_home_never_rest_on_a_heading(self): + state = checked_state() + press(state, "o") + rows = install.tui_rows(state) + for start in (1, 6, len(rows) - 1): + for key in ("pgup", "home"): + with self.subTest(start=start, key=key): + state.focus["options"] = start + press(state, key) + self.assertEqual(state.focus["options"], 1) + + def test_offline_is_refused_for_a_target_with_no_board_list_held(self): + state = checked_state() + state.target = "bcm2711" + state.stack.append("options") + self.focus(state, "offline") + with mock.patch.dict(os.environ, {}, clear=False): + os.environ.pop(install.OFFLINE_VARIABLE, None) + self.assertEqual(press(state, "enter"), []) + self.assertIn("Open Device once first", self.text(state)) + state.targets, state.targets_for = dict(TARGETS), "retroarch" + self.assertEqual(press(state, "enter"), [("offline", True)]) + + def test_the_board_list_is_fetched_once_with_the_list_s_timeout(self): + inbox: queue.Queue = queue.Queue() + with mock.patch.object(install, "_fetch_json", return_value={}) as fetched, \ + mock.patch.object(install, "local_targets", return_value=None): + install._tui_fetch_targets("retroarch", inbox, io.StringIO()) + self.assertEqual(fetched.call_args.kwargs["attempts"], 1) + self.assertEqual(fetched.call_args.kwargs["timeout"], install.TUI_FETCH_TIMEOUT) + self.assertEqual(inbox.get_nowait(), ("targets", "retroarch", {}, "")) + if __name__ == "__main__": unittest.main()