Smarter launch-config command building

This way the linux runner can apply it's 'exe' setting without any prefixing
command like WINE has. This makes launch-configs work for the linux runner.

Also, launch_configs can now outright specify a command key to override
the normal command building stuff.

Resolves #4637
This commit is contained in:
Daniel Johnson 2022-12-07 17:51:34 -05:00
parent b90e349294
commit cf95fe7d99
4 changed files with 66 additions and 19 deletions

View file

@ -453,17 +453,7 @@ class Game(GObject.Object):
if dlg.config_index:
config = configs[dlg.config_index - 1]
if "command" not in gameplay_info:
logger.debug("No command in %s", gameplay_info)
logger.debug(config)
# The 'file' sort of gameplay_info cannot be made to use a configuration
raise GameConfigError(_("The runner could not find a command to apply the configuration to."))
gameplay_info["command"] = [gameplay_info["command"][0], config["exe"]]
if config.get("args"):
gameplay_info["command"] += strings.split_arguments(config["args"])
if config.get("working_dir"):
gameplay_info["working_dir"] = config["working_dir"]
self.runner.apply_launch_config(gameplay_info, config)
return gameplay_info

View file

@ -5,6 +5,7 @@ import stat
from gettext import gettext as _
# Lutris Modules
from lutris.exceptions import GameConfigError
from lutris.runners.runner import Runner
from lutris.util import system
from lutris.util.strings import split_arguments
@ -88,12 +89,10 @@ class linux(Runner):
def resolve_game_path(self):
return super().resolve_game_path() or os.path.dirname(self.game_exe or "")
def get_relative_exe(self):
"""Return a relative path if a working dir is set in the options
def get_relative_exe(self, exe_path, working_dir):
"""Return a relative path if a working dir is provided
Some games such as Unreal Gold fail to run if given the absolute path
"""
exe_path = self.game_exe
working_dir = self.game_config.get("working_dir")
if exe_path and working_dir:
relative = os.path.relpath(exe_path, start=working_dir)
if not relative.startswith("../"):
@ -123,6 +122,26 @@ class linux(Runner):
"""Well of course Linux is installed, you're using Linux right ?"""
return True
def get_launch_config_command(self, gameplay_info, launch_config):
# The linux runner has no command (by default) beyond the 'exe' itself;
# so the command in gameplay_info is discarded.
if "command" in launch_config:
command = split_arguments(launch_config["command"])
else:
command = []
working_dir = launch_config.get("working_dir") or self.working_dir
if "exe" in launch_config:
command.append(self.get_relative_exe(launch_config["exe"], working_dir))
elif len(command) == 0:
raise GameConfigError(_("The runner could not find a command or exe to use for this configuration."))
if launch_config.get("args"):
command += split_arguments(launch_config["args"])
return command
def play(self):
"""Run native game."""
launch_info = {}
@ -146,7 +165,7 @@ class linux(Runner):
if ld_library_path:
launch_info["ld_library_path"] = os.path.expanduser(ld_library_path)
command = [self.get_relative_exe()]
command = [self.get_relative_exe(self.game_exe, self.working_dir)]
args = self.game_config.get("args") or ""
for arg in split_arguments(args):

View file

@ -9,10 +9,10 @@ from lutris import runtime, settings
from lutris.command import MonitoredCommand
from lutris.config import LutrisConfig
from lutris.database.games import get_game_by_field
from lutris.exceptions import UnavailableLibrariesError
from lutris.exceptions import GameConfigError, UnavailableLibrariesError
from lutris.gui import dialogs
from lutris.runners import RunnerInstallationError
from lutris.util import system
from lutris.util import strings, system
from lutris.util.extract import ExtractFailure, extract_archive
from lutris.util.http import HTTPError, Request
from lutris.util.linux import LINUX_SYSTEM
@ -250,6 +250,41 @@ class Runner: # pylint: disable=too-many-public-methods
"""
return runtime.get_env(prefer_system_libs=self.system_config.get("prefer_system_libs", True))
def apply_launch_config(self, gameplay_info, launch_config):
"""Updates the gameplay_info to reflect a launch_config section. Called only
if a non-default config is chosen."""
gameplay_info["command"] = self.get_launch_config_command(gameplay_info, launch_config)
if launch_config.get("working_dir"):
gameplay_info["working_dir"] = launch_config["working_dir"]
def get_launch_config_command(self, gameplay_info, launch_config):
"""Generates a new command for the gameplay_info, to implement the launch_config.
Returns a new list of strings; the caller can modify it further.
If launch_config has no command, this builds one from the gameplay_info command
and the 'exe' value in the launch_config.
Runners override this when required to control the command used."""
if "command" in launch_config:
command = strings.split_arguments(launch_config["command"])
elif "command" in gameplay_info:
command = [gameplay_info["command"][0]]
else:
logger.debug("No command in %s", gameplay_info)
logger.debug(launch_config)
# The 'file' sort of gameplay_info cannot be made to use a configuration
raise GameConfigError(_("The runner could not find a command to apply the configuration to."))
if "exe" in launch_config:
command.append(launch_config["exe"])
if launch_config.get("args"):
command += strings.split_arguments(launch_config["args"])
return command
def prelaunch(self):
"""Run actions before running the game, override this method in runners; raise an
exception if prelaunch fails, and it will be reported to the user, and

View file

@ -115,9 +115,12 @@ properties:
name:
type: string
description: Identifier to show in the launcher dialog
command:
type: string
description: Full bash command to execute, overriding default game command entirely
exe:
type: string
description: Main game executable
description: Main game executable, to combine with the command
args:
type: string
description: Additional command arguments