From b26ce7cbdc76ab63d6f5ff2c7fb90f9367f72d56 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:07:31 +0200 Subject: [PATCH] fix: close an idle interface past its first screen RETROBIOS_IDLE_EXIT only counted while the first screen was up. Given --platform, the check runs and the plan follows without a key, so the close never came. It now counts from the end of the last run, as long as no key has been pressed. --- install.py | 18 +++++++++++------- tests/test_install_tui.py | 12 ++++++++++++ wiki/installer.md | 13 ++++++++----- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/install.py b/install.py index 4b1b1ba8..cb4001f3 100644 --- a/install.py +++ b/install.py @@ -10118,9 +10118,9 @@ TUI_PAD_ROUTES = ("gptokeyb", "gptokeyb_muxterm", "muxterm", "evmapy", "miyoo") _STEAM_MARKERS = ("SteamGamepadUI", "SteamDeck", "SteamGameId", "GAMESCOPE_WAYLAND_DISPLAY") -# Seconds the first screen waits for a first key before it closes, set by a -# launcher whose mapper may never open the pad: the screen would otherwise -# hold a handheld that has no other way out. +# Seconds the interface waits for a first key, no run going, before it +# closes, set by a launcher whose mapper may never open the pad: the screen +# would otherwise hold a handheld that has no other way out. TUI_IDLE_ENV = "RETROBIOS_IDLE_EXIT" TUI_IDLE_WORDS = "No button pressed, closing." @@ -12895,7 +12895,10 @@ def run_tui(args: argparse.Namespace) -> int: if number is not None: handlers[number] = signal.signal(number, handler) idle = tui_idle_exit() - first_screen, opened, pressed = state.screen, time.monotonic(), False + # Counted from the moment the screen last waited for a key with no + # run going: given --platform, the first screen moves on by itself + # to the plan once the check is read, and no key comes either. + quiet_since, pressed = time.monotonic(), False sys.stdout = sys.stderr = buffer try: _prune_work_folders(work.parent, time.time()) @@ -12931,9 +12934,10 @@ def run_tui(args: argparse.Namespace) -> int: perform(tui_update(state, key)) if state.exit_code is not None: break - if (idle is not None and not pressed and state.exit_code is None - and state.screen == first_screen - and time.monotonic() - opened >= idle): + if state.running: + quiet_since = time.monotonic() + elif (idle is not None and not pressed and state.exit_code is None + and time.monotonic() - quiet_since >= idle): state.idle_closed, state.exit_code = True, 0 if flags["interrupt"]: flags["interrupt"] = False diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index 5e59b0d4..121104eb 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -3176,6 +3176,18 @@ class EndToEnd(unittest.TestCase): term.send("q") self.assertEqual(term.close(), 130) + def test_no_key_on_a_screen_reached_by_itself_closes_it(self): + """Given --platform, as the offline launcher route is, the check + runs at once and the plan follows without a key: the close counted + only on the first screen, and never came.""" + repo = self.repo() + self.env[install.TUI_IDLE_ENV] = "1" + term = self.start("--platform", "retroarch", "--dest", str(repo.dest)) + term.wait("> Install") + self.assertEqual(term.close(), 0) + self.assertIn("No button pressed, closing.", term.after()) + self.assertEqual(repo.installed(), []) + def test_a_desktop_names_its_keys_and_a_pad_its_buttons(self): self.repo() for env, bar in (({"DISPLAY": ":0"}, "Enter Continue Esc Quit ? Help"), diff --git a/wiki/installer.md b/wiki/installer.md index db937aa1..a99bd781 100644 --- a/wiki/installer.md +++ b/wiki/installer.md @@ -1072,7 +1072,8 @@ page there: muxterm keeps PgUp and PgDn for its own scrollback, so a list moves with the D-pad. Each launcher also sets `RETROBIOS_IDLE_EXIT` to 300: with no button pressed -on its first screen for 5 minutes, the interface closes by itself. A pad the +for 5 minutes since it opened, and no check or install running, the interface +closes by itself. A pad the system has no mapping for sends nothing, and would otherwise leave the console stuck on the interface with no way out. @@ -1089,9 +1090,11 @@ Each launcher tells the interface which buttons reach it, in A launcher with no mapper at all (a keyboard) leaves it unset. A launcher can also set `RETROBIOS_IDLE_EXIT` to a number of seconds: when -no button reaches the first screen for that long, before any choice, the -interface closes with `No button pressed, closing.` and exit status 0. A -mapper that never opened the pad would otherwise hold the device on that +no button has reached the interface since it opened, and nothing has run for +that long, it closes with `No button pressed, closing.` and exit status 0. +The time counts from the end of the last check, so a launcher that names the +platform, whose interface moves on to the plan by itself, closes the same way. +A mapper that never opened the pad would otherwise hold the device on that screen. On a Miyoo, B, X, Y and Select are Left Ctrl, Shift, Alt and Right Ctrl: pressed alone, a modifier sends nothing to a terminal, so the interface goes back with R2 @@ -1342,7 +1345,7 @@ RetroArch folder that holds only `scph5501.bin`, in the order written: | `SSL_CERT_FILE`, `SSL_CERT_DIR` | The trusted certificates to check servers with, for a Python that finds none of its own | | `RETROBIOS_PLAIN`, `ACCESSIBLE` | Set to anything but `0`: the questions come line by line, as with `--plain`, instead of the full-screen interface | | `RETROBIOS_PAD` | The buttons a handheld launcher's mapper sends to the full screen (`gptokeyb`, `gptokeyb_muxterm`, `muxterm`, `evmapy`, `miyoo`), so its hints name them. See [On a handheld](#on-a-handheld-the-retrobios-port) | -| `RETROBIOS_IDLE_EXIT` | Seconds the full screen's first screen waits for a first button. With none pressed, it closes with exit status 0 | +| `RETROBIOS_IDLE_EXIT` | Seconds the full screen waits for a first button, counted while nothing runs. With none pressed, it closes with exit status 0 | | `HTTPS_PROXY`, `NO_PROXY` | Honoured for every download through the standard library's default proxy handling: one names the proxy, the other the hosts to reach directly | ## When something goes wrong