Skip to content

Commit

Permalink
fix: restore environment before launching external programs (#707)
Browse files Browse the repository at this point in the history
* rename promptless_Popen

* linux: restore env before calling popen

* windows: set dll directory to default before calling popen

* ruff formatting suggestions

* edited silent_popen docstring
  • Loading branch information
mashed5894 authored Jan 24, 2025
1 parent 0ead238 commit 2a41c15
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 16 deletions.
7 changes: 4 additions & 3 deletions tagstudio/src/qt/helpers/file_opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import structlog
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLabel
from src.qt.helpers.silent_popen import silent_Popen

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -38,7 +39,7 @@ def open_file(path: str | Path, file_manager: bool = False):

# For some reason, if the args are passed in a list, this will error when the
# path has spaces, even while surrounded in double quotes.
subprocess.Popen(
silent_Popen(
command_name + command_arg,
shell=True,
close_fds=True,
Expand All @@ -47,7 +48,7 @@ def open_file(path: str | Path, file_manager: bool = False):
)
else:
command = f'"{normpath}"'
subprocess.Popen(
silent_Popen(
command,
shell=True,
close_fds=True,
Expand Down Expand Up @@ -79,7 +80,7 @@ def open_file(path: str | Path, file_manager: bool = False):
command_args = [str(path)]
command = shutil.which(command_name)
if command is not None:
subprocess.Popen([command] + command_args, close_fds=True)
silent_Popen([command] + command_args, close_fds=True)
else:
logger.info("Could not find command on system PATH", command=command_name)
except Exception:
Expand Down
24 changes: 19 additions & 5 deletions tagstudio/src/qt/helpers/silent_popen.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import os
import subprocess
import sys

"""Implementation of subprocess.Popen that does not spawn console windows or log output."""
"""Implementation of subprocess.Popen that does not spawn console windows or log output
and sanitizes pyinstall environment variables."""


def promptless_Popen( # noqa: N802
def silent_Popen( # noqa: N802
args,
bufsize=-1,
executable=None,
Expand All @@ -21,6 +23,7 @@ def promptless_Popen( # noqa: N802
env=None,
universal_newlines=None,
startupinfo=None,
creationflags=0,
restore_signals=True,
start_new_session=False,
pass_fds=(),
Expand All @@ -36,9 +39,20 @@ def promptless_Popen( # noqa: N802
process_group=None,
):
"""Call subprocess.Popen without creating a console window."""
creation_flags = 0
if sys.platform == "win32":
creation_flags = subprocess.CREATE_NO_WINDOW
creationflags |= subprocess.CREATE_NO_WINDOW
import ctypes

ctypes.windll.kernel32.SetDllDirectoryW(None)
elif (
sys.platform == "linux"
or sys.platform.startswith("freebsd")
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 ""

return subprocess.Popen(
args=args,
Expand All @@ -54,7 +68,7 @@ def promptless_Popen( # noqa: N802
env=env,
universal_newlines=universal_newlines,
startupinfo=startupinfo,
creationflags=creation_flags,
creationflags=creationflags,
restore_signals=restore_signals,
start_new_session=start_new_session,
pass_fds=pass_fds,
Expand Down
4 changes: 2 additions & 2 deletions tagstudio/src/qt/helpers/vendored/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import ffmpeg
import structlog
from src.qt.helpers.silent_popen import promptless_Popen
from src.qt.helpers.silent_popen import silent_Popen

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -44,7 +44,7 @@ def _probe(filename, cmd=FFPROBE_CMD, timeout=None, **kwargs):
args += [filename]

# PATCHED
p = promptless_Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = silent_Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
communicate_kwargs = {}
if timeout is not None:
communicate_kwargs["timeout"] = timeout
Expand Down
8 changes: 4 additions & 4 deletions tagstudio/src/qt/helpers/vendored/pydub/audio_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
get_encoder_name,
ratio_to_db,
)
from src.qt.helpers.silent_popen import promptless_Popen
from src.qt.helpers.silent_popen import silent_Popen
from src.qt.helpers.vendored.pydub.utils import _mediainfo_json

basestring = str
Expand Down Expand Up @@ -606,7 +606,7 @@ def is_format(f):

with open(os.devnull, "rb") as devnull:
# PATCHED
p = promptless_Popen(
p = silent_Popen(
conversion_command, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
p_out, p_err = p.communicate()
Expand Down Expand Up @@ -785,7 +785,7 @@ def is_format(f):
log_conversion(conversion_command)

# PATCHED
p = promptless_Popen(
p = silent_Popen(
conversion_command,
stdin=stdin_parameter,
stdout=subprocess.PIPE,
Expand Down Expand Up @@ -1012,7 +1012,7 @@ def export(
# read stdin / write stdout
with open(os.devnull, "rb") as devnull:
# PATCHED
p = promptless_Popen(
p = silent_Popen(
conversion_command, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
p_out, p_err = p.communicate()
Expand Down
4 changes: 2 additions & 2 deletions tagstudio/src/qt/helpers/vendored/pydub/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
get_extra_info,
get_prober_name,
)
from src.qt.helpers.silent_popen import promptless_Popen
from src.qt.helpers.silent_popen import silent_Popen


def _mediainfo_json(filepath, read_ahead_limit=-1):
Expand Down Expand Up @@ -38,7 +38,7 @@ def _mediainfo_json(filepath, read_ahead_limit=-1):

command = [prober, "-of", "json"] + command_args
# PATCHED
res = promptless_Popen(
res = silent_Popen(
command, stdin=stdin_parameter, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
output, stderr = res.communicate(input=stdin_data)
Expand Down

0 comments on commit 2a41c15

Please sign in to comment.