summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-10 14:32:16 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-10 14:32:16 -0400
commitd7115074060a3e9e682d511eae3c47a1c64042b8 (patch)
tree84362580dc7ab5d72be7d3ccf811d99c66d893d5 /src
parent684929fe850b744ef841475f88cf2a3189fbb986 (diff)
downloaddecky-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.ts34
-rw-r--r--src/components/ConfigurationTab.tsx12
-rw-r--r--src/components/Content.tsx7
-rw-r--r--src/components/GameConfigurationSelector.tsx2
-rw-r--r--src/components/NowPlayingTab.tsx35
-rw-r--r--src/components/SetupTab.tsx33
-rw-r--r--src/hooks/useGameConfiguration.ts43
-rw-r--r--src/hooks/useLsfgHooks.ts33
-rw-r--r--src/utils/steamLaunchOptions.ts73
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",
);