From 97d0a478d2fd3a1d62cdbee62ed911c372df5712 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 6 Nov 2026 08:18:59 +0100 Subject: [PATCH] fix: read RetroArch's config under XDG_CONFIG_HOME Native RetroArch on Linux keeps retroarch.cfg under $XDG_CONFIG_HOME/retroarch when the variable is set, and falls back to ~/.retroarch.cfg (file_path_special.c fill_pathname_application_data, configuration.c open_default_config_file at e98e1da0). Detection read ~/.config/retroarch only. --- install.ps1 | 2 +- install.py | 31 +++++++++++++++++------- install.sh | 2 +- install/launchers/index.json | 16 ++++++------- install/retroarch.json | 12 ++++++++++ platforms/_registry.yml | 13 ++++++++++ tests/test_install.py | 46 ++++++++++++++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 18 deletions(-) diff --git a/install.ps1 b/install.ps1 index 896cdaf76..e41f7f32d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -16,7 +16,7 @@ # and Invoke-WebRequest throws on a cut transfer before the hash is read: # the site's build rewrites this line there (scripts/sitebootstrap.py). $mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." - $defaultInstallSha256 = "34fd006cc0456c61b9a621e52f35cee6d3fd55ff14bbc6cea207ddc7bc57d858" + $defaultInstallSha256 = "f48287847612b616bcaf607bd50207353bef5648e650a732c2d9595a64726ab0" $maximumInstallerBytes = 2MB $installerExit = 0 $installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null } diff --git a/install.py b/install.py index 01c0b9425..05a057480 100644 --- a/install.py +++ b/install.py @@ -291,7 +291,6 @@ def _os_default_dests(os_type: str) -> dict[str, Path]: the command line but not found must not land in ~/bios, which none of them reads. """ - home = Path.home() if os_type == "windows": return { # RetroArch's system directory defaults to \system @@ -307,13 +306,13 @@ def _os_default_dests(os_type: str) -> dict[str, Path]: # RetroArch may be the Linux one inside the distribution, while RetroBat # only exists on the Windows side, reached through /mnt/c. return { - "retroarch": home / ".config" / "retroarch" / "system", + "retroarch": _unix_retroarch_base() / "system", "retrobat": _host_path("C:/RetroBat/bios"), } if os_type == "darwin": return {"retroarch": _macos_retroarch_base() / "system"} return { - "retroarch": home / ".config" / "retroarch" / "system", + "retroarch": _unix_retroarch_base() / "system", } @@ -1385,6 +1384,17 @@ def _macos_retroarch_base() -> Path: return Path.home() / "Documents" / "RetroArch" +def _unix_retroarch_base() -> Path: + """RetroArch's base folder on Linux: $XDG_CONFIG_HOME/retroarch when the + variable is set, else ~/.config/retroarch (fill_pathname_application_data + in file_path_special.c, frontend_unix_get_env in platform_unix.c). The + code tests only that the variable exists, so an empty one counts.""" + xdg = os.environ.get("XDG_CONFIG_HOME") + if xdg is not None: + return Path(xdg) / "retroarch" + return Path.home() / ".config" / "retroarch" + + # System directory -> the retroarch.cfg it was read from and the directory # ':' expands to there, for finding the cores of the same installation. _RETROARCH_CONFIGS: dict[Path, tuple[Path, Path]] = {} @@ -2773,11 +2783,16 @@ def detect_platforms(os_type: str) -> list[tuple[str, Path]]: if ra_dir: found.append(("retroarch", ra_dir)) - # RetroArch native - native_cfg = home / ".config" / "retroarch" / "retroarch.cfg" - ra_dir = _parse_retroarch_system_dir(native_cfg) - if ra_dir: - found.append(("retroarch", ra_dir)) + # RetroArch native: the config under its base folder, then + # ~/.retroarch.cfg when that one is missing (open_default_config_file + # in configuration.c). Either way the system folder defaults to the + # base folder's. + native_base = _unix_retroarch_base() + for native_cfg in (native_base / "retroarch.cfg", home / ".retroarch.cfg"): + ra_dir = _parse_retroarch_system_dir(native_cfg, native_base) + if ra_dir: + found.append(("retroarch", ra_dir)) + break if os_type == "darwin": # open_default_config_file reads /RetroArch/config diff --git a/install.sh b/install.sh index b085a4cbb..26c45e4c6 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins # curl or wget stop on a cut transfer before the hash is read: the site's # build rewrites this line there (scripts/sitebootstrap.py). MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." -DEFAULT_INSTALL_SHA256="34fd006cc0456c61b9a621e52f35cee6d3fd55ff14bbc6cea207ddc7bc57d858" +DEFAULT_INSTALL_SHA256="f48287847612b616bcaf607bd50207353bef5648e650a732c2d9595a64726ab0" MAX_INSTALLER_BYTES=2097152 cleanup() { diff --git a/install/launchers/index.json b/install/launchers/index.json index 358ee41c9..5ee293708 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1103111, - "sha256": "34fd006cc0456c61b9a621e52f35cee6d3fd55ff14bbc6cea207ddc7bc57d858", + "size": 1103894, + "sha256": "f48287847612b616bcaf607bd50207353bef5648e650a732c2d9595a64726ab0", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1156738, - "sha256": "35c0920008baadca997d0f778e99ce46d702bbe3d3f0d33104d7158422b292aa" + "size": 1157521, + "sha256": "0f9b070334a530a0ed5a6b6b773928d92f5a08aa0d0af18fde7b6cb4a16e731d" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1103111, - "sha256": "34fd006cc0456c61b9a621e52f35cee6d3fd55ff14bbc6cea207ddc7bc57d858", + "size": 1103894, + "sha256": "f48287847612b616bcaf607bd50207353bef5648e650a732c2d9595a64726ab0", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1103111, - "sha256": "34fd006cc0456c61b9a621e52f35cee6d3fd55ff14bbc6cea207ddc7bc57d858", + "size": 1103894, + "sha256": "f48287847612b616bcaf607bd50207353bef5648e650a732c2d9595a64726ab0", "executable": false }, { diff --git a/install/retroarch.json b/install/retroarch.json index 024fb6396..166d197d3 100644 --- a/install/retroarch.json +++ b/install/retroarch.json @@ -21,12 +21,24 @@ "config": "$HOME/snap/retroarch/current/.config/retroarch/retroarch.cfg", "parse_key": "system_directory" }, + { + "os": "linux", + "method": "config_file", + "config": "$XDG_CONFIG_HOME/retroarch/retroarch.cfg", + "parse_key": "system_directory" + }, { "os": "linux", "method": "config_file", "config": "$HOME/.config/retroarch/retroarch.cfg", "parse_key": "system_directory" }, + { + "os": "linux", + "method": "config_file", + "config": "$HOME/.retroarch.cfg", + "parse_key": "system_directory" + }, { "os": "darwin", "method": "config_file", diff --git a/platforms/_registry.yml b/platforms/_registry.yml index 891b01a95..a2da8d55c 100644 --- a/platforms/_registry.yml +++ b/platforms/_registry.yml @@ -219,10 +219,23 @@ platforms: method: config_file config: $HOME/snap/retroarch/current/.config/retroarch/retroarch.cfg parse_key: system_directory + # Native: $XDG_CONFIG_HOME/retroarch when the variable is set, else + # $HOME/.config/retroarch, then $HOME/.retroarch.cfg when that file is + # missing (file_path_special.c fill_pathname_application_data, + # configuration.c open_default_config_file). A value left at default + # is the system folder of the first of the two folders. + - os: linux + method: config_file + config: $XDG_CONFIG_HOME/retroarch/retroarch.cfg + parse_key: system_directory - os: linux method: config_file config: $HOME/.config/retroarch/retroarch.cfg parse_key: system_directory + - os: linux + method: config_file + config: $HOME/.retroarch.cfg + parse_key: system_directory # configuration.c reads /RetroArch/config, and a value # left at default is ~/Documents/RetroArch/system (platform_darwin.m). # Older builds kept the file one level up. diff --git a/tests/test_install.py b/tests/test_install.py index f36389a1e..dbef10dd9 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -2646,6 +2646,52 @@ class RetroArchWhereItReallyLives(unittest.TestCase): self.assertIn(("retroarch", support / "system"), found) +class LinuxRetroArchFollowsXdgConfigHome(unittest.TestCase): + """RetroArch on Linux keeps its config under $XDG_CONFIG_HOME/retroarch + when the variable is set, then falls back to ~/.retroarch.cfg + (file_path_special.c fill_pathname_application_data, configuration.c + open_default_config_file). Only ~/.config/retroarch was read: a home + with its own XDG layout detected nothing.""" + + def _env(self) -> tuple[Path, Path]: + home = Path(_tempdir(self)) + xdg = Path(_tempdir(self)) + patcher = unittest.mock.patch.dict(os.environ, {"HOME": str(home)}, clear=True) + patcher.start() + self.addCleanup(patcher.stop) + return home, xdg + + def test_the_config_under_xdg_config_home_is_read(self): + home, xdg = self._env() + (xdg / "retroarch").mkdir() + (xdg / "retroarch" / "retroarch.cfg").write_text('system_directory = "/srv/bios"\n') + # A config under ~/.config is not the one this RetroArch opens. + (home / ".config" / "retroarch").mkdir(parents=True) + (home / ".config" / "retroarch" / "retroarch.cfg").write_text( + 'system_directory = "/elsewhere"\n') + os.environ["XDG_CONFIG_HOME"] = str(xdg) + found = install.detect_platforms("linux") + self.assertIn(("retroarch", Path("/srv/bios")), found) + self.assertNotIn(("retroarch", Path("/elsewhere")), found) + self.assertEqual(install._os_default_dests("linux")["retroarch"], + xdg / "retroarch" / "system") + + def test_home_retroarch_cfg_is_the_fallback(self): + home, xdg = self._env() + os.environ["XDG_CONFIG_HOME"] = str(xdg) + (home / ".retroarch.cfg").write_text('system_directory = "default"\n') + found = install.detect_platforms("linux") + self.assertIn(("retroarch", xdg / "retroarch" / "system"), found) + + def test_without_the_variable_the_folder_is_under_dot_config(self): + home, _ = self._env() + (home / ".config" / "retroarch").mkdir(parents=True) + (home / ".config" / "retroarch" / "retroarch.cfg").write_text( + 'system_directory = "default"\n') + found = install.detect_platforms("linux") + self.assertIn(("retroarch", home / ".config" / "retroarch" / "system"), found) + + class NoPythonNamesThisSystemsCommand(unittest.TestCase): """Without Python the bootstrap said `sudo apt install python3` on every system: Termux has no sudo and installs with `pkg install python`, and