Merge pull request #167 from kra-mo/retroarch-make-exec

RetroArch executable improvement (part 1)
This commit is contained in:
Geoffrey Coulaud
2023-08-17 14:14:58 +02:00
committed by GitHub
4 changed files with 107 additions and 50 deletions

View File

@@ -26,7 +26,7 @@ from gi.repository import GLib, Gtk
from src import shared from src import shared
from src.game import Game from src.game import Game
from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.location import Location, LocationSubPath
from src.importer.sources.source import Source, SourceIterable from src.importer.sources.source import ExecutableFormatSource, SourceIterable
class FlatpakSourceIterable(SourceIterable): class FlatpakSourceIterable(SourceIterable):
@@ -116,7 +116,7 @@ class FlatpakLocations(NamedTuple):
data: Location data: Location
class FlatpakSource(Source): class FlatpakSource(ExecutableFormatSource):
"""Generic Flatpak source""" """Generic Flatpak source"""
source_id = "flatpak" source_id = "flatpak"

View File

@@ -26,7 +26,11 @@ from typing import NamedTuple
from src import shared from src import shared
from src.game import Game from src.game import Game
from src.importer.sources.location import Location, LocationSubPath from src.importer.sources.location import Location, LocationSubPath
from src.importer.sources.source import Source, SourceIterable, SourceIterationResult from src.importer.sources.source import (
ExecutableFormatSource,
SourceIterationResult,
SourceIterable,
)
class LegendarySourceIterable(SourceIterable): class LegendarySourceIterable(SourceIterable):
@@ -93,7 +97,7 @@ class LegendaryLocations(NamedTuple):
config: Location config: Location
class LegendarySource(Source): class LegendarySource(ExecutableFormatSource):
source_id = "legendary" source_id = "legendary"
name = _("Legendary") name = _("Legendary")
executable_format = "legendary launch {app_name}" executable_format = "legendary launch {app_name}"

View File

@@ -23,8 +23,10 @@ import re
from hashlib import md5 from hashlib import md5
from json import JSONDecodeError from json import JSONDecodeError
from pathlib import Path from pathlib import Path
from shlex import quote as shell_quote
from time import time from time import time
from typing import NamedTuple from typing import NamedTuple
from urllib.parse import quote as url_quote
from src import shared from src import shared
from src.errors.friendly_error import FriendlyError from src.errors.friendly_error import FriendlyError
@@ -103,9 +105,9 @@ class RetroarchSourceIterable(SourceIterable):
"added": added_time, "added": added_time,
"name": item["label"], "name": item["label"],
"game_id": self.source.game_id_format.format(game_id=game_id), "game_id": self.source.game_id_format.format(game_id=game_id),
"executable": self.source.executable_format.format( "executable": self.source.make_executable(
rom_path=item["path"],
core_path=core_path, core_path=core_path,
rom_path=item["path"],
), ),
} }
@@ -147,13 +149,44 @@ class RetroarchSource(Source):
locations: RetroarchLocations locations: RetroarchLocations
@property def __init__(self) -> None:
def executable_format(self): super().__init__()
self.locations.config.resolve() self.locations = RetroarchLocations(
is_flatpak = self.locations.config.root.is_relative_to(shared.flatpak_dir) Location(
base = "flatpak run org.libretro.RetroArch" if is_flatpak else "retroarch" schema_key="retroarch-location",
args = '-L "{core_path}" "{rom_path}"' candidates=[
return f"{base} {args}" shared.flatpak_dir
/ "org.libretro.RetroArch"
/ "config"
/ "retroarch",
shared.config_dir / "retroarch",
shared.home / ".config" / "retroarch",
# TODO: Windows support, waiting for executable path setting improvement
# Path("C:\\RetroArch-Win64"),
# Path("C:\\RetroArch-Win32"),
# TODO: UWP support (URL handler - https://github.com/libretro/RetroArch/pull/13563)
# shared.local_appdata_dir
# / "Packages"
# / "1e4cf179-f3c2-404f-b9f3-cb2070a5aad8_8ngdn9a6dx1ma"
# / "LocalState",
],
paths={
"retroarch.cfg": LocationSubPath("retroarch.cfg"),
},
invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE,
)
)
# TODO enable when we get the Steam RetroArch games work
# self.add_steam_location_candidate()
def add_steam_location_candidate(self) -> None:
"""Add the Steam RetroAcrh location to the config candidates"""
try:
self.locations.config.candidates.append(self.get_steam_location())
except (OSError, KeyError, UnresolvableLocationError):
logging.debug("Steam isn't installed")
except ValueError as error:
logging.debug("RetroArch Steam location candiate not found", exc_info=error)
def get_steam_location(self) -> str: def get_steam_location(self) -> str:
""" """
@@ -185,36 +218,41 @@ class RetroarchSource(Source):
# Not found # Not found
raise ValueError("RetroArch not found in Steam library") raise ValueError("RetroArch not found in Steam library")
def __init__(self) -> None: def make_executable(self, core_path: Path, rom_path: Path) -> str:
super().__init__() """
self.locations = RetroarchLocations( Generate an executable command from the rom path and core path,
Location( depending on the source's location.
schema_key="retroarch-location",
candidates=[ The format depends on RetroArch's installation method,
shared.flatpak_dir detected from the source config location
/ "org.libretro.RetroArch"
/ "config" :param Path rom_path: the game's rom path
/ "retroarch", :param Path core_path: the game's core path
shared.config_dir / "retroarch", :return str: an executable command
shared.home / ".config" / "retroarch", """
# TODO: Windows support, waiting for executable path setting improvement
# Path("C:\\RetroArch-Win64"), self.locations.config.resolve()
# Path("C:\\RetroArch-Win32"), args = ("-L", core_path, rom_path)
# TODO: UWP support (URL handler - https://github.com/libretro/RetroArch/pull/13563)
# shared.local_appdata_dir # Steam RetroArch
# / "Packages" # (Must check before Flatpak, because Steam itself can be installed as one)
# / "1e4cf179-f3c2-404f-b9f3-cb2070a5aad8_8ngdn9a6dx1ma" # TODO enable when we get Steam RetroArch executable to work
# / "LocalState", # if self.locations.config.root.parent.parent.name == "steamapps":
], # # steam://run exepects args to be url-encoded and separated by spaces.
paths={ # args = map(lambda s: url_quote(str(s), safe=""), args)
"retroarch.cfg": LocationSubPath("retroarch.cfg"), # args_str = " ".join(args)
}, # uri = f"steam://run/1118310//{args_str}/"
invalid_subtitle=Location.CONFIG_INVALID_SUBTITLE, # return f"xdg-open {shell_quote(uri)}"
)
) # Flatpak RetroArch
try: args = map(lambda s: shell_quote(str(s)), args)
self.locations.config.candidates.append(self.get_steam_location()) args_str = " ".join(args)
except (OSError, KeyError, UnresolvableLocationError): if self.locations.config.root.is_relative_to(shared.flatpak_dir):
logging.debug("Steam isn't installed") return f"flatpak run org.libretro.RetroArch {args_str}"
except ValueError as error:
logging.debug("RetroArch Steam location candiate not found", exc_info=error) # TODO executable override for non-sandboxed sources
# Linux native RetroArch
return f"retroarch {args_str}"
# TODO implement for windows (needs override)

View File

@@ -78,10 +78,12 @@ class Source(Iterable):
def is_available(self) -> bool: def is_available(self) -> bool:
return sys.platform in self.available_on return sys.platform in self.available_on
@property
@abstractmethod @abstractmethod
def executable_format(self) -> str: def make_executable(self, *args, **kwargs) -> str:
"""The executable format used to construct game executables""" """
Create a game executable command.
Should be implemented by child classes.
"""
def __iter__(self) -> Generator[SourceIterationResult, None, None]: def __iter__(self) -> Generator[SourceIterationResult, None, None]:
""" """
@@ -93,8 +95,21 @@ class Source(Iterable):
return iter(self.iterable_class(self)) return iter(self.iterable_class(self))
class ExecutableFormatSource(Source):
"""Source class that uses a simple executable format to start games"""
@property
@abstractmethod
def executable_format(self) -> str:
"""The executable format used to construct game executables"""
def make_executable(self, *args, **kwargs) -> str:
"""Use the executable format to"""
return self.executable_format.format(args, kwargs)
# pylint: disable=abstract-method # pylint: disable=abstract-method
class URLExecutableSource(Source): class URLExecutableSource(ExecutableFormatSource):
"""Source class that use custom URLs to start games""" """Source class that use custom URLs to start games"""
url_format: str url_format: str