From 9a941e3001fadce824871b53590bd11ae0310f09 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 3 Sep 2023 01:38:42 +0300 Subject: [PATCH] Wine: Set environment through `flatpak-spawn` arguments Partially fixes #305 --- rare/launcher/__init__.py | 28 +++++++++++++---------- rare/launcher/lgd_helper.py | 33 ++++++++++++++++++---------- rare/shared/workers/wine_resolver.py | 33 ++++++++++++++++------------ rare/utils/wine.py | 29 ++++++++++++++---------- 4 files changed, 74 insertions(+), 49 deletions(-) diff --git a/rare/launcher/__init__.py b/rare/launcher/__init__.py index dae563cc..e8793a66 100644 --- a/rare/launcher/__init__.py +++ b/rare/launcher/__init__.py @@ -68,7 +68,7 @@ class PreLaunchThread(QRunnable): if launch_args.pre_launch_command: proc = get_configured_process() - proc.setProcessEnvironment(launch_args.env) + proc.setProcessEnvironment(launch_args.environment) self.signals.started_pre_launch_command.emit() proc.start(launch_args.pre_launch_command[0], launch_args.pre_launch_command[1:]) if launch_args.pre_launch_wait: @@ -119,6 +119,7 @@ class RareLauncher(RareApp): def __init__(self, args: InitArgs): super(RareLauncher, self).__init__(args, f"{type(self).__name__}_{args.app_name}_{{0}}.log") + self.socket: Optional[QLocalSocket] = None self._hook.deleteLater() self._hook = RareLauncherException(self, args, self) @@ -126,7 +127,11 @@ class RareLauncher(RareApp): self.no_sync_on_exit = False self.args = args self.core = LegendaryCore() - self.rgame = RareGameSlim(self.core, self.core.get_game(args.app_name)) + game = self.core.get_game(args.app_name) + if not game: + self.logger.error(f"Game {args.app_name} not found. Exiting") + QApplication.exit(1) + self.rgame = RareGameSlim(self.core, game) lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) @@ -242,13 +247,14 @@ class RareLauncher(RareApp): ) self.stop() + @pyqtSlot(object) def launch_game(self, args: LaunchArgs): # should never happen if not args: self.stop() return if self.console: - self.console.set_env(args.env) + self.console.set_env(args.environment) self.start_time = time.time() if args.is_origin_game: @@ -256,9 +262,9 @@ class RareLauncher(RareApp): self.stop() # stop because it is no subprocess return - if args.cwd: - self.game_process.setWorkingDirectory(args.cwd) - self.game_process.setProcessEnvironment(args.env) + if args.working_directory: + self.game_process.setWorkingDirectory(args.working_directory) + self.game_process.setProcessEnvironment(args.environment) # send start message after process started self.game_process.started.connect(lambda: self.send_message( StateChangedModel( @@ -268,8 +274,8 @@ class RareLauncher(RareApp): )) if self.rgame.app_name in DETACHED_APP_NAMES and platform.system() == "Windows": self.game_process.deleteLater() - subprocess.Popen([args.executable] + args.args, cwd=args.cwd, - env={i: args.env.value(i) for i in args.env.keys()}) + subprocess.Popen([args.executable] + args.arguments, cwd=args.working_directory, + env={i: args.environment.value(i) for i in args.environment.keys()}) if self.console: self.console.log("Launching game detached") self.stop() @@ -277,13 +283,13 @@ class RareLauncher(RareApp): if self.args.dry_run: self.logger.info("Dry run activated") if self.console: - self.console.log(f"{args.executable} {' '.join(args.args)}") + self.console.log(f"{args.executable} {' '.join(args.arguments)}") self.console.log(f"Do not start {self.rgame.app_name}") self.console.accept_close = True - print(args.executable, " ".join(args.args)) + print(args.executable, " ".join(args.arguments)) self.stop() return - self.game_process.start(args.executable, args.args) + self.game_process.start(args.executable, args.arguments) def error_occurred(self, error_str: str): self.logger.warning(error_str) diff --git a/rare/launcher/lgd_helper.py b/rare/launcher/lgd_helper.py index 9d0bdd53..7e65dd74 100644 --- a/rare/launcher/lgd_helper.py +++ b/rare/launcher/lgd_helper.py @@ -44,9 +44,9 @@ class InitArgs(Namespace): @dataclass class LaunchArgs: executable: str = "" - args: List[str] = None - cwd: str = None - env: QProcessEnvironment = None + arguments: List[str] = None + working_directory: str = None + environment: QProcessEnvironment = None pre_launch_command: str = "" pre_launch_wait: bool = False is_origin_game: bool = False # only for windows to launch as url @@ -60,7 +60,7 @@ def get_origin_params(core: LegendaryCore, app_name, offline: bool, origin_uri = core.get_origin_uri(app_name, offline) if platform.system() == "Windows": launch_args.executable = origin_uri - launch_args.args = [] + launch_args.arguments = [] # only set it here true, because on linux it is a launch command like every other game launch_args.is_origin_game = True return launch_args @@ -71,12 +71,19 @@ def get_origin_params(core: LegendaryCore, app_name, offline: bool, command.append(origin_uri) env = core.get_app_environment(app_name) - launch_args.env = QProcessEnvironment.systemEnvironment() + + if os.environ.get("container") == "flatpak": + flatpak_command = ["flatpak-spawn", "--host"] + for name, value in env.items(): + flatpak_command.append(f"--env={name}={value}") + command = flatpak_command.extend(command) + + launch_args.environment = QProcessEnvironment.systemEnvironment() for name, value in env.items(): - launch_args.env.insert(name, value) + launch_args.environment.insert(name, value) launch_args.executable = command[0] - launch_args.args = command[1:] + launch_args.arguments = command[1:] return launch_args @@ -100,10 +107,12 @@ def get_game_params(core: LegendaryCore, igame: InstalledGame, args: InitArgs, app_name=igame.app_name, offline=args.offline ) - full_params = list() + full_params = [] if os.environ.get("container") == "flatpak": full_params.extend(["flatpak-spawn", "--host"]) + for name, value in params.environment.items(): + full_params.append(f"--env={name}={value}") full_params.extend(params.launch_command) full_params.append( @@ -114,12 +123,12 @@ def get_game_params(core: LegendaryCore, igame: InstalledGame, args: InitArgs, full_params.extend(params.user_parameters) launch_args.executable = full_params[0] - launch_args.args = full_params[1:] + launch_args.arguments = full_params[1:] - launch_args.env = QProcessEnvironment.systemEnvironment() + launch_args.environment = QProcessEnvironment.systemEnvironment() for name, value in params.environment.items(): - launch_args.env.insert(name, value) - launch_args.cwd = params.working_directory + launch_args.environment.insert(name, value) + launch_args.working_directory = params.working_directory return launch_args diff --git a/rare/shared/workers/wine_resolver.py b/rare/shared/workers/wine_resolver.py index b57813a9..c095e248 100644 --- a/rare/shared/workers/wine_resolver.py +++ b/rare/shared/workers/wine_resolver.py @@ -38,7 +38,7 @@ class WineResolver(Worker): # pylint: disable=E1136 self.signals.result_ready[str].emit("") return - if not os.path.exists(self.wine_exec) or not os.path.exists(wine.winepath(self.wine_exec)): + if not os.path.exists(self.wine_exec): # pylint: disable=E1136 self.signals.result_ready[str].emit("") return @@ -82,28 +82,33 @@ class OriginWineWorker(QRunnable): wine_env = wine.environ(self.core, rgame.app_name) wine_exec = wine.wine(self.core, rgame.app_name) - # lk: this is the original way of gettijng the path by parsing "system.reg" - wine_prefix = wine.prefix(self.core, rgame.app_name) - reg = self.__cache.get(wine_prefix, None) or wine.read_registry("system.reg", wine_prefix) - self.__cache[wine_prefix] = reg + use_wine = False + if not use_wine: + # lk: this is the original way of gettijng the path by parsing "system.reg" + wine_prefix = wine.prefix(self.core, rgame.app_name) + reg = self.__cache.get(wine_prefix, None) or wine.read_registry("system.reg", wine_prefix) + self.__cache[wine_prefix] = reg - reg_path = reg_path.replace("SOFTWARE", "Software").replace("WOW6432Node", "Wow6432Node") - # lk: split and rejoin the registry path to avoid slash expansion - reg_path = "\\\\".join([x for x in reg_path.split("\\") if bool(x)]) + reg_path = reg_path.replace("SOFTWARE", "Software").replace("WOW6432Node", "Wow6432Node") + # lk: split and rejoin the registry path to avoid slash expansion + reg_path = "\\\\".join([x for x in reg_path.split("\\") if bool(x)]) - install_dir = reg.get(reg_path, f'"{reg_key}"', fallback=None) + install_dir = reg.get(reg_path, f'"{reg_key}"', fallback=None) + else: + # lk: this is the alternative way of getting the path by using wine itself + install_dir = wine.query_reg_key(wine_exec, wine_env, f"HKLM\\{reg_path}", reg_key) - # lk: this is the alternative way of getting the path by using wine itself - # install_dir = wine.query_reg_key(wine_exec, wine_env, f"HKLM\\{reg_path}", reg_key) + logger.debug("Found Wine install directory %s", install_dir) if install_dir: install_dir = wine.convert_to_unix_path(wine_exec, wine_env, install_dir) + logger.debug("Found Unix install directory %s", install_dir) if install_dir: if os.path.isdir(install_dir): install_size = path_size(install_dir) rgame.set_origin_attributes(install_dir, install_size) - logger.debug(f"Found Origin game {rgame.title} ({install_dir}, {format_size(install_size)})") + logger.debug(f"Origin game {rgame.title} ({install_dir}, {format_size(install_size)})") else: - logger.warning(f"Found Origin game {rgame.title} ({install_dir} does not exist)") - logger.info(f"Origin registry worker finished in {time.time() - t}s") + logger.warning(f"Origin game {rgame.title} ({install_dir} does not exist)") + logger.info(f"Origin worker finished in {time.time() - t}s") diff --git a/rare/utils/wine.py b/rare/utils/wine.py index 474d06ac..0d7e963e 100644 --- a/rare/utils/wine.py +++ b/rare/utils/wine.py @@ -2,10 +2,13 @@ import os import shutil import subprocess from configparser import ConfigParser +from logging import getLogger from typing import Mapping, Dict, List, Tuple from rare.lgndr.core import LegendaryCore +logger = getLogger("Wine") + # this is a copied function from legendary.utils.wine_helpers, but registry file can be specified def read_registry(registry: str, wine_pfx: str) -> ConfigParser: @@ -21,16 +24,22 @@ def read_registry(registry: str, wine_pfx: str) -> ConfigParser: def execute(cmd: List, wine_env: Mapping) -> Tuple[str, str]: if os.environ.get("container") == "flatpak": - cmd = ["flatpak-spawn", "--host"] + cmd + flatpak_cmd = ["flatpak-spawn", "--host"] + for name, value in wine_env.items(): + flatpak_cmd.append(f"--env={name}={value}") + cmd = flatpak_cmd + cmd proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=wine_env, + # Use the current environment if we are in flatpak or our own if we are on host + # In flatpak our environment is passed through `flatpak-spawn` arguments + env=os.environ.copy() if os.environ.get("container") == "flatpak" else wine_env, shell=False, text=True, ) - return proc.communicate() + res = proc.communicate() + return res def resolve_path(wine_exec: str, wine_env: Mapping, path: str) -> str: @@ -65,18 +74,11 @@ def convert_to_windows_path(wine_exec: str, wine_env: Mapping, path: str) -> str def convert_to_unix_path(wine_exec: str, wine_env: Mapping, path: str) -> str: path = path.strip().strip('"') - cmd = [winepath(wine_exec), "-u", path] + cmd = [wine_exec, "winepath.exe", "-u", path] out, err = execute(cmd, wine_env) return os.path.realpath(out.strip()) -def winepath(wine_exec: str) -> str: - _winepath = os.path.join(os.path.dirname(wine_exec), "winepath") - if not os.path.isfile(_winepath): - return "" - return _winepath - - def wine(core: LegendaryCore, app_name: str = "default") -> str: _wine = core.lgd.config.get( app_name, "wine_executable", fallback=core.lgd.config.get( @@ -87,8 +89,11 @@ def wine(core: LegendaryCore, app_name: str = "default") -> str: def environ(core: LegendaryCore, app_name: str = "default") -> Dict: - _environ = os.environ.copy() + # Get a clean environment if we are in flatpak, this environment will be pass + # to `flatpak-spawn`, otherwise use the system's. + _environ = {} if os.environ.get("container") == "flatpak" else os.environ.copy() _environ.update(core.get_app_environment(app_name)) + _environ["WINEDEBUG"] = "-all" _environ["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;" _environ["DISPLAY"] = "" return _environ