[PATCH 1/3] scripts/runfvp: check available terminal types
Gyorgy Szing <[email protected]> Thu, 23 Apr 2026 17:37:19 +0200
| Newsgroups | org.yoctoproject.lists.meta-arm |
|---|---|
| Message-ID | <[email protected]> |
Improve usability by detecting which terminal types are available on the system. Extend the terminal abstraction to support checking whether a terminal can be executed, and add basic validation for all terminal types. Update the documentation to reflect the new behavior. Signed-off-by: Gyorgy Szing <[email protected]> --- documentation/runfvp.md | 5 ++ meta-arm/lib/fvp/terminal.py | 95 +++++++++++++++++++++++++++++++----- scripts/runfvp | 8 ++- 3 files changed, 96 insertions(+), 12 deletions(-) diff --git a/documentation/runfvp.md b/documentation/runfvp.md index d1331101..aed99971 100644 --- a/documentation/runfvp.md +++ b/documentation/runfvp.md @@ -28,6 +28,11 @@ Note that currently meta-arm's `scripts` directory isn= 't in `PATH`, so a full pa =20 `runfvp` will automatically start terminals connected to each of the ser= ial ports that the machine specifies. This can be controlled by using th= e `--terminals` option, for example `--terminals=3Dnone` will mean no ter= minals are started, and `--terminals=3Dtmux` will start the terminals in = [`tmux`][tmux] sessions. Alternatively, passing `--console` will connect= the serial port directly to the current session, without needing to open= further windows. =20 +The tool attempts to automatically select a suitable terminal type. To s= ee which terminal type is selected by default in your environment, run `r= unfvp --help`. + +`runfvp` determines availability by checking for required executables in= your PATH as well as environment variables specific to each terminal typ= e. If any of these checks fail, the corresponding terminal type is disabl= ed. +The --help output also lists all currently available terminal types. + The default terminal can also be configured by writing a [INI-style][INI= ] configuration file to `~/.config/runfvp.conf`: =20 ``` diff --git a/meta-arm/lib/fvp/terminal.py b/meta-arm/lib/fvp/terminal.py index 280fb349..c0087fa1 100644 --- a/meta-arm/lib/fvp/terminal.py +++ b/meta-arm/lib/fvp/terminal.py @@ -3,9 +3,14 @@ import collections import pathlib import os =20 +import logging +import configparser from typing import List, Optional =20 =20 +logger =3D logging.getLogger("Terminal") + + def get_config_dir() -> pathlib.Path: value =3D os.environ.get("XDG_CONFIG_HOME") if value and os.path.isabs(value): @@ -13,47 +18,115 @@ def get_config_dir() -> pathlib.Path: else: return pathlib.Path.home() / ".config" =20 + +def check_executable(*cmd) -> bool: + import subprocess + + try: + result =3D subprocess.run( + cmd, + stdout=3Dsubprocess.DEVNULL, + stderr=3Dsubprocess.DEVNULL + ) + + exitcode =3D result.returncode + + except FileNotFoundError: + exitcode =3D 127 + + return exitcode =3D=3D 0 + + +def tmux_is_ready(*, silent: bool =3D False) -> bool: + log_print =3D (lambda *_args, **_kwargs: None) if silent else logger= .error + + if not check_executable("tmux", "-V"): + log_print("--terminal tmux requires tmux to be available and run= nable, but startup failed.") + return False + + return True + + +def is_display_available(log_print, terminal_name: str) -> bool: + if "DISPLAY" not in os.environ and "WAYLAND_DISPLAY" not in os.envir= on: + log_print(f"--terminal {terminal_name} requires a graphical disp= lay" + " but nor DISPLAY nor WAYLAND_DISPLAY is set.") + return False + return True + + +def gterm_is_ready(*, silent: bool =3D False) -> bool: + log_print =3D (lambda *_args, **_kwargs: None) if silent else logger= .error + + if not is_display_available(log_print, "gnome-terminal"): + return False + + if not check_executable("gnome-terminal", "--version"): + log_print("--terminal gnome-terminal requires gnome-terminal to = be available and runnable, but startup failed.") + return False + + return True + + +def xterm_is_ready(*, silent: bool =3D False) -> bool: + log_print =3D (lambda *_args, **_kwargs: None) if silent else logger= .error + + if not is_display_available(log_print, "xterm"): + return False + + if not check_executable("xterm", "-version"): + log_print("--terminal xterm requires xterm to be available and r= unnable, but startup failed.") + return False + + return True + + class Terminals: - Terminal =3D collections.namedtuple("Terminal", ["priority", "name",= "command"]) + Terminal =3D collections.namedtuple("Terminal", ["priority", "name",= "command", "is_ready"]) =20 def __init__(self): self.terminals =3D [] =20 - def add_terminal(self, priority, name, command): - self.terminals.append(Terminals.Terminal(priority, name, command= )) + def always_ready(self, *, silent: bool =3D False) -> bool: + return True + + def add_terminal(self, priority, name, command, is_ready=3DNone): + if is_ready is None: + is_ready =3D self.always_ready + self.terminals.append(Terminals.Terminal(priority, name, command= , is_ready)) # Keep this list sorted by priority self.terminals.sort(reverse=3DTrue, key=3Dlambda t: t.priority) self.name_map =3D {t.name: t for t in self.terminals} =20 def configured_terminal(self) -> Optional[str]: - import configparser - config =3D configparser.ConfigParser() config.read(get_config_dir() / "runfvp.conf") return config.get("RunFVP", "Terminal", fallback=3DNone) =20 def preferred_terminal(self) -> str: - import shlex - preferred =3D self.configured_terminal() if preferred: return preferred =20 for t in self.terminals: - if t.command and shutil.which(shlex.split(t.command)[0]): + if t.command and t.is_ready(silent=3DTrue): return t.name return self.terminals[-1].name =20 def all_terminals(self) -> List[str]: return self.name_map.keys() =20 + def available_terminals(self) -> List[str]: + return [t for t in self.name_map if self.name_map[t].is_ready(si= lent=3DTrue)] + def __getitem__(self, name: str): return self.name_map[name] =20 + terminals =3D Terminals() # TODO: option to switch between telnet and netcat connect_command =3D "telnet localhost %port" -terminals.add_terminal(2, "tmux", f"tmux new-window -n \"{{name}}\" \"{c= onnect_command}\"") -terminals.add_terminal(2, "gnome-terminal", f"gnome-terminal --window --= title \"{{name}} - %title\" --command \"{connect_command}\"") -terminals.add_terminal(1, "xterm", f"xterm -title \"{{name}} - %title\" = -e {connect_command}") +terminals.add_terminal(2, "tmux", f'tmux new-window -n "{{name}}" "{conn= ect_command}"', tmux_is_ready) +terminals.add_terminal(2, "gnome-terminal", f'gnome-terminal --window --= title "{{name}} - %title" --command "{connect_command}"', gterm_is_ready) +terminals.add_terminal(1, "xterm", f'xterm -title "{{name}} - %title" -e= {connect_command}', xterm_is_ready) terminals.add_terminal(0, "none", None) diff --git a/scripts/runfvp b/scripts/runfvp index ceae18ae..8e6fe655 100755 --- a/scripts/runfvp +++ b/scripts/runfvp @@ -23,7 +23,8 @@ def parse_args(arguments): parser =3D argparse.ArgumentParser(description=3D"Run images in a FV= P") parser.add_argument("config", nargs=3D"?", help=3D"Machine name or p= ath to .fvpconf file") group =3D parser.add_mutually_exclusive_group() - group.add_argument("-t", "--terminals", choices=3Dterminals.all_term= inals(), default=3Dterminals.preferred_terminal(), help=3D"Automatically = start terminals (default: %(default)s)") + available_terminals=3D",".join(terminals.available_terminals()) + group.add_argument("-t", "--terminals", choices=3Dterminals.all_term= inals(), default=3Dterminals.preferred_terminal(), help=3Df"Automatically= start terminals (default: %(default)s). Available terminals are ({availa= ble_terminals})") group.add_argument("-c", "--console", action=3D"store_true", help=3D= "Attach the first uart to stdin/stdout") parser.add_argument("--verbose", action=3D"store_true", help=3D"Outp= ut verbose logging") parser.usage =3D f"{parser.format_usage().strip()} -- [ arguments pa= ssed to FVP ]" @@ -52,6 +53,11 @@ def parse_args(arguments): def start_fvp(args, fvpconf, extra_args): fvp =3D runner.FVPRunner(logger) try: + + if args.terminals: + if not terminal.terminals[args.terminals].is_ready(): + return 1 + fvp.start(fvpconf, extra_args, args.terminals) =20 if args.console: --=20 2.43.0