From 670f001d10c23802786d603c7424e537fb149a2b Mon Sep 17 00:00:00 2001 From: R1kaB3rN <100738684+R1kaB3rN@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:36:51 -0700 Subject: [PATCH] Make zenity optional for setup or download - This commit makes the zenity popup window optional when downloading the runtime platform or a Proton build optional to avoid conflicting with a launcher's UI/UX, and hides the feature via the UMU_ZENITY environment variable. - The launcher is designed to be used as a backend and GUI launchers develop using their own various toolkits, so zenity's default themes will not conform with it and lacks the customization to do so. On top of that, when gaming on a handheld device in a game mode session, the zenity popup window would look rather unpleasant. Instead of using curl and zenity as the default operation, the launcher will default to downloading using native Python functionality. - In the future, alternative methods will be explored to notify download state to clients. To re-enable zenity, set UMU_ZENITY=1 --- umu/umu_dl_util.py | 43 ++++++++++++++++--------------- umu/umu_plugins.py | 1 + umu/umu_run.py | 1 + umu/umu_util.py | 63 +++++++++++++++++++--------------------------- 4 files changed, 49 insertions(+), 59 deletions(-) diff --git a/umu/umu_dl_util.py b/umu/umu_dl_util.py index ff53afd1..f412a138 100644 --- a/umu/umu_dl_util.py +++ b/umu/umu_dl_util.py @@ -25,9 +25,6 @@ def get_umu_proton(env: Dict[str, str]) -> Union[Dict[str, str]]: Downloads the latest if not first found in: ~/.local/share/Steam/compatibilitytools.d - - The cache directory ~/.cache/umu is referenced for the latest then as - fallback """ files: List[Tuple[str, str]] = [] tmp: Path = Path(mkdtemp()) @@ -124,6 +121,7 @@ def _fetch_proton( hash, hash_url = files[0] proton, proton_url = files[1] proton_dir: str = proton[: proton.find(".tar.gz")] # Proton dir + ret: int = 0 # Exit code from zenity log.console(f"Downloading {hash} ...") @@ -147,8 +145,8 @@ def _fetch_proton( file.write(resp.read()) # Proton - # Check for Zenity otherwise print - try: + # Create a popup with zenity when the env var is set + if environ.get("UMU_ZENITY") == "1": bin: str = "curl" opts: List[str] = [ "-LJO", @@ -157,18 +155,18 @@ def _fetch_proton( "--output-dir", tmp.as_posix(), ] - msg: str = f"Downloading {proton_dir} ..." - enable_zenity(bin, opts, msg) - except TimeoutError: - err: str = f"Unable to download {proton}\ngithub.com request timed out" - raise TimeoutError(err) - except FileNotFoundError: + ret = enable_zenity(bin, opts, msg) + if ret: + tmp.joinpath(proton).unlink(missing_ok=True) + log.warning("zenity exited with the status code: %s", ret) + log.console("Retrying from Python ...") + if not environ.get("UMU_ZENITY") or ret: log.console(f"Downloading {proton} ...") - - with urlopen(proton_url, timeout=180, context=create_default_context()) as resp: # noqa: S310 + with urlopen( # noqa: S310 + proton_url, timeout=300, context=create_default_context() + ) as resp: # Without Proton, the launcher will not work - # Continue by referring to cache if resp.status != 200: err: str = ( f"Unable to download {proton}\n" @@ -185,7 +183,8 @@ def _fetch_proton( sha512(file.read()).hexdigest() != tmp.joinpath(hash).read_text().split(" ")[0] ): - err: str = "Digests mismatched.\nFalling back to cache ..." + err: str = "Digests mismatched" + log.warning(err) raise ValueError(err) log.console(f"{proton}: SHA512 is OK") @@ -196,9 +195,9 @@ def _fetch_proton( return env -def _extract_dir(proton: Path, steam_compat: Path) -> None: +def _extract_dir(file: Path, steam_compat: Path) -> None: """Extract from the cache to another location.""" - with tar_open(proton.as_posix(), "r:gz") as tar: + with tar_open(file.as_posix(), "r:gz") as tar: if tar_filter: log.debug("Using filter for archive") tar.extraction_filter = tar_filter @@ -206,23 +205,23 @@ def _extract_dir(proton: Path, steam_compat: Path) -> None: log.debug("Using no filter for archive") log.warning("Archive will be extracted insecurely") - log.console(f"Extracting {proton} -> {steam_compat} ...") + log.console(f"Extracting {file} -> {steam_compat} ...") # TODO: Rather than extracting all of the contents, we should prefer # the difference (e.g., rsync) tar.extractall(path=steam_compat.as_posix()) # noqa: S202 log.console("Completed.") -def _cleanup(tarball: str, proton: str, cache: Path, steam_compat: Path) -> None: +def _cleanup(tarball: str, proton: str, tmp: Path, steam_compat: Path) -> None: """Remove files that may have been left in an incomplete state to avoid corruption. We want to do this when a download for a new release is interrupted """ log.console("Keyboard Interrupt.\nCleaning ...") - if cache.joinpath(tarball).is_file(): - log.console(f"Purging {tarball} in {cache} ...") - cache.joinpath(tarball).unlink() + if tmp.joinpath(tarball).is_file(): + log.console(f"Purging {tarball} in {tmp} ...") + tmp.joinpath(tarball).unlink() if steam_compat.joinpath(proton).is_dir(): log.console(f"Purging {proton} in {steam_compat} ...") rmtree(steam_compat.joinpath(proton).as_posix()) diff --git a/umu/umu_plugins.py b/umu/umu_plugins.py index d49da16d..fa5035fe 100644 --- a/umu/umu_plugins.py +++ b/umu/umu_plugins.py @@ -180,6 +180,7 @@ def enable_zenity(command: str, opts: List[str], msg: str) -> int: f"--text={msg}", "--percentage=0", "--pulsate", + "--no-cancel", ], stdin=PIPE, ) as zenity_proc, diff --git a/umu/umu_run.py b/umu/umu_run.py index 11041f53..39c91d3c 100755 --- a/umu/umu_run.py +++ b/umu/umu_run.py @@ -278,6 +278,7 @@ def main() -> int: # noqa: D103 "PROTON_VERB": "", "UMU_ID": "", "ULWGL_ID": "", + "UMU_ZENITY": "", } command: List[str] = [] opts: List[str] = None diff --git a/umu/umu_util.py b/umu/umu_util.py index 0f755752..d0effd2b 100644 --- a/umu/umu_util.py +++ b/umu/umu_util.py @@ -1,5 +1,5 @@ from tarfile import open as tar_open, TarInfo -from os import getuid +from os import getuid, environ from umu_consts import CONFIG, STEAM_COMPAT, UMU_LOCAL from typing import Any, Dict, List, Callable from json import load, dump @@ -72,46 +72,35 @@ def setup_runtime(json: Dict[str, Any]) -> None: # noqa: D103 # Step 1: Define the URL of the file to download # We expect the archive name to not change base_url: str = f"https://repo.steampowered.com/steamrt3/images/{version}/{archive}" + bin: str = "curl" + opts: List[str] = [ + "-LJO", + "--silent", + f"{base_url}", + "--output-dir", + tmp.as_posix(), + ] + ret: int = 0 # Exit code from zenity + log.debug("URL: %s", base_url) # Download the runtime - # Attempt to create a popup with zenity otherwise print - try: - bin: str = "curl" - opts: List[str] = [ - "-LJO", - "--silent", - base_url, - "--output-dir", - tmp.as_posix(), - ] - - msg: str = "Downloading Runtime, please wait..." + # Optionally create a popup with zenity + if environ.get("UMU_ZENITY") == "1": + msg: str = "Downloading UMU-Runtime ..." ret: int = enable_zenity(bin, opts, msg) - - # Handle the symbol lookup error from the zenity flatpak in lutris if ret: - log.warning("zenity exited with the status code: %s", ret) - log.warning("zenity will not be used") tmp.joinpath(archive).unlink(missing_ok=True) - raise FileNotFoundError - except TimeoutError: - # Without the runtime, the launcher will not work - # Just exit on timeout or download failure - err: str = ( - "Unable to download the Steam Runtime\n" - "repo.steampowered.com request timed out " - "or timeout limit was reached" - ) - raise TimeoutError(err) - except FileNotFoundError: - log.console(f"Downloading {runtime_platform_value} ...") - - # We hardcode the URL and trust it - with urlopen(base_url, timeout=60, context=create_default_context()) as resp: # noqa: S310 + log.warning("zenity exited with the status code: %s", ret) + log.console("Retrying from Python ...") + if not environ.get("UMU_ZENITY") or ret: + log.console(f"Downloading {runtime_platform_value}, please wait ...") + with urlopen( # noqa: S310 + base_url, timeout=300, context=create_default_context() + ) as resp: if resp.status != 200: err: str = ( - "Unable to download the Steam Runtime\n" + f"Unable to download {hash}\n" f"repo.steampowered.com returned the status: {resp.status}" ) raise HTTPException(err) @@ -225,11 +214,11 @@ def _install_umu( local.mkdir(parents=True, exist_ok=True) # Config - log.console(f"Copying {CONFIG} -> {local} ...") + log.console(f"Copied {CONFIG} -> {local}") copy(root.joinpath(CONFIG), local.joinpath(CONFIG)) # Reaper - log.console(f"Copying reaper -> {local} ...") + log.console(f"Copied reaper -> {local}") copy(root.joinpath("reaper"), local.joinpath("reaper")) # Runtime platform @@ -239,7 +228,7 @@ def _install_umu( # Launcher files for file in root.glob("*.py"): if not file.name.startswith("umu_test"): - log.console(f"Copying {file} -> {local} ...") + log.console(f"Copied {file} -> {local}") copy(file, local.joinpath(file.name)) local.joinpath("umu-run").symlink_to("umu_run.py") @@ -247,7 +236,7 @@ def _install_umu( # Runner steam_compat.mkdir(parents=True, exist_ok=True) - log.console(f"Copying umu-launcher -> {steam_compat} ...") + log.console(f"Copied umu-launcher -> {steam_compat}") # Remove existing files if they exist -- this is a clean install. if steam_compat.joinpath("umu-launcher").is_dir():