diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-09-10 14:32:16 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-09-10 14:32:16 -0400 |
| commit | d7115074060a3e9e682d511eae3c47a1c64042b8 (patch) | |
| tree | 84362580dc7ab5d72be7d3ccf811d99c66d893d5 /src | |
| parent | 684929fe850b744ef841475f88cf2a3189fbb986 (diff) | |
| download | decky-lsfg-vk-d7115074060a3e9e682d511eae3c47a1c64042b8.tar.gz decky-lsfg-vk-d7115074060a3e9e682d511eae3c47a1c64042b8.zip | |
refactor: wrap only direct Flatpak shortcut targets
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/lsfgApi.ts | 34 | ||||
| -rw-r--r-- | src/components/ConfigurationTab.tsx | 12 | ||||
| -rw-r--r-- | src/components/Content.tsx | 7 | ||||
| -rw-r--r-- | src/components/GameConfigurationSelector.tsx | 2 | ||||
| -rw-r--r-- | src/components/NowPlayingTab.tsx | 35 | ||||
| -rw-r--r-- | src/components/SetupTab.tsx | 33 | ||||
| -rw-r--r-- | src/hooks/useGameConfiguration.ts | 43 | ||||
| -rw-r--r-- | src/hooks/useLsfgHooks.ts | 33 | ||||
| -rw-r--r-- | src/utils/steamLaunchOptions.ts | 73 |
9 files changed, 149 insertions, 123 deletions
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index 567e1ae..cf09ed0 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -32,8 +32,7 @@ export interface SteamBranchStatus extends ApiResult { export type LsfgConfig = ConfigurationData; export type TargetTransport = | { kind: "host" } - | { kind: "flatpak"; flatpakAppId: string }; -export type FlatpakTargetSupportStatus = "ready" | "needs-runtime" | "unsupported" | "error"; + | { kind: "flatpak" }; export interface GameConfigEntry { appid: string; @@ -49,7 +48,6 @@ export interface InstalledGame { executable?: string; arguments?: string; startDir?: string; - flatpakSupport?: FlatpakTargetSupport; } export interface GlobalConfig { @@ -57,15 +55,6 @@ export interface GlobalConfig { no_fp16: boolean; } -export interface FlatpakTargetSupport extends ApiResult { - flatpak_app_id?: string; - runtime?: string | null; - runtime_branch?: string | null; - support_status: FlatpakTargetSupportStatus; - extension_installed: boolean; - installed_branches: string[]; -} - export interface WorkaroundState { dxvkFrameRate: number; disableGamescopeWsi: boolean; @@ -121,16 +110,18 @@ export interface DebugFileContentsResult extends ApiResult { export interface FlatpakExtensionStatus extends ApiResult { message: string; available: boolean; + ready: boolean; extension_id: string; supported_branches: string[]; installed_branches: string[]; -} - -export interface FlatpakExtensionToggleResult extends ApiResult { - message: string; - runtime_branch: string; - enabled: boolean; - installed: boolean; + filesystem_grants: Array<{ + path: string; + present: boolean; + read_only: boolean; + }>; + missing_filesystem_grants: string[]; + plugin_owned_branches?: string[]; + plugin_owned_filesystems?: string[]; } export const installLsfgVk = callable<[], InstallationResult>("install_lsfg_vk"); @@ -139,9 +130,8 @@ export const checkLsfgVkInstalled = callable<[], InstallationStatus>("check_lsfg export const getLosslessScalingBranchStatus = callable<[], SteamBranchStatus>("get_lossless_scaling_branch_status"); export const getConfigFileContent = callable<[], FileContentResult>("get_config_file_content"); export const getFlatpakSupportStatus = callable<[], FlatpakExtensionStatus>("get_flatpak_support_status"); -export const ensureFlatpakSupport = callable<[string], FlatpakTargetSupport>("ensure_flatpak_support"); -export const repairFlatpakSupport = callable<[string], FlatpakTargetSupport>("repair_flatpak_support"); -export const setFlatpakExtensionEnabled = callable<[string, boolean], FlatpakExtensionToggleResult>("set_flatpak_extension_enabled"); +export const ensureFlatpakSupport = callable<[], FlatpakExtensionStatus>("ensure_flatpak_support"); +export const repairFlatpakSupport = callable<[], FlatpakExtensionStatus>("repair_flatpak_support"); export const getGameConfigs = callable<[], GameConfigsResult>("get_game_configs"); export const getInstalledGames = callable<[], InstalledGamesResult>("get_installed_games"); export const updateGameConfig = callable<[string, string, LsfgConfig], GameConfigResult>("update_game_config"); diff --git a/src/components/ConfigurationTab.tsx b/src/components/ConfigurationTab.tsx index 4e2d93d..4031a9a 100644 --- a/src/components/ConfigurationTab.tsx +++ b/src/components/ConfigurationTab.tsx @@ -182,18 +182,6 @@ export function ConfigurationTab({ </PanelSectionRow> )} </PanelSection> - {selectedTarget?.configured && selectedTarget.transport.kind === "flatpak" && selectedTarget.flatpakSupport?.support_status !== "ready" && ( - <PanelSection> - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={() => void onRepair(selectedTarget.appid)} - > - Repair Flatpak support - </ButtonItem> - </PanelSectionRow> - </PanelSection> - )} {selectedTarget?.configured && ( <GameConfigurationControls config={config} diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 43720c0..d2093cd 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -59,11 +59,14 @@ export function Content() { installationStatus, losslessScalingInstalled, losslessScalingStatus, + flatpakStatus, steamBranchStatus, isInstalling, isUninstalling, + isRepairingFlatpak, install, uninstall, + repairFlatpak, } = useInstallation(reload); const [tab, setTab] = useState("Setup"); const [showDebugTab, setShowDebugTab] = usePersistentBoolean(DEBUG_TAB_VISIBILITY_KEY, true); @@ -114,11 +117,14 @@ export function Content() { installationStatus={installationStatus} losslessScalingInstalled={losslessScalingInstalled} losslessScalingStatus={losslessScalingStatus} + flatpakStatus={flatpakStatus} steamBranchStatus={steamBranchStatus} isInstalling={isInstalling} isUninstalling={isUninstalling} + isRepairingFlatpak={isRepairingFlatpak} onInstall={() => void install()} onUninstall={() => void uninstall()} + onRepairFlatpak={() => void repairFlatpak()} /> ); @@ -132,7 +138,6 @@ export function Content() { game={runningGame} config={config} onConfigChange={(field, value) => handleConfigChange(field, value)} - onRepair={repair} /> ), }] : []), diff --git a/src/components/GameConfigurationSelector.tsx b/src/components/GameConfigurationSelector.tsx index 92eacba..8e0e3be 100644 --- a/src/components/GameConfigurationSelector.tsx +++ b/src/components/GameConfigurationSelector.tsx @@ -137,7 +137,7 @@ export function GameConfigurationSelector({ showModal( <ConfirmModal strTitle="Enable all available games?" - strDescription="Create individual LSFG-VK profiles for every available game using the plugin defaults. Flatpak targets will be provisioned as needed." + strDescription="Create individual LSFG-VK profiles for every available game using the plugin defaults. Flatpak support is configured globally." strOKButtonText="Enable all" strCancelButtonText="Cancel" onOK={() => void onEnableAll()} diff --git a/src/components/NowPlayingTab.tsx b/src/components/NowPlayingTab.tsx index 57858db..9a69060 100644 --- a/src/components/NowPlayingTab.tsx +++ b/src/components/NowPlayingTab.tsx @@ -1,5 +1,4 @@ -import { ButtonItem, Field, Focusable, PanelSection, PanelSectionRow } from "@decky/ui"; -import { useState } from "react"; +import { Field, Focusable, PanelSection, PanelSectionRow } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; import { GameTarget } from "../hooks/useGameConfiguration"; import { GameConfigurationControls } from "./GameConfigurationControls"; @@ -11,7 +10,6 @@ interface Props { fieldName: keyof ConfigurationData, value: boolean | number | string | string[], ) => Promise<void>; - onRepair: (appid: string) => Promise<boolean>; } function targetDescription(game: GameTarget): string { @@ -23,23 +21,7 @@ export function NowPlayingTab({ game, config, onConfigChange, - onRepair, }: Props) { - const [busy, setBusy] = useState(false); - const supportNeedsRepair = - game.transport.kind === "flatpak" && - game.flatpakSupport?.support_status !== "ready"; - - const handleRepair = async () => { - if (busy) return; - setBusy(true); - try { - await onRepair(game.appid); - } finally { - setBusy(false); - } - }; - return ( <Focusable> <PanelSection title="Now Playing"> @@ -47,21 +29,6 @@ export function NowPlayingTab({ <Field label={game.name} description={targetDescription(game)} /> </PanelSectionRow> </PanelSection> - {supportNeedsRepair && ( - <PanelSection> - <PanelSectionRow> - <Field - label="Flatpak support needs repair" - description={game.flatpakSupport?.error || "The target runtime extension is not ready."} - /> - </PanelSectionRow> - <PanelSectionRow> - <ButtonItem layout="below" disabled={busy} onClick={() => void handleRepair()}> - {busy ? "Repairing..." : "Repair Flatpak support"} - </ButtonItem> - </PanelSectionRow> - </PanelSection> - )} <GameConfigurationControls config={config} onConfigChange={onConfigChange} diff --git a/src/components/SetupTab.tsx b/src/components/SetupTab.tsx index d769200..0095a7b 100644 --- a/src/components/SetupTab.tsx +++ b/src/components/SetupTab.tsx @@ -1,5 +1,5 @@ import { ButtonItem, Field, PanelSection, PanelSectionRow } from "@decky/ui"; -import { type SteamBranchStatus } from "../api/lsfgApi"; +import { type FlatpakExtensionStatus, type SteamBranchStatus } from "../api/lsfgApi"; import t from "../i18n/i18n"; interface SetupTabProps { @@ -7,11 +7,14 @@ interface SetupTabProps { installationStatus: string; losslessScalingInstalled: boolean; losslessScalingStatus: string; + flatpakStatus: FlatpakExtensionStatus | null; steamBranchStatus: SteamBranchStatus | null; isInstalling: boolean; isUninstalling: boolean; + isRepairingFlatpak: boolean; onInstall: () => void; onUninstall: () => void; + onRepairFlatpak: () => void; } export function SetupTab(props: SetupTabProps) { @@ -20,11 +23,14 @@ export function SetupTab(props: SetupTabProps) { installationStatus, losslessScalingInstalled, losslessScalingStatus, + flatpakStatus, steamBranchStatus, isInstalling, isUninstalling, + isRepairingFlatpak, onInstall, onUninstall, + onRepairFlatpak, } = props; const losslessScalingAppInstalled = losslessScalingInstalled || steamBranchStatus?.installed === true; const buttonLabel = isInstalling @@ -46,6 +52,31 @@ export function SetupTab(props: SetupTabProps) { <PanelSectionRow> <Field label="LSFG-VK" description={installationStatus} /> </PanelSectionRow> + <PanelSectionRow> + <Field + label="Flatpak support" + description={ + !flatpakStatus + ? "Status unavailable" + : !flatpakStatus.available + ? "Flatpak is not available" + : flatpakStatus.ready + ? "Configured for 23.08, 24.08, and 25.08" + : flatpakStatus.error || "Needs repair" + } + /> + </PanelSectionRow> + {flatpakStatus?.available && !flatpakStatus.ready && ( + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={onRepairFlatpak} + disabled={isRepairingFlatpak || isInstalling || isUninstalling} + > + {isRepairingFlatpak ? "Repairing Flatpak support..." : "Repair Flatpak support"} + </ButtonItem> + </PanelSectionRow> + )} {steamBranchStatus?.installed && ( <PanelSectionRow> <Field diff --git a/src/hooks/useGameConfiguration.ts b/src/hooks/useGameConfiguration.ts index 3260019..a3976a0 100644 --- a/src/hooks/useGameConfiguration.ts +++ b/src/hooks/useGameConfiguration.ts @@ -1,13 +1,22 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useQuickAccessVisible } from "@decky/api"; import { Router } from "@decky/ui"; -import { ensureFlatpakSupport, getGameConfigs, getInstalledGames, getWorkaroundState, removeWorkaroundState, repairFlatpakSupport, resetGameConfig, resetAllGameConfigs, setWorkaroundState, updateGameConfig, type GameConfigEntry, type GlobalConfig, type InstalledGame, type WorkaroundState } from "../api/lsfgApi"; +import { getGameConfigs, getInstalledGames, getWorkaroundState, removeWorkaroundState, resetGameConfig, resetAllGameConfigs, setWorkaroundState, updateGameConfig, type GameConfigEntry, type GlobalConfig, type InstalledGame, type WorkaroundState } from "../api/lsfgApi"; import { ConfigurationData, getDefaults } from "../config/configSchema"; import { getDefaultWrapperPath, installWrapperIntegration, removeWrapperIntegration } from "../utils/steamLaunchOptions"; import { showErrorToast } from "../utils/toastUtils"; export interface GameTarget extends InstalledGame { configured: boolean; } +function shortcutTransport(executable: unknown): InstalledGame["transport"] { + const target = typeof executable === "string" ? executable.trim() : ""; + return target === "/usr/bin/flatpak" + || target === '"~/.lsfg" "/usr/bin/flatpak"' + || target === "~/.lsfg /usr/bin/flatpak" + ? { kind: "flatpak" } + : { kind: "host" }; +} + async function getSteamShortcuts(): Promise<InstalledGame[]> { const apps = (globalThis as any).SteamClient?.Apps; if (typeof apps?.GetAllShortcuts !== "function") return []; @@ -19,11 +28,12 @@ async function getSteamShortcuts(): Promise<InstalledGame[]> { const appid = Number(shortcut?.appid); const name = shortcut?.data?.strAppName; if (!Number.isInteger(appid) || appid === 0 || typeof name !== "string" || !name) return []; + const executable = shortcut?.data?.strShortcutExe ?? shortcut?.data?.strExe ?? shortcut?.data?.exe; return [{ appid: String(appid >>> 0), name, nonSteam: true, - transport: { kind: "host" }, + transport: shortcutTransport(executable), }]; }); } catch { @@ -116,19 +126,6 @@ export function useGameConfiguration() { const template = useMemo(() => ({ ...getDefaults(), ...globalConfig }), [globalConfig]); const config = games.find((game) => game.appid === selectedAppId)?.config || template; - const ensureTargetFlatpakSupport = useCallback(async (target: GameTarget): Promise<boolean> => { - if (target.transport.kind !== "flatpak") return true; - const result = await ensureFlatpakSupport(target.transport.flatpakAppId); - if (!result.success || result.support_status !== "ready") { - showErrorToast( - "Flatpak support unavailable", - result.error || result.message || "The required Flatpak runtime extension is not ready", - ); - return false; - } - return true; - }, []); - const ensureTargetWorkarounds = useCallback(async (target: GameTarget): Promise<boolean> => { if (!installedGames.some((game) => game.appid === target.appid)) return true; const appId = Number(target.appid); @@ -228,19 +225,17 @@ export function useGameConfiguration() { const enable = useCallback(async (appid: string) => { const target = targets.find((item) => item.appid === appid); if (!target?.name) return false; - if (!(await ensureTargetFlatpakSupport(target))) return false; if (!(await ensureTargetWorkarounds(target))) return false; const result = await updateGameConfig(appid, target.name, template); if (result.success) await load(); else await removeTargetWorkarounds(target); return result.success; - }, [ensureTargetFlatpakSupport, ensureTargetWorkarounds, load, removeTargetWorkarounds, targets, template]); + }, [ensureTargetWorkarounds, load, removeTargetWorkarounds, targets, template]); const enableAll = useCallback(async (): Promise<void> => { const available = targets.filter((target) => !target.configured && target.name); if (available.length === 0) return; for (const target of available) { - if (!(await ensureTargetFlatpakSupport(target))) return; if (!(await ensureTargetWorkarounds(target))) return; const result = await updateGameConfig(target.appid, target.name, template); if (!result.success) { @@ -253,20 +248,10 @@ export function useGameConfiguration() { } } await load(); - }, [ensureTargetFlatpakSupport, ensureTargetWorkarounds, load, removeTargetWorkarounds, targets, template]); + }, [ensureTargetWorkarounds, load, removeTargetWorkarounds, targets, template]); const repair = useCallback(async (appid: string): Promise<boolean> => { const target = targets.find((item) => item.appid === appid); if (!target) return false; - if (target.transport.kind === "flatpak") { - const support = await repairFlatpakSupport(target.transport.flatpakAppId); - if (!support.success || support.support_status !== "ready") { - showErrorToast( - "Flatpak support unavailable", - support.error || support.message || "The required Flatpak runtime extension is not ready", - ); - return false; - } - } const success = await ensureTargetWorkarounds(target); if (success) await load(); return success; diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts index 9beb749..16f8962 100644 --- a/src/hooks/useLsfgHooks.ts +++ b/src/hooks/useLsfgHooks.ts @@ -1,9 +1,12 @@ import { useEffect, useState } from "react"; import { checkLsfgVkInstalled, + getFlatpakSupportStatus, getLosslessScalingBranchStatus, installLsfgVk, + repairFlatpakSupport, uninstallLsfgVk, + type FlatpakExtensionStatus, type SteamBranchStatus, } from "../api/lsfgApi"; import { @@ -11,6 +14,7 @@ import { showInstallSuccessToast, showUninstallErrorToast, showUninstallSuccessToast, + showErrorToast, } from "../utils/toastUtils"; export function useInstallation(reloadConfig?: () => Promise<void>) { @@ -18,12 +22,20 @@ export function useInstallation(reloadConfig?: () => Promise<void>) { const [installationStatus, setInstallationStatus] = useState(""); const [losslessScalingInstalled, setLosslessScalingInstalled] = useState(false); const [losslessScalingStatus, setLosslessScalingStatus] = useState(""); + const [flatpakStatus, setFlatpakStatus] = useState<FlatpakExtensionStatus | null>(null); const [steamBranchStatus, setSteamBranchStatus] = useState<SteamBranchStatus | null>(null); const [isInstalling, setIsInstalling] = useState(false); const [isUninstalling, setIsUninstalling] = useState(false); + const [isRepairingFlatpak, setIsRepairingFlatpak] = useState(false); const checkInstallation = async () => { try { + setFlatpakStatus(await getFlatpakSupportStatus()); + } catch (error) { + console.error("Error checking Flatpak support:", error); + setFlatpakStatus(null); + } + try { setSteamBranchStatus(await getLosslessScalingBranchStatus()); } catch (error) { console.error("Error checking Lossless Scaling Steam branch:", error); @@ -95,16 +107,37 @@ export function useInstallation(reloadConfig?: () => Promise<void>) { } }; + const repairFlatpak = async () => { + if (isRepairingFlatpak) return false; + setIsRepairingFlatpak(true); + try { + const result = await repairFlatpakSupport(); + setFlatpakStatus(result); + if (!result.success) { + showErrorToast("Flatpak support repair failed", result.error || result.message || "Unknown error"); + } + return result.success; + } catch (error) { + showErrorToast("Flatpak support repair failed", String(error)); + return false; + } finally { + setIsRepairingFlatpak(false); + } + }; + return { isInstalled, installationStatus, losslessScalingInstalled, losslessScalingStatus, + flatpakStatus, steamBranchStatus, isInstalling, isUninstalling, + isRepairingFlatpak, install, uninstall, + repairFlatpak, checkInstallation, }; } diff --git a/src/utils/steamLaunchOptions.ts b/src/utils/steamLaunchOptions.ts index 82cf116..bcd69d0 100644 --- a/src/utils/steamLaunchOptions.ts +++ b/src/utils/steamLaunchOptions.ts @@ -2,6 +2,7 @@ import type { TargetTransport } from "../api/lsfgApi"; const DEFAULT_WRAPPER_PATH = "~/.lsfg"; const COMMAND_TOKEN = "%command%"; +const DIRECT_FLATPAK_EXECUTABLE = "/usr/bin/flatpak"; export const LEGACY_WRAPPER_TOKENS = new Set([ "~/lsfg", @@ -166,10 +167,25 @@ const usesShortcutTarget = (nonSteam: boolean, transport: TargetTransport) => no function selectFlatpakExecutable(transport: TargetTransport, candidate?: string | null): string | undefined { if (transport.kind !== "flatpak") return undefined; - const value = candidate?.trim() ? decodeToken(candidate.trim()) : ""; - if (value === "flatpak") return "/usr/bin/flatpak"; - if (value === "/usr/bin/flatpak") return value; - return undefined; + const tokens = candidate?.trim() ? tokenize(candidate.trim()) : []; + return tokens.length === 1 && tokens[0].value === DIRECT_FLATPAK_EXECUTABLE + ? DIRECT_FLATPAK_EXECUTABLE + : undefined; +} + +function quoteTargetToken(value: string): string { + return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`; +} + +function flatpakTargetValue(wrapperPath: string, executable: string): string { + return `${quoteTargetToken(wrapperPath)} ${quoteTargetToken(executable)}`; +} + +function managedFlatpakExecutable(target: string, wrapperPath: string): string | undefined { + const tokens = tokenize(target); + return tokens.length === 2 && tokens[0].value === wrapperPath && tokens[1].value === DIRECT_FLATPAK_EXECUTABLE + ? DIRECT_FLATPAK_EXECUTABLE + : undefined; } export const normalizeLaunchOptions = (options: string) => serialize(tokenize(options)); @@ -272,7 +288,7 @@ export function isWrapperIntegrationInstalled( wrapperPath = DEFAULT_WRAPPER_PATH, ): boolean { return usesShortcutTarget(nonSteam, transport) - ? steam.target === wrapperPath + ? managedFlatpakExecutable(steam.target, wrapperPath) !== undefined : hasWrapperLaunchIntegration(steam.options, wrapperPath); } @@ -283,7 +299,11 @@ export function assertKnownShortcutTarget( wrapperPath: string, originalExecutable?: string | null, ): void { - if (usesShortcutTarget(nonSteam, transport) && steam.target === wrapperPath && !originalExecutable) { + if ( + usesShortcutTarget(nonSteam, transport) && + (isWrapperToken(steam.target, wrapperPath) || managedFlatpakExecutable(steam.target, wrapperPath) !== undefined) && + !originalExecutable + ) { throw new Error("Managed shortcut Target has no saved original executable"); } } @@ -386,7 +406,7 @@ export function installWrapperIntegration( let current = await readSteamLaunchOptions(appId, nonSteam); if (usesShortcutTarget(nonSteam, transport)) { if (!current.target) throw new Error("Steam shortcut Target is empty; refusing to replace it"); - if (current.target !== wrapperPath && isWrapperToken(current.target, wrapperPath)) { + if (isWrapperToken(current.target, wrapperPath) && !managedFlatpakExecutable(current.target, wrapperPath)) { throw new Error("The shortcut Target points to a legacy frame-generation wrapper; restore it first"); } const cleaned = cleanupPluginLaunchOptions(current.options, wrapperPath); @@ -399,19 +419,24 @@ export function installWrapperIntegration( ); } const savedOriginal = selectFlatpakExecutable(transport, originalExecutable); - if (current.target === wrapperPath) { - if (!savedOriginal) throw new Error("Managed shortcut Target has no saved original executable"); - return { snapshot: current, originalExecutable: savedOriginal, commandTokenAdded: false, changed: launchOptionsChanged }; - } - if (savedOriginal && selectFlatpakExecutable(transport, current.target) !== savedOriginal) { - throw new Error("Shortcut Target changed externally; refusing to replace it"); + const managedOriginal = managedFlatpakExecutable(current.target, wrapperPath); + if (managedOriginal) { + if (savedOriginal && savedOriginal !== managedOriginal) { + throw new Error("Shortcut Target changed externally; refusing to replace it"); + } + return { + snapshot: current, + originalExecutable: savedOriginal || managedOriginal, + commandTokenAdded: false, + changed: launchOptionsChanged, + }; } const currentOriginal = selectFlatpakExecutable(transport, current.target); - if (!currentOriginal || (originalExecutable && !savedOriginal)) { - throw new Error("Flatpak shortcut Target is not a supported executable"); + if (!currentOriginal || (savedOriginal && currentOriginal !== savedOriginal)) { + throw new Error("Flatpak shortcut Target must be exactly /usr/bin/flatpak"); } const value = await writeVerified( - appId, true, current.target, wrapperPath, + appId, true, current.target, flatpakTargetValue(wrapperPath, currentOriginal), (target) => writeTarget(appId, target), readTarget, "Steam did not accept the shortcut Target", ); @@ -442,6 +467,10 @@ export function removeWrapperIntegration( return queued(appId, nonSteam, async () => { let current = await readSteamLaunchOptions(appId, nonSteam); if (usesShortcutTarget(nonSteam, transport)) { + const managedOriginal = managedFlatpakExecutable(current.target, wrapperPath); + if (!managedOriginal && isWrapperToken(current.target, wrapperPath)) { + throw new Error("Original shortcut Target is unavailable; refusing to overwrite the current Target"); + } const cleaned = cleanupPluginLaunchOptions(current.options, wrapperPath); if (cleaned !== current.options) { current = await writeVerified( @@ -450,20 +479,18 @@ export function removeWrapperIntegration( "Steam did not clean shortcut launch options", ); } - if (current.target !== wrapperPath) { - if (isWrapperToken(current.target, wrapperPath)) { - throw new Error("Original shortcut Target is unavailable; refusing to overwrite the current Target"); - } + if (!managedOriginal) { if (originalExecutable && selectFlatpakExecutable(transport, current.target) !== selectFlatpakExecutable(transport, originalExecutable)) { throw new Error("Shortcut Target changed externally; refusing to restore it"); } return current; } - if (!originalExecutable || isWrapperToken(originalExecutable, wrapperPath)) { - throw new Error("Original shortcut Target is unavailable; refusing to overwrite the current Target"); + const restoreTarget = selectFlatpakExecutable(transport, originalExecutable); + if (originalExecutable && restoreTarget !== managedOriginal) { + throw new Error("Shortcut Target changed externally; refusing to restore it"); } return writeVerified( - appId, true, wrapperPath, originalExecutable, + appId, true, current.target, restoreTarget || managedOriginal, (target) => writeTarget(appId, target), readTarget, "Steam did not restore the shortcut Target", ); |
