Launcher: Add konsole to terminal list and rework launch dialog (#5684)

* Make component launching indicate if no terminal window, add konsole

* Attempt to spell better and remove whitespace

* Update terminal priority

* Make helper for clearing LD_LIBRARY_PATH

* Add handling to linux launch

* Hopefully fix setter

* Apply suggestions from code review

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>

---------

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
Duck
2026-03-29 12:07:55 -06:00
committed by GitHub
parent ffe4c6dd15
commit 1705620c4f
2 changed files with 45 additions and 29 deletions

View File

@@ -29,8 +29,8 @@ if __name__ == "__main__":
import settings
import Utils
from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename,
user_path)
from Utils import (env_cleared_lib_path, init_logging, is_frozen, is_linux, is_macos, is_windows, local_path,
messagebox, open_filename, user_path)
if __name__ == "__main__":
init_logging('Launcher')
@@ -52,10 +52,7 @@ def open_host_yaml():
webbrowser.open(file)
return
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
env = env_cleared_lib_path()
subprocess.Popen([exe, file], env=env)
def open_patch():
@@ -106,10 +103,7 @@ def open_folder(folder_path):
return
if exe:
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
env = env_cleared_lib_path()
subprocess.Popen([exe, folder_path], env=env)
else:
logging.warning(f"No file browser available to open {folder_path}")
@@ -202,22 +196,32 @@ def get_exe(component: str | Component) -> Sequence[str] | None:
return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None
def launch(exe, in_terminal=False):
def launch(exe: Sequence[str], in_terminal: bool = False) -> bool:
"""Runs the given command/args in `exe` in a new process.
If `in_terminal` is True, it will attempt to run in a terminal window,
and the return value will indicate whether one was found."""
if in_terminal:
if is_windows:
# intentionally using a window title with a space so it gets quoted and treated as a title
subprocess.Popen(["start", "Running Archipelago", *exe], shell=True)
return
return True
elif is_linux:
terminal = which('x-terminal-emulator') or which('gnome-terminal') or which('xterm')
terminal = which("x-terminal-emulator") or which("konsole") or which("gnome-terminal") or which("xterm")
if terminal:
subprocess.Popen([terminal, '-e', shlex.join(exe)])
return
# Clear LD_LIB_PATH during terminal startup, but set it again when running command in case it's needed
ld_lib_path = os.environ.get("LD_LIBRARY_PATH")
lib_path_setter = f"env LD_LIBRARY_PATH={shlex.quote(ld_lib_path)} " if ld_lib_path else ""
env = env_cleared_lib_path()
subprocess.Popen([terminal, "-e", lib_path_setter + shlex.join(exe)], env=env)
return True
elif is_macos:
terminal = [which('open'), '-W', '-a', 'Terminal.app']
terminal = [which("open"), "-W", "-a", "Terminal.app"]
subprocess.Popen([*terminal, *exe])
return
return True
subprocess.Popen(exe)
return False
def create_shortcut(button: Any, component: Component) -> None:
@@ -406,12 +410,17 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
@staticmethod
def component_action(button):
MDSnackbar(MDSnackbarText(text="Opening in a new window..."), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()
open_text = "Opening in a new window..."
if button.component.func:
# Note: if we want to draw the Snackbar before running func, func needs to be wrapped in schedule_once
button.component.func()
else:
launch(get_exe(button.component), button.component.cli)
# if launch returns False, it started the process in background (not in a new terminal)
if not launch(get_exe(button.component), button.component.cli) and button.component.cli:
open_text = "Running in the background..."
MDSnackbar(MDSnackbarText(text=open_text), y=dp(24), pos_hint={"center_x": 0.5},
size_hint_x=0.5).open()
def _on_drop_file(self, window: Window, filename: bytes, x: int, y: int) -> None:
""" When a patch file is dropped into the window, run the associated component. """

View File

@@ -22,7 +22,7 @@ from datetime import datetime, timezone
from settings import Settings, get_settings
from time import sleep
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
from typing import BinaryIO, Coroutine, Mapping, Optional, Set, Dict, Any, Union, TypeGuard
from yaml import load, load_all, dump
from pathspec import PathSpec, GitIgnoreSpec
from typing_extensions import deprecated
@@ -236,10 +236,7 @@ def open_file(filename: typing.Union[str, "pathlib.Path"]) -> None:
open_command = which("open") if is_macos else (which("xdg-open") or which("gnome-open") or which("kde-open"))
assert open_command, "Didn't find program for open_file! Please report this together with system details."
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
env = env_cleared_lib_path()
subprocess.call([open_command, filename], env=env)
@@ -756,6 +753,19 @@ def is_kivy_running() -> bool:
return False
def env_cleared_lib_path() -> Mapping[str, str]:
"""
Creates a copy of the current environment vars with the LD_LIBRARY_PATH removed if set, as this can interfere when
launching something in a subprocess.
"""
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"]
return env
def _mp_open_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None:
if is_kivy_running():
raise RuntimeError("kivy should not be running in multiprocess")
@@ -768,10 +778,7 @@ def _mp_save_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args:
res.put(save_filename(*args))
def _run_for_stdout(*args: str):
env = os.environ
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
env = env_cleared_lib_path()
return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None