diff --git a/src/main/core/paths.ts b/src/main/core/paths.ts index c1d67a7ec..a630e9e57 100644 --- a/src/main/core/paths.ts +++ b/src/main/core/paths.ts @@ -96,6 +96,37 @@ export function windowsStorageRoots( }; } +export interface LinuxStorageHomes { + readonly config: string; + readonly data: string; + readonly cache: string; + readonly state: string; +} + +/** Resolve the Flatpak layout beneath the sandbox-provided XDG homes. */ +export function linuxStorageRoots( + homes: LinuxStorageHomes, +): ApplicationStorageRoots { + for (const [name, root] of Object.entries(homes)) { + if (!path.posix.isAbsolute(root)) { + throw new Error(`Linux ${name} home must be absolute`); + } + } + + const config = path.posix.join(homes.config, "gwonmac"); + const data = path.posix.join(homes.data, "gwonmac"); + const cache = path.posix.join(homes.cache, "gwonmac"); + const state = path.posix.join(homes.state, "gwonmac"); + return { + config, + data, + cache, + state, + logs: path.posix.join(state, "logs"), + sessions: path.posix.join(data, "sessions"), + }; +} + export function gamePaths(storage: ApplicationStorageRoots): GamePaths { const game = path.join(storage.cache, "game"); const artifacts = path.join(game, "artifacts"); diff --git a/src/main/main.ts b/src/main/main.ts index 5a4a8fce7..1547802eb 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -81,6 +81,7 @@ import { sweepOrphanDirectories } from "./core/atomic-file.js"; import { colocatedStorageRoots, documentDirectories, + linuxStorageRoots, windowsStorageRoots, } from "./core/paths.js"; import { gamePaths } from "./paths.js"; @@ -200,12 +201,23 @@ const explicitUserData = app.commandLine.hasSwitch("user-data-dir"); if (!explicitUserData && process.platform === "darwin") { app.setPath("userData", path.join(app.getPath("appData"), "Guild Wars")); } +const linuxFlatpak = process.platform === "linux" + && process.env.FLATPAK_ID === DISTRIBUTION_CHANNEL_CONFIG.release.bundleId; const applicationStorageRoots = explicitUserData ? colocatedStorageRoots(app.getPath("userData")) : windowsNativeHost ? windowsStorageRoots(windowsNativeHost.localAppData()) + : linuxFlatpak + ? linuxStorageRoots({ + config: process.env.XDG_CONFIG_HOME ?? app.getPath("appData"), + data: process.env.XDG_DATA_HOME ?? app.getPath("appData"), + cache: + process.env.XDG_CACHE_HOME ?? path.join(app.getPath("home"), ".cache"), + state: process.env.XDG_STATE_HOME + ?? path.join(process.env.XDG_DATA_HOME ?? app.getPath("appData"), "state"), + }) : colocatedStorageRoots(app.getPath("userData")); -if (!explicitUserData && process.platform === "win32") { +if (!explicitUserData && (process.platform === "win32" || linuxFlatpak)) { app.setPath("userData", applicationStorageRoots.sessions); app.setPath("sessionData", applicationStorageRoots.sessions); } diff --git a/tests/unit/paths.test.ts b/tests/unit/paths.test.ts index b43c28045..087365e9a 100644 --- a/tests/unit/paths.test.ts +++ b/tests/unit/paths.test.ts @@ -7,6 +7,7 @@ import { diagnosticFramesPath, documentDirectories, gamePaths, + linuxStorageRoots, multiProfilePaths, nativeExecutableName, unpackedPath, @@ -223,4 +224,29 @@ describe("resolved profile paths", () => { "C:\\Users\\Player\\AppData\\Local\\Guild Wars Reforged\\data\\sessions", }); }); + + it("pins the Flatpak layout beneath sandbox-provided XDG homes", () => { + assert.deepEqual(linuxStorageRoots({ + config: "/var/config", + data: "/var/data", + cache: "/var/cache", + state: "/var/state", + }), { + config: "/var/config/gwonmac", + data: "/var/data/gwonmac", + cache: "/var/cache/gwonmac", + state: "/var/state/gwonmac", + logs: "/var/state/gwonmac/logs", + sessions: "/var/data/gwonmac/sessions", + }); + }); + + it("refuses relative Linux XDG homes", () => { + assert.throws(() => linuxStorageRoots({ + config: "config", + data: "/var/data", + cache: "/var/cache", + state: "/var/state", + }), /Linux config home must be absolute/); + }); });