Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/tagstudio/core/utils/silent_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@
"""Implementation of subprocess.Popen that does not spawn console windows or log output
and sanitizes pyinstaller environment variables."""

PYINSTALLER_QT_ENV_VARS = (
"QT_PLUGIN_PATH",
"QT_QPA_PLATFORM_PLUGIN_PATH",
"QML2_IMPORT_PATH",
)


def sanitized_subprocess_env(env: dict[str, str] | None = None) -> dict[str, str]:
"""Return an environment safe to pass to external system applications.

PyInstaller/PySide builds can add bundled library and Qt plugin paths to the
process environment. If inherited by ``xdg-open`` and the default application,
those paths can cause silent launch failures or broken Qt/LibreOffice starts.
"""
clean_env = dict(os.environ if env is None else env)
original_ld_library_path = clean_env.get("LD_LIBRARY_PATH_ORIG")

if original_ld_library_path is not None:
clean_env["LD_LIBRARY_PATH"] = original_ld_library_path
else:
clean_env.pop("LD_LIBRARY_PATH", None)

for env_var in PYINSTALLER_QT_ENV_VARS:
clean_env.pop(env_var, None)

return clean_env


def silent_popen(
args,
Expand Down Expand Up @@ -55,9 +82,7 @@ def silent_popen(
or sys.platform.startswith("openbsd")
):
# pass clean environment to the subprocess
current_env = os.environ
original_env = current_env.get("LD_LIBRARY_PATH_ORIG")
current_env["LD_LIBRARY_PATH"] = original_env if original_env else ""
current_env = sanitized_subprocess_env(current_env)

return subprocess.Popen(
args=args,
Expand Down Expand Up @@ -131,9 +156,7 @@ def silent_run(
or sys.platform.startswith("openbsd")
):
# pass clean environment to the subprocess
env = os.environ
original_env = env.get("LD_LIBRARY_PATH_ORIG")
env["LD_LIBRARY_PATH"] = original_env if original_env else ""
env = sanitized_subprocess_env(env)

return subprocess.run(
args=args,
Expand Down
35 changes: 35 additions & 0 deletions tests/test_silent_subprocess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only

from tagstudio.core.utils.silent_subprocess import sanitized_subprocess_env


def test_sanitized_subprocess_env_restores_original_library_path():
env = {
"LD_LIBRARY_PATH": "/tmp/_MEI12345",
"LD_LIBRARY_PATH_ORIG": "/usr/lib",
"PATH": "/usr/bin",
}

clean_env = sanitized_subprocess_env(env)

assert clean_env["LD_LIBRARY_PATH"] == "/usr/lib"
assert env["LD_LIBRARY_PATH"] == "/tmp/_MEI12345"


def test_sanitized_subprocess_env_removes_bundled_qt_paths():
env = {
"LD_LIBRARY_PATH": "/tmp/_MEI12345",
"QT_PLUGIN_PATH": "/tmp/_MEI12345/PySide6/Qt/plugins",
"QT_QPA_PLATFORM_PLUGIN_PATH": "/tmp/_MEI12345/PySide6/Qt/plugins/platforms",
"QML2_IMPORT_PATH": "/tmp/_MEI12345/PySide6/Qt/qml",
"PATH": "/usr/bin",
}

clean_env = sanitized_subprocess_env(env)

assert "LD_LIBRARY_PATH" not in clean_env
assert "QT_PLUGIN_PATH" not in clean_env
assert "QT_QPA_PLATFORM_PLUGIN_PATH" not in clean_env
assert "QML2_IMPORT_PATH" not in clean_env
assert clean_env["PATH"] == "/usr/bin"