fix(flatpak): console on host + dialog persistence

- term console via flatpak-spawn --host so host venvs resolve (bins.host_console_command)
- QSettings sync() before subprocess kill in choices/tested-refs dialogs
- console regression test: fails on the in-sandbox 0.2.1 console

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 23:42:48 +02:00
parent de32a524da
commit 59e63e1338
5 changed files with 51 additions and 3 deletions

View File

@@ -81,9 +81,13 @@ class TermConsole(Console):
bufsize=0) bufsize=0)
else: else:
self.term = pexpect.spawn( shell_cmd, # In Flatpak this returns a `flatpak-spawn --host` wrapper so the
echo=False, # console behaves like a host shell (matching py_func / lua_func /
cwd=self.ppath) # run); elsewhere it's the chosen command unchanged.
from interpreter.utils import bins
argv = bins.host_console_command(shell_cmd, self.ppath)
self.term = pexpect.spawn(argv[0], args=argv[1:],
echo=False, cwd=self.ppath)
self.q = BytesStore() self.q = BytesStore()
self.t = threading.Thread(target=self.enqueue_output) self.t = threading.Thread(target=self.enqueue_output)

View File

@@ -221,6 +221,11 @@ def main(args, conn=None):
if conn: if conn:
settings.setValue(SettingsLastChoices, result) settings.setValue(SettingsLastChoices, result)
# Flush before sending: the parent terminates this subprocess as soon
# as it reads the result, so the QSettings destructor never runs and
# the write would race the kill (lost under Flatpak — see the
# tested-references dialog for the full rationale).
settings.sync()
conn.send([result, success]) conn.send([result, success])
conn.close() conn.close()
else: else:

View File

@@ -76,6 +76,12 @@ def main(args, conn=None):
if conn: if conn:
settings.setValue(SettingsLastReference, result) settings.setValue(SettingsLastReference, result)
# Flush to disk *before* handing the result back: as soon as the parent
# receives it on the pipe it terminates this subprocess (SIGTERM, no
# handler), so the QSettings destructor never runs. Without sync() the
# write races the kill and is lost — reliably so under Flatpak, where
# the .conf is atomically renamed on the slower ~/.var/app overlay.
settings.sync()
conn.send([result, success]) conn.send([result, success])
conn.close() conn.close()
else: else:

View File

@@ -19,6 +19,7 @@ Public API
import atexit import atexit
import os import os
import shlex
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
@@ -177,6 +178,27 @@ def flatpak_host_spawn(interp_bin, cmd_args, host_cwd, extra_env=None):
return spawn return spawn
def host_console_command(shell_cmd, cwd):
"""Build the argv to start *shell_cmd* as an ordinary interactive console.
*shell_cmd* is the command the caller chose (a string — shell-split — or
an argv list); the choice is preserved verbatim.
Outside Flatpak the command is returned unchanged. Inside Flatpak a bare
spawn would run in the sandbox under the runtime python3, so a host venv
(``/path/venv/bin/python3 -m mod``) can't see its pip deps. We simply run
it on the host with ``flatpak-spawn --host`` so it behaves like any other
terminal: flatpak-spawn passes the current environment through unchanged
and the shell (sourced venv, profile, …) sets things up as the user wants.
No env forwarding or scrubbing — the launcher's leaked PYTHONPATH points at
/app paths absent on the host, so it's inert there.
"""
argv = shlex.split(shell_cmd) if isinstance(shell_cmd, str) else list(shell_cmd)
if not _in_flatpak():
return argv
return ["flatpak-spawn", "--host", f"--directory={cwd}", *argv]
def _which_host_flatpak(name): def _which_host_flatpak(name):
"""Resolve a binary name (or absolute path) on the host via flatpak-spawn. """Resolve a binary name (or absolute path) on the host via flatpak-spawn.

View File

@@ -94,6 +94,17 @@
{% endif %} {% endif %}
- read_until: {expected: endOfCmd, timeout: 1, process_result: "'Hello' in r'''$(result)''' and 'PASS' in r'''$(result)''' "} - read_until: {expected: endOfCmd, timeout: 1, process_result: "'Hello' in r'''$(result)''' and 'PASS' in r'''$(result)''' "}
{% if os == "Linux" %}
- console:
name: Console runs on host (not the Flatpak sandbox)
doc: Regression guard for the 0.2.1 Flatpak bug (term console spawned inside the sandbox instead of on the host). /.flatpak-info exists only inside the sandbox, so the host-only marker is emitted (and matched by read_until) ONLY when the shell really runs on the host. On a broken Flatpak the marker never appears, read_until times out and the item FAILS. The marker is built at runtime ($M) so it is never present in the command line itself. Passes on every other channel.
console_name: term
key: $(test)_PASS
steps:
- writeln: 'test -e /.flatpak-info && M=SANDBOX || M=HOST; echo "console_host_check_$M"'
- read_until: {expected: console_host_check_HOST, timeout: 5}
{% endif %}
- console: - console:
name: Console closure name: Console closure
execute_on_stop: true execute_on_stop: true