diff --git a/platforms/_registry.yml b/platforms/_registry.yml index 42bf41f68..6c5babac7 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -931,6 +931,10 @@ platforms: # retroarch/package.mk:115 at v4.8) and setsettings.sh never sets it, so # RetroArch saves beside each game: no single place. save_directory: null + # root's home, which every launcher script gets from /etc/profile + # (packages/sysutils/busybox/package.mk:168 and + # packages/sysutils/busybox/profile.d/98-busybox.conf:4 at v4.8). + home_directory: /storage schedule: monthly target_scraper: emuelec_targets target_source: https://github.com/EmuELEC/EmuELEC/blob/dev/packages/sx05re/emuelec/package.mk diff --git a/scripts/launchers.py b/scripts/launchers.py index 7b898da25..e3ca1e6ff 100644 --- a/scripts/launchers.py +++ b/scripts/launchers.py @@ -347,14 +347,19 @@ _HOME = re.compile(r"^(?:\$\{HOME\}|\$HOME|\$home|~)(?=/|$)") _FLATPAK_DATA = re.compile(r"^~/\.var/app/[^/]+/data(?=/|$)") -def normalize_handle(token: str) -> str: +def normalize_handle(token: str, home: str = "") -> str: """One spelling for a path, setting or function a launcher record names. ``$home``, ``$HOME``, ``${HOME}`` and ``~`` are one directory, and a - Flatpak's data directory is its ``$XDG_DATA_HOME``. + Flatpak's data directory is its ``$XDG_DATA_HOME``. ``home`` is the + platform's home directory spelled out (``home_directory``): EmuELEC + links /storage/.local/share/flycast, Flycast's ~/.local/share/flycast. """ token = token.strip().strip("\"'").rstrip(",;").rstrip("/") token = token.removesuffix("()") + home = home.rstrip("/") + if home and _below(token, home) is not None: + token = "~" + token[len(home):] token = _HOME.sub("~", token) return _FLATPAK_DATA.sub("$XDG_DATA_HOME", token) @@ -464,6 +469,7 @@ def handed_directories(config: dict) -> list[HandedDirectory]: holds gives one directory per system the platform declares. """ base = str(config.get("base_destination") or "bios") + home = str(config.get("home_directory") or "") natives = native_systems(config) declared = {d.lower() for d in declared_destinations(config)} found: list[HandedDirectory] = [] @@ -490,7 +496,7 @@ def handed_directories(config: dict) -> list[HandedDirectory]: if any(re.search(r"[\s,:;]", part) for part in parts): continue for part in parts: - directory = normalize_handle(part) + directory = normalize_handle(part, home) if passes_to.startswith(WORKING_DIRECTORY_PASSED): # The program runs from the directory: what its code # reads through getcwd(), or relative to it, is below it. @@ -499,7 +505,7 @@ def handed_directories(config: dict) -> list[HandedDirectory]: handles = frozenset( handle for token in passes_to.split() - if (handle := normalize_handle(token)) and handle != directory + if (handle := normalize_handle(token, home)) and handle != directory ) | ini_keys(passes_to) folder = _folder_in_bios(part, base) for emulator in (e.strip() for e in str(record.get("emulator", "")).split(",")): diff --git a/tests/test_launcher_placement.py b/tests/test_launcher_placement.py index 95170a93a..2f734d495 100644 --- a/tests/test_launcher_placement.py +++ b/tests/test_launcher_placement.py @@ -721,5 +721,31 @@ class Quasi88OnRetroPie(unittest.TestCase): ) +class FlycastOnEmuELEC(unittest.TestCase): + """EmuELEC links /storage/.local/share/flycast to bios/dc (flycast.sh:11-14), + and root's home is /storage (busybox profile.d/98-busybox.conf:4).""" + + def test_the_home_directory_is_spelled_as_home(self) -> None: + from launchers import normalize_handle + + self.assertEqual( + normalize_handle("/storage/.local/share/flycast", "/storage"), "~/.local/share/flycast" + ) + self.assertEqual(normalize_handle("/storage", "/storage/"), "~") + self.assertEqual(normalize_handle("/storagex/a", "/storage"), "/storagex/a") + self.assertEqual( + normalize_handle("/storage/.local/share/flycast"), "/storage/.local/share/flycast" + ) + + def test_the_bios_lands_in_dc(self) -> None: + profile = load_emulator_profiles(str(REPO_ROOT / "emulators"))["flycast"] + config = load_platform_config("emuelec", str(REPO_ROOT / "platforms")) + layouts = platform_layouts("flycast", profile, standalone_set(config)) + (view,) = [v for v, standalone in layouts if standalone] + self.assertTrue(view["files"]) + for entry in view["files"]: + self.assertEqual(entry["standalone_path"], "dc/" + entry["layout_path"]) + + if __name__ == "__main__": unittest.main()