import os import shutil from gettext import gettext as _ from pathlib import Path from typing import Callable from lutris.exceptions import GameConfigError, MissingExecutableError from lutris.monitored_command import MonitoredCommand from lutris.runners import NonInstallableRunnerError from lutris.runners.runner import Runner from lutris.util import flatpak as _flatpak from lutris.util import system from lutris.util.strings import split_arguments class flatpak(Runner): """ Runner for Flatpak applications. """ description = _("Runs Flatpak applications") platforms = [_("Linux")] entry_point_option = "application" human_name = _("Flatpak") runnable_alone = False system_options_override = [{"option": "disable_runtime", "default": True}] install_locations = {"system": "var/lib/flatpak/app/", "user": f"{Path.home()}/.local/share/flatpak/app/"} game_options = [ { "option": "appid", "type": "string", "label": _("Application ID"), "help": _("The application's unique three-part identifier (tld.domain.app)."), }, { "option": "arch", "type": "string", "label": _("Architecture"), "help": _( "The architecture to run. " "See flatpak --supported-arches for architectures supported by the host." ), "advanced": True, }, {"option": "branch", "type": "string", "label": _("Branch"), "help": _("The branch to use."), "advanced": True}, { "option": "install_type", "type": "string", "label": _("Install type"), "help": _("Can be system or user."), "advanced": True, }, { "option": "args", "type": "string", "label": _("Args"), "help": _("Arguments to be passed to the application."), }, { "option": "fcommand", "type": "string", "label": _("Command"), "help": _("The command to run instead of the one listed in the application metadata."), "advanced": True, }, { "option": "working_dir", "type": "directory", "label": _("Working directory"), "warn_if_non_writable_parent": True, "help": _("The directory to run the command in. Note that this must be a directory inside the sandbox."), "advanced": True, }, { "option": "env_vars", "type": "string", "label": _("Environment variables"), "help": _( "Set an environment variable in the application. " "This overrides to the Context section from the application metadata." ), "advanced": True, }, ] def is_installed(self, flatpak_allowed: bool = True) -> bool: return _flatpak.is_installed() def get_executable(self) -> str: exe = _flatpak.get_executable() if not system.path_exists(exe): raise MissingExecutableError(_("The Flatpak executable could not be found.")) return exe def install(self, install_ui_delegate, version=None, callback=None): raise NonInstallableRunnerError( _( "Flatpak installation is not handled by Lutris.\n" "Install Flatpak with the package provided by your distribution." ) ) def can_uninstall(self): return False def uninstall(self, uninstall_callback: Callable[[], None]) -> None: raise RuntimeError("Flatpak can't be uninstalled from Lutris") @property def game_path(self): if shutil.which("flatpak-spawn"): return "/" install_type, application, arch, fcommand, branch = ( self.game_config.get(key, "") for key in ("install_type", "appid", "arch", "fcommand", "branch") ) return os.path.join(self.install_locations[install_type or "user"], application, arch, fcommand, branch) def remove_game_data(self, app_id=None, game_path=None, **kwargs): if not self.is_installed(): return False command = MonitoredCommand( [self.get_executable(), f"uninstall --app --noninteractive {app_id}"], runner=self, env=self.get_env(), title=f"Uninstalling Flatpak App: {app_id}", ) command.start() def get_command(self): arch = self.game_config.get("arch", "") branch = self.game_config.get("branch", "") fcommand = self.game_config.get("fcommand", "") return _flatpak.get_bare_run_command(arch, fcommand, branch) def play(self): appid = self.game_config.get("appid", "") args = self.game_config.get("args", "") if not appid: raise GameConfigError(_("No application specified.")) if appid.count(".") < 2: raise GameConfigError( _("Application ID is not specified in correct format." "Must be something like: tld.domain.app") ) if any(x in appid for x in ("--", "/")): raise GameConfigError(_("Application ID field must not contain options or arguments.")) command = self.get_command() + [appid] if args: command.extend(split_arguments(args)) return {"command": command}