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
+29 -20
View File
@@ -29,8 +29,8 @@ if __name__ == "__main__":
import settings import settings
import Utils import Utils
from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename, from Utils import (env_cleared_lib_path, init_logging, is_frozen, is_linux, is_macos, is_windows, local_path,
user_path) messagebox, open_filename, user_path)
if __name__ == "__main__": if __name__ == "__main__":
init_logging('Launcher') init_logging('Launcher')
@@ -52,10 +52,7 @@ def open_host_yaml():
webbrowser.open(file) webbrowser.open(file)
return return
env = os.environ env = env_cleared_lib_path()
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.Popen([exe, file], env=env) subprocess.Popen([exe, file], env=env)
def open_patch(): def open_patch():
@@ -106,10 +103,7 @@ def open_folder(folder_path):
return return
if exe: if exe:
env = os.environ env = env_cleared_lib_path()
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.Popen([exe, folder_path], env=env) subprocess.Popen([exe, folder_path], env=env)
else: else:
logging.warning(f"No file browser available to open {folder_path}") 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 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 in_terminal:
if is_windows: if is_windows:
# intentionally using a window title with a space so it gets quoted and treated as a title # 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) subprocess.Popen(["start", "Running Archipelago", *exe], shell=True)
return return True
elif is_linux: 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: if terminal:
subprocess.Popen([terminal, '-e', shlex.join(exe)]) # Clear LD_LIB_PATH during terminal startup, but set it again when running command in case it's needed
return 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: elif is_macos:
terminal = [which('open'), '-W', '-a', 'Terminal.app'] terminal = [which("open"), "-W", "-a", "Terminal.app"]
subprocess.Popen([*terminal, *exe]) subprocess.Popen([*terminal, *exe])
return return True
subprocess.Popen(exe) subprocess.Popen(exe)
return False
def create_shortcut(button: Any, component: Component) -> None: def create_shortcut(button: Any, component: Component) -> None:
@@ -406,12 +410,17 @@ def run_gui(launch_components: list[Component], args: Any) -> None:
@staticmethod @staticmethod
def component_action(button): def component_action(button):
MDSnackbar(MDSnackbarText(text="Opening in a new window..."), y=dp(24), pos_hint={"center_x": 0.5}, open_text = "Opening in a new window..."
size_hint_x=0.5).open()
if button.component.func: 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() button.component.func()
else: 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: 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. """ """ When a patch file is dropped into the window, run the associated component. """
+16 -9
View File
@@ -22,7 +22,7 @@ from datetime import datetime, timezone
from settings import Settings, get_settings from settings import Settings, get_settings
from time import sleep 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 yaml import load, load_all, dump
from pathspec import PathSpec, GitIgnoreSpec from pathspec import PathSpec, GitIgnoreSpec
from typing_extensions import deprecated 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")) 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." assert open_command, "Didn't find program for open_file! Please report this together with system details."
env = os.environ env = env_cleared_lib_path()
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
subprocess.call([open_command, filename], env=env) subprocess.call([open_command, filename], env=env)
@@ -756,6 +753,19 @@ def is_kivy_running() -> bool:
return False 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: def _mp_open_filename(res: "multiprocessing.Queue[typing.Optional[str]]", *args: Any) -> None:
if is_kivy_running(): if is_kivy_running():
raise RuntimeError("kivy should not be running in multiprocess") 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)) res.put(save_filename(*args))
def _run_for_stdout(*args: str): def _run_for_stdout(*args: str):
env = os.environ env = env_cleared_lib_path()
if "LD_LIBRARY_PATH" in env:
env = env.copy()
del env["LD_LIBRARY_PATH"] # exe is a system binary, so reset LD_LIBRARY_PATH
return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None return subprocess.run(args, capture_output=True, text=True, env=env).stdout.split("\n", 1)[0] or None