diff --git a/ui/frame_connect.py b/ui/frame_connect.py index 0728a94..aa912ab 100644 --- a/ui/frame_connect.py +++ b/ui/frame_connect.py @@ -21,6 +21,10 @@ FRAME_ALIAS = os.environ.get("FRAME_ALIAS", "frame") SSH_DIR = Path.home() / ".ssh" KEY = SSH_DIR / "id_ed25519_frame" CONFIG = SSH_DIR / "config" +# Both go into ~/.ssh/config, so nothing that could add a line or a directive. +for _name, _value in (("FRAME_ALIAS", FRAME_ALIAS), ("FRAME_USER", FRAME_USER)): + if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", _value): + sys.exit(f"{_name} must be a plain name, not {_value!r}") BEGIN = f"# >>> steam-frame ({FRAME_ALIAS}) >>>" END = f"# <<< steam-frame ({FRAME_ALIAS}) <<<" @@ -125,7 +129,7 @@ def main(argv): say("==> Looking for the Steam Frame") found = pick_host(argv[0] if argv else None) while not found: - say("Could not reach the Frame on port 22.") + say("Could not reach the Frame over SSH.") say("Check: Developer Mode on and a user password set; same network; no client isolation.") try: typed = input("Type the Frame's IP address (Quick Settings shows it), or press Enter to quit: ").strip() diff --git a/ui/frame_host.py b/ui/frame_host.py index 6b2dde7..994a056 100644 --- a/ui/frame_host.py +++ b/ui/frame_host.py @@ -146,19 +146,29 @@ def open_terminal(argv, title="Frame Control"): # `start` gives the command its own console window; cmd /k keeps it open. # One hand-built command line: quoting it twice through list2cmdline would # produce backslash-escaped quotes, which cmd doesn't understand. - # Every argument is quoted, so cmd treats & | < > ^ in them literally. - inner = " ".join('"%s"' % a.replace('"', '\\"') for a in argv) + # Every argument is quoted, so cmd treats & | < > ^ in them literally. cmd has + # no escape for a quote inside quotes (and expands %VAR% regardless), so refuse those. + if any(c in a for a in argv for c in '"%\r\n'): + raise HostError("Can't pass quotes or % to a Windows terminal") + inner = " ".join(f'"{a}"' for a in argv) subprocess.Popen(f'cmd.exe /c start "{title}" cmd.exe /k "{inner}"', **DETACHED) return "a terminal window" script = f'{shlex.join(argv)}; echo; read -r -p "Press Enter to close. " _' + # flags=None: the terminal takes the whole command as one string after -e. for name, flags in (("x-terminal-emulator", ["-e"]), ("gnome-terminal", ["--"]), ("ptyxis", ["--"]), ("kgx", ["--"]), ("konsole", ["-e"]), ("xfce4-terminal", ["-x"]), - ("tilix", ["-e"]), ("lxterminal", ["-e"]), ("kitty", []), ("alacritty", ["-e"]), + ("tilix", None), ("lxterminal", None), ("kitty", []), ("alacritty", ["-e"]), ("wezterm", ["start", "--"]), ("foot", []), ("xterm", ["-e"])): exe = which(name) - if exe: + if not exe: + continue + if flags is None: + _spawn([exe, "-e", "bash -c " + shlex.quote(script)]) + elif name == "x-terminal-emulator" and "lxterminal" in os.path.realpath(exe): + _spawn([exe, "-e", "bash -c " + shlex.quote(script)]) # Debian alternative -> lxterminal + else: _spawn([exe, *flags, "bash", "-c", script]) - return name + return name raise HostError("No terminal program found (tried gnome-terminal, konsole, xterm and others)")