From 8352ee74e8a437c1f4a4dadb75d63049f45b164b Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Tue, 8 Sep 2026 16:25:11 -0400 Subject: add back workarounds sections, scope out of now playing --- src/hooks/usePerAppWorkarounds.ts | 173 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/hooks/usePerAppWorkarounds.ts (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts new file mode 100644 index 0000000..a937780 --- /dev/null +++ b/src/hooks/usePerAppWorkarounds.ts @@ -0,0 +1,173 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + applyWorkaroundChange, + parseWorkaroundOptions, + readSteamLaunchOptions, + subscribeSteamLaunchOptions, + updateSteamLaunchOptions, + type ParsedWorkaroundOptions, + type SteamLaunchOptionsSnapshot, + type WorkaroundField, +} from "../utils/steamLaunchOptions"; +import { showErrorToast } from "../utils/toastUtils"; + +export type WorkaroundLoadStatus = "loading" | "ready" | "error"; + +const SLIDER_DEBOUNCE_MS = 250; + +interface PendingSliderUpdate { + timer: number; + value: number; + waiters: Array<(success: boolean) => void>; +} + +interface WorkaroundSnapshot { + steam: SteamLaunchOptionsSnapshot; + parsed: ParsedWorkaroundOptions; +} + +interface PerAppWorkarounds { + status: WorkaroundLoadStatus; + snapshot: WorkaroundSnapshot | null; + refresh: () => Promise; + update: (field: WorkaroundField, value: boolean | number) => Promise; + error: string | null; +} + +function asError(error: unknown): Error { + return error instanceof Error ? error : new Error(String(error)); +} + +function makeSnapshot(steam: SteamLaunchOptionsSnapshot): WorkaroundSnapshot { + return { steam, parsed: parseWorkaroundOptions(steam.options) }; +} + +export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWorkarounds { + const [status, setStatus] = useState("loading"); + const [snapshot, setSnapshot] = useState(null); + const [error, setError] = useState(null); + const pendingSliderUpdate = useRef(null); + const numericAppId = Number(appId); + + const applySnapshot = useCallback((steam: SteamLaunchOptionsSnapshot) => { + setSnapshot(makeSnapshot(steam)); + setStatus("ready"); + setError(null); + }, []); + + const refresh = useCallback(async () => { + setStatus("loading"); + setError(null); + try { + applySnapshot(await readSteamLaunchOptions(numericAppId, nonSteam)); + } catch (refreshError) { + const nextError = asError(refreshError); + setStatus("error"); + setError(nextError.message); + } + }, [applySnapshot, nonSteam, numericAppId]); + + useEffect(() => { + let active = true; + setStatus("loading"); + setSnapshot(null); + setError(null); + + const handleSnapshot = (nextSnapshot: SteamLaunchOptionsSnapshot) => { + if (!active) return; + applySnapshot(nextSnapshot); + }; + const handleSubscriptionError = (subscriptionError: Error) => { + if (!active) return; + setStatus("error"); + setError(subscriptionError.message); + }; + + let unsubscribe = () => {}; + try { + unsubscribe = subscribeSteamLaunchOptions( + numericAppId, + nonSteam, + handleSnapshot, + handleSubscriptionError, + ); + } catch (subscriptionError) { + handleSubscriptionError(asError(subscriptionError)); + } + + void readSteamLaunchOptions(numericAppId, nonSteam) + .then((nextSnapshot) => { + if (active) applySnapshot(nextSnapshot); + }) + .catch((readError) => { + if (active) handleSubscriptionError(asError(readError)); + }); + + return () => { + active = false; + unsubscribe(); + }; + }, [applySnapshot, nonSteam, numericAppId]); + + const persistUpdate = useCallback(async (field: WorkaroundField, value: boolean | number): Promise => { + setError(null); + try { + const nextSnapshot = await updateSteamLaunchOptions( + numericAppId, + nonSteam, + (options) => applyWorkaroundChange(options, field, value), + ); + applySnapshot(nextSnapshot); + return true; + } catch (updateError) { + const nextError = asError(updateError); + setStatus("error"); + setError(nextError.message); + showErrorToast("Workaround update failed", nextError.message); + return false; + } + }, [applySnapshot, nonSteam, numericAppId]); + + const flushSliderUpdate = useCallback(async (): Promise => { + const pending = pendingSliderUpdate.current; + if (!pending) return true; + + pendingSliderUpdate.current = null; + window.clearTimeout(pending.timer); + const success = await persistUpdate("dxvkFrameRate", pending.value); + pending.waiters.forEach((resolve) => resolve(success)); + return success; + }, [persistUpdate]); + + const update = useCallback(async (field: WorkaroundField, value: boolean | number): Promise => { + if (field === "dxvkFrameRate") { + setError(null); + return new Promise((resolve) => { + const pending = pendingSliderUpdate.current ?? { timer: 0, value: 0, waiters: [] }; + window.clearTimeout(pending.timer); + pending.value = Number(value); + pending.waiters.push(resolve); + pending.timer = window.setTimeout(() => { + void flushSliderUpdate(); + }, SLIDER_DEBOUNCE_MS); + pendingSliderUpdate.current = pending; + }); + } + + const sliderSuccess = await flushSliderUpdate(); + if (!sliderSuccess) return false; + return persistUpdate(field, value); + }, [flushSliderUpdate, persistUpdate]); + + useEffect(() => { + return () => { + const pending = pendingSliderUpdate.current; + if (!pending) return; + window.clearTimeout(pending.timer); + pendingSliderUpdate.current = null; + pending.waiters.forEach((resolve) => resolve(false)); + }; + }, [numericAppId, nonSteam]); + + return useMemo(() => ({ status, snapshot, refresh, update, error }), [error, refresh, snapshot, status, update]); +} -- cgit v1.2.3 From 1b932fd69c3dba925e0cbf027e05508b2daf5e8c Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 01:01:42 -0400 Subject: add back launcher script and correct pathing --- src/hooks/usePerAppWorkarounds.ts | 234 +++++++++++++++++++++++++++++--------- 1 file changed, 178 insertions(+), 56 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index a937780..9e283db 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -1,29 +1,50 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { - applyWorkaroundChange, - parseWorkaroundOptions, + getWorkaroundState, + removeWorkaroundState, + setWorkaroundState, + type WorkaroundState, +} from "../api/lsfgApi"; +import { + getDefaultWrapperPath, + hasWrapperLaunchIntegration, + installWrapperIntegration, + isLegacyWrapperToken, readSteamLaunchOptions, + removeWrapperIntegration, subscribeSteamLaunchOptions, - updateSteamLaunchOptions, - type ParsedWorkaroundOptions, type SteamLaunchOptionsSnapshot, - type WorkaroundField, } from "../utils/steamLaunchOptions"; import { showErrorToast } from "../utils/toastUtils"; +export type WorkaroundField = keyof WorkaroundState; export type WorkaroundLoadStatus = "loading" | "ready" | "error"; const SLIDER_DEBOUNCE_MS = 250; +const DEFAULT_WORKAROUND_STATE: WorkaroundState = { + dxvkFrameRate: 0, + disableGamescopeWsi: true, + disableHdr: true, + disableSteamdeckMode: false, + disableVkbasalt: false, + enableZink: false, +}; + interface PendingSliderUpdate { timer: number; value: number; waiters: Array<(success: boolean) => void>; } -interface WorkaroundSnapshot { +export interface WorkaroundSnapshot { steam: SteamLaunchOptionsSnapshot; - parsed: ParsedWorkaroundOptions; + state: WorkaroundState; + wrapperPath: string; + wrapperOwned: boolean; + integrationInstalled: boolean; + commandTokenAdded: boolean; + shortcutExe?: string | null; } interface PerAppWorkarounds { @@ -38,8 +59,84 @@ function asError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); } -function makeSnapshot(steam: SteamLaunchOptionsSnapshot): WorkaroundSnapshot { - return { steam, parsed: parseWorkaroundOptions(steam.options) }; +function integrationIsInstalled( + steam: SteamLaunchOptionsSnapshot, + nonSteam: boolean, + wrapperPath: string, +): boolean { + return nonSteam ? steam.target === wrapperPath : hasWrapperLaunchIntegration(steam.options, wrapperPath); +} + +function makeSnapshot( + steam: SteamLaunchOptionsSnapshot, + result: Awaited>, + nonSteam: boolean, +): WorkaroundSnapshot { + if (!result.state) throw new Error("Workaround state is not initialized for this profile"); + const wrapperPath = result.wrapper_path || getDefaultWrapperPath(); + if (nonSteam && steam.target === wrapperPath && !result.shortcut_exe) { + throw new Error("Managed shortcut Target has no saved original executable"); + } + return { + steam, + state: result.state, + wrapperPath, + wrapperOwned: result.wrapper_owned === true, + integrationInstalled: integrationIsInstalled(steam, nonSteam, wrapperPath), + commandTokenAdded: result.command_token_added === true, + shortcutExe: result.shortcut_exe, + }; +} + +async function adoptWorkaroundState( + appId: string, + nonSteam: boolean, + steam: SteamLaunchOptionsSnapshot, + wrapperPath: string, +): Promise { + if (nonSteam && (!steam.target || steam.target === wrapperPath || isLegacyWrapperToken(steam.target))) { + throw new Error("Shortcut Target is a wrapper but its original Target is unknown"); + } + const originalExecutable = nonSteam ? steam.target : null; + const initial = await setWorkaroundState(appId, DEFAULT_WORKAROUND_STATE, originalExecutable, false); + if (!initial.success) throw new Error(initial.error || "Could not create workaround state"); + let integration: Awaited> | null = null; + try { + integration = await installWrapperIntegration( + Number(appId), + nonSteam, + wrapperPath, + ); + const finalized = await setWorkaroundState( + appId, + DEFAULT_WORKAROUND_STATE, + nonSteam ? (integration.originalExecutable || originalExecutable) : null, + integration.commandTokenAdded, + ); + if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); + return makeSnapshot(integration.snapshot, finalized, nonSteam); + } catch (error) { + let rollbackSucceeded = true; + if (integration) { + try { + await removeWrapperIntegration( + Number(appId), + nonSteam, + wrapperPath, + nonSteam ? (integration?.originalExecutable || originalExecutable || undefined) : undefined, + integration?.commandTokenAdded ?? false, + ); + } catch { + // Leave the owned integration in place rather than guessing at cleanup. + rollbackSucceeded = false; + } + } + if (rollbackSucceeded) { + const removed = await removeWorkaroundState(appId); + if (!removed.success) throw new Error(removed.error || "Could not roll back workaround state"); + } + throw error; + } } export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWorkarounds { @@ -49,8 +146,25 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo const pendingSliderUpdate = useRef(null); const numericAppId = Number(appId); - const applySnapshot = useCallback((steam: SteamLaunchOptionsSnapshot) => { - setSnapshot(makeSnapshot(steam)); + const loadSnapshot = useCallback(async () => { + const [result, steam] = await Promise.all([ + getWorkaroundState(appId), + readSteamLaunchOptions(numericAppId, nonSteam), + ]); + if (!result.success) throw new Error(result.error || "Could not read workaround state"); + if (!result.state) { + return adoptWorkaroundState( + appId, + nonSteam, + steam, + result.wrapper_path || getDefaultWrapperPath(), + ); + } + return makeSnapshot(steam, result, nonSteam); + }, [appId, nonSteam, numericAppId]); + + const applySnapshot = useCallback((next: WorkaroundSnapshot) => { + setSnapshot(next); setStatus("ready"); setError(null); }, []); @@ -59,65 +173,79 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo setStatus("loading"); setError(null); try { - applySnapshot(await readSteamLaunchOptions(numericAppId, nonSteam)); + applySnapshot(await loadSnapshot()); } catch (refreshError) { const nextError = asError(refreshError); setStatus("error"); setError(nextError.message); } - }, [applySnapshot, nonSteam, numericAppId]); + }, [applySnapshot, loadSnapshot]); useEffect(() => { let active = true; setStatus("loading"); setSnapshot(null); setError(null); - - const handleSnapshot = (nextSnapshot: SteamLaunchOptionsSnapshot) => { - if (!active) return; - applySnapshot(nextSnapshot); - }; - const handleSubscriptionError = (subscriptionError: Error) => { - if (!active) return; - setStatus("error"); - setError(subscriptionError.message); - }; - let unsubscribe = () => {}; try { unsubscribe = subscribeSteamLaunchOptions( numericAppId, nonSteam, - handleSnapshot, - handleSubscriptionError, + (steam) => { + if (!active) return; + setSnapshot((current) => current ? { + ...current, + steam, + integrationInstalled: integrationIsInstalled(steam, nonSteam, current.wrapperPath), + } : current); + }, + (subscriptionError) => { + if (!active) return; + setStatus("error"); + setError(subscriptionError.message); + }, ); } catch (subscriptionError) { - handleSubscriptionError(asError(subscriptionError)); + if (active) { + setStatus("error"); + setError(asError(subscriptionError).message); + } } - - void readSteamLaunchOptions(numericAppId, nonSteam) - .then((nextSnapshot) => { - if (active) applySnapshot(nextSnapshot); - }) + void loadSnapshot() + .then((next) => { if (active) applySnapshot(next); }) .catch((readError) => { - if (active) handleSubscriptionError(asError(readError)); + if (active) { + setStatus("error"); + setError(asError(readError).message); + } }); - return () => { active = false; unsubscribe(); }; - }, [applySnapshot, nonSteam, numericAppId]); + }, [applySnapshot, loadSnapshot, nonSteam, numericAppId]); const persistUpdate = useCallback(async (field: WorkaroundField, value: boolean | number): Promise => { + const current = snapshot; + if (!current) return false; setError(null); + const nextState = { ...current.state, [field]: value } as WorkaroundState; try { - const nextSnapshot = await updateSteamLaunchOptions( - numericAppId, - nonSteam, - (options) => applyWorkaroundChange(options, field, value), + const result = await setWorkaroundState( + appId, + nextState, + current.shortcutExe ?? null, + current.commandTokenAdded, ); - applySnapshot(nextSnapshot); + if (!result.success || !result.state) throw new Error(result.error || "Could not save workaround state"); + applySnapshot({ + ...current, + state: result.state, + wrapperPath: result.wrapper_path || current.wrapperPath, + wrapperOwned: result.wrapper_owned === true, + shortcutExe: result.shortcut_exe, + commandTokenAdded: result.command_token_added === true, + }); return true; } catch (updateError) { const nextError = asError(updateError); @@ -126,14 +254,13 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo showErrorToast("Workaround update failed", nextError.message); return false; } - }, [applySnapshot, nonSteam, numericAppId]); + }, [appId, applySnapshot, snapshot]); const flushSliderUpdate = useCallback(async (): Promise => { const pending = pendingSliderUpdate.current; if (!pending) return true; - pendingSliderUpdate.current = null; - window.clearTimeout(pending.timer); + clearTimeout(pending.timer); const success = await persistUpdate("dxvkFrameRate", pending.value); pending.waiters.forEach((resolve) => resolve(success)); return success; @@ -143,30 +270,25 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo if (field === "dxvkFrameRate") { setError(null); return new Promise((resolve) => { - const pending = pendingSliderUpdate.current ?? { timer: 0, value: 0, waiters: [] }; + const pending = pendingSliderUpdate.current || { timer: 0, value: 0, waiters: [] }; window.clearTimeout(pending.timer); pending.value = Number(value); pending.waiters.push(resolve); - pending.timer = window.setTimeout(() => { - void flushSliderUpdate(); - }, SLIDER_DEBOUNCE_MS); + pending.timer = window.setTimeout(() => { void flushSliderUpdate(); }, SLIDER_DEBOUNCE_MS); pendingSliderUpdate.current = pending; }); } - const sliderSuccess = await flushSliderUpdate(); if (!sliderSuccess) return false; return persistUpdate(field, value); }, [flushSliderUpdate, persistUpdate]); - useEffect(() => { - return () => { - const pending = pendingSliderUpdate.current; - if (!pending) return; - window.clearTimeout(pending.timer); - pendingSliderUpdate.current = null; - pending.waiters.forEach((resolve) => resolve(false)); - }; + useEffect(() => () => { + const pending = pendingSliderUpdate.current; + if (!pending) return; + window.clearTimeout(pending.timer); + pendingSliderUpdate.current = null; + pending.waiters.forEach((resolve) => resolve(false)); }, [numericAppId, nonSteam]); return useMemo(() => ({ status, snapshot, refresh, update, error }), [error, refresh, snapshot, status, update]); -- cgit v1.2.3 From cc1e6f47dd9838b066822162a607d2859c043aff Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 19:16:30 -0400 Subject: refactor: unify flatpak targets with steam profiles --- src/hooks/usePerAppWorkarounds.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index 9e283db..c7413b0 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -3,6 +3,7 @@ import { getWorkaroundState, removeWorkaroundState, setWorkaroundState, + type TargetTransport, type WorkaroundState, } from "../api/lsfgApi"; import { @@ -45,6 +46,7 @@ export interface WorkaroundSnapshot { integrationInstalled: boolean; commandTokenAdded: boolean; shortcutExe?: string | null; + transport: TargetTransport; } interface PerAppWorkarounds { @@ -85,12 +87,14 @@ function makeSnapshot( integrationInstalled: integrationIsInstalled(steam, nonSteam, wrapperPath), commandTokenAdded: result.command_token_added === true, shortcutExe: result.shortcut_exe, + transport: result.transport || { kind: "host" }, }; } async function adoptWorkaroundState( appId: string, nonSteam: boolean, + transport: TargetTransport, steam: SteamLaunchOptionsSnapshot, wrapperPath: string, ): Promise { @@ -98,7 +102,13 @@ async function adoptWorkaroundState( throw new Error("Shortcut Target is a wrapper but its original Target is unknown"); } const originalExecutable = nonSteam ? steam.target : null; - const initial = await setWorkaroundState(appId, DEFAULT_WORKAROUND_STATE, originalExecutable, false); + const initial = await setWorkaroundState( + appId, + DEFAULT_WORKAROUND_STATE, + originalExecutable, + false, + transport, + ); if (!initial.success) throw new Error(initial.error || "Could not create workaround state"); let integration: Awaited> | null = null; try { @@ -112,6 +122,7 @@ async function adoptWorkaroundState( DEFAULT_WORKAROUND_STATE, nonSteam ? (integration.originalExecutable || originalExecutable) : null, integration.commandTokenAdded, + transport, ); if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); return makeSnapshot(integration.snapshot, finalized, nonSteam); @@ -139,7 +150,11 @@ async function adoptWorkaroundState( } } -export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWorkarounds { +export function usePerAppWorkarounds( + appId: string, + nonSteam: boolean, + transport: TargetTransport = { kind: "host" }, +): PerAppWorkarounds { const [status, setStatus] = useState("loading"); const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(null); @@ -156,12 +171,13 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo return adoptWorkaroundState( appId, nonSteam, + transport, steam, result.wrapper_path || getDefaultWrapperPath(), ); } return makeSnapshot(steam, result, nonSteam); - }, [appId, nonSteam, numericAppId]); + }, [appId, nonSteam, numericAppId, transport]); const applySnapshot = useCallback((next: WorkaroundSnapshot) => { setSnapshot(next); @@ -236,6 +252,7 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo nextState, current.shortcutExe ?? null, current.commandTokenAdded, + current.transport, ); if (!result.success || !result.state) throw new Error(result.error || "Could not save workaround state"); applySnapshot({ @@ -245,6 +262,7 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo wrapperOwned: result.wrapper_owned === true, shortcutExe: result.shortcut_exe, commandTokenAdded: result.command_token_added === true, + transport: result.transport || current.transport, }); return true; } catch (updateError) { -- cgit v1.2.3 From fb4d053213bdbda271a54b517a11a89c4780f80a Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 20:45:20 -0400 Subject: fix: restore profile and Flatpak controls --- src/hooks/usePerAppWorkarounds.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index c7413b0..e9e44e1 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -61,6 +61,18 @@ function asError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); } +function selectShortcutExecutable( + transport: TargetTransport, + ...candidates: Array +): string | undefined { + const absolute = candidates + .map((candidate) => candidate?.trim()) + .find((candidate) => candidate && candidate.startsWith("/")); + if (absolute) return absolute; + if (transport.kind === "flatpak") return "/usr/bin/flatpak"; + return candidates.map((candidate) => candidate?.trim()).find(Boolean); +} + function integrationIsInstalled( steam: SteamLaunchOptionsSnapshot, nonSteam: boolean, @@ -101,7 +113,9 @@ async function adoptWorkaroundState( if (nonSteam && (!steam.target || steam.target === wrapperPath || isLegacyWrapperToken(steam.target))) { throw new Error("Shortcut Target is a wrapper but its original Target is unknown"); } - const originalExecutable = nonSteam ? steam.target : null; + const originalExecutable = nonSteam + ? selectShortcutExecutable(transport, steam.target) + : null; const initial = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, @@ -120,7 +134,9 @@ async function adoptWorkaroundState( const finalized = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, - nonSteam ? (integration.originalExecutable || originalExecutable) : null, + nonSteam + ? (selectShortcutExecutable(transport, integration.originalExecutable, originalExecutable) || null) + : null, integration.commandTokenAdded, transport, ); @@ -134,7 +150,9 @@ async function adoptWorkaroundState( Number(appId), nonSteam, wrapperPath, - nonSteam ? (integration?.originalExecutable || originalExecutable || undefined) : undefined, + nonSteam + ? (selectShortcutExecutable(transport, integration?.originalExecutable, originalExecutable) || undefined) + : undefined, integration?.commandTokenAdded ?? false, ); } catch { -- cgit v1.2.3 From b197e25b45d53c6c7175a45dd0b14642f2aab198 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 11:51:22 -0400 Subject: fixes for appimage and flatpak --- src/hooks/usePerAppWorkarounds.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index e9e44e1..ebb12e2 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -73,22 +73,31 @@ function selectShortcutExecutable( return candidates.map((candidate) => candidate?.trim()).find(Boolean); } +function usesShortcutTarget(nonSteam: boolean, transport: TargetTransport): boolean { + return nonSteam && transport.kind === "flatpak"; +} + function integrationIsInstalled( steam: SteamLaunchOptionsSnapshot, nonSteam: boolean, + transport: TargetTransport, wrapperPath: string, ): boolean { - return nonSteam ? steam.target === wrapperPath : hasWrapperLaunchIntegration(steam.options, wrapperPath); + return usesShortcutTarget(nonSteam, transport) + ? steam.target === wrapperPath + : hasWrapperLaunchIntegration(steam.options, wrapperPath); } function makeSnapshot( steam: SteamLaunchOptionsSnapshot, result: Awaited>, nonSteam: boolean, + transport: TargetTransport, ): WorkaroundSnapshot { if (!result.state) throw new Error("Workaround state is not initialized for this profile"); const wrapperPath = result.wrapper_path || getDefaultWrapperPath(); - if (nonSteam && steam.target === wrapperPath && !result.shortcut_exe) { + const selectedTransport = result.transport || transport; + if (usesShortcutTarget(nonSteam, selectedTransport) && steam.target === wrapperPath && !result.shortcut_exe) { throw new Error("Managed shortcut Target has no saved original executable"); } return { @@ -96,10 +105,10 @@ function makeSnapshot( state: result.state, wrapperPath, wrapperOwned: result.wrapper_owned === true, - integrationInstalled: integrationIsInstalled(steam, nonSteam, wrapperPath), + integrationInstalled: integrationIsInstalled(steam, nonSteam, selectedTransport, wrapperPath), commandTokenAdded: result.command_token_added === true, shortcutExe: result.shortcut_exe, - transport: result.transport || { kind: "host" }, + transport: selectedTransport, }; } @@ -110,10 +119,11 @@ async function adoptWorkaroundState( steam: SteamLaunchOptionsSnapshot, wrapperPath: string, ): Promise { - if (nonSteam && (!steam.target || steam.target === wrapperPath || isLegacyWrapperToken(steam.target))) { + const shortcutTarget = usesShortcutTarget(nonSteam, transport); + if (shortcutTarget && (!steam.target || steam.target === wrapperPath || isLegacyWrapperToken(steam.target))) { throw new Error("Shortcut Target is a wrapper but its original Target is unknown"); } - const originalExecutable = nonSteam + const originalExecutable = shortcutTarget ? selectShortcutExecutable(transport, steam.target) : null; const initial = await setWorkaroundState( @@ -130,18 +140,20 @@ async function adoptWorkaroundState( Number(appId), nonSteam, wrapperPath, + false, + transport.kind, ); const finalized = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, - nonSteam + shortcutTarget ? (selectShortcutExecutable(transport, integration.originalExecutable, originalExecutable) || null) : null, integration.commandTokenAdded, transport, ); if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); - return makeSnapshot(integration.snapshot, finalized, nonSteam); + return makeSnapshot(integration.snapshot, finalized, nonSteam, transport); } catch (error) { let rollbackSucceeded = true; if (integration) { @@ -150,10 +162,11 @@ async function adoptWorkaroundState( Number(appId), nonSteam, wrapperPath, - nonSteam + shortcutTarget ? (selectShortcutExecutable(transport, integration?.originalExecutable, originalExecutable) || undefined) : undefined, integration?.commandTokenAdded ?? false, + transport.kind, ); } catch { // Leave the owned integration in place rather than guessing at cleanup. @@ -194,7 +207,7 @@ export function usePerAppWorkarounds( result.wrapper_path || getDefaultWrapperPath(), ); } - return makeSnapshot(steam, result, nonSteam); + return makeSnapshot(steam, result, nonSteam, transport); }, [appId, nonSteam, numericAppId, transport]); const applySnapshot = useCallback((next: WorkaroundSnapshot) => { @@ -230,7 +243,7 @@ export function usePerAppWorkarounds( setSnapshot((current) => current ? { ...current, steam, - integrationInstalled: integrationIsInstalled(steam, nonSteam, current.wrapperPath), + integrationInstalled: integrationIsInstalled(steam, nonSteam, current.transport, current.wrapperPath), } : current); }, (subscriptionError) => { -- cgit v1.2.3 From 670f36e8cc75da9c8b1b174c24e722657bbf2a56 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 12:15:05 -0400 Subject: cleanup flatpak handles --- src/hooks/usePerAppWorkarounds.ts | 83 +++++++++------------------------------ 1 file changed, 18 insertions(+), 65 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index ebb12e2..29e1181 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -7,10 +7,10 @@ import { type WorkaroundState, } from "../api/lsfgApi"; import { + assertKnownShortcutTarget, getDefaultWrapperPath, - hasWrapperLaunchIntegration, installWrapperIntegration, - isLegacyWrapperToken, + isWrapperIntegrationInstalled, readSteamLaunchOptions, removeWrapperIntegration, subscribeSteamLaunchOptions, @@ -61,33 +61,6 @@ function asError(error: unknown): Error { return error instanceof Error ? error : new Error(String(error)); } -function selectShortcutExecutable( - transport: TargetTransport, - ...candidates: Array -): string | undefined { - const absolute = candidates - .map((candidate) => candidate?.trim()) - .find((candidate) => candidate && candidate.startsWith("/")); - if (absolute) return absolute; - if (transport.kind === "flatpak") return "/usr/bin/flatpak"; - return candidates.map((candidate) => candidate?.trim()).find(Boolean); -} - -function usesShortcutTarget(nonSteam: boolean, transport: TargetTransport): boolean { - return nonSteam && transport.kind === "flatpak"; -} - -function integrationIsInstalled( - steam: SteamLaunchOptionsSnapshot, - nonSteam: boolean, - transport: TargetTransport, - wrapperPath: string, -): boolean { - return usesShortcutTarget(nonSteam, transport) - ? steam.target === wrapperPath - : hasWrapperLaunchIntegration(steam.options, wrapperPath); -} - function makeSnapshot( steam: SteamLaunchOptionsSnapshot, result: Awaited>, @@ -97,17 +70,16 @@ function makeSnapshot( if (!result.state) throw new Error("Workaround state is not initialized for this profile"); const wrapperPath = result.wrapper_path || getDefaultWrapperPath(); const selectedTransport = result.transport || transport; - if (usesShortcutTarget(nonSteam, selectedTransport) && steam.target === wrapperPath && !result.shortcut_exe) { - throw new Error("Managed shortcut Target has no saved original executable"); - } + const shortcutExe = selectedTransport.kind === "flatpak" ? result.shortcut_exe : undefined; + assertKnownShortcutTarget(steam, nonSteam, selectedTransport, wrapperPath, shortcutExe); return { steam, state: result.state, wrapperPath, wrapperOwned: result.wrapper_owned === true, - integrationInstalled: integrationIsInstalled(steam, nonSteam, selectedTransport, wrapperPath), + integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, selectedTransport, wrapperPath), commandTokenAdded: result.command_token_added === true, - shortcutExe: result.shortcut_exe, + shortcutExe, transport: selectedTransport, }; } @@ -116,24 +88,8 @@ async function adoptWorkaroundState( appId: string, nonSteam: boolean, transport: TargetTransport, - steam: SteamLaunchOptionsSnapshot, wrapperPath: string, ): Promise { - const shortcutTarget = usesShortcutTarget(nonSteam, transport); - if (shortcutTarget && (!steam.target || steam.target === wrapperPath || isLegacyWrapperToken(steam.target))) { - throw new Error("Shortcut Target is a wrapper but its original Target is unknown"); - } - const originalExecutable = shortcutTarget - ? selectShortcutExecutable(transport, steam.target) - : null; - const initial = await setWorkaroundState( - appId, - DEFAULT_WORKAROUND_STATE, - originalExecutable, - false, - transport, - ); - if (!initial.success) throw new Error(initial.error || "Could not create workaround state"); let integration: Awaited> | null = null; try { integration = await installWrapperIntegration( @@ -141,14 +97,12 @@ async function adoptWorkaroundState( nonSteam, wrapperPath, false, - transport.kind, + transport, ); const finalized = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, - shortcutTarget - ? (selectShortcutExecutable(transport, integration.originalExecutable, originalExecutable) || null) - : null, + integration.originalExecutable ?? null, integration.commandTokenAdded, transport, ); @@ -156,17 +110,15 @@ async function adoptWorkaroundState( return makeSnapshot(integration.snapshot, finalized, nonSteam, transport); } catch (error) { let rollbackSucceeded = true; - if (integration) { + if (integration?.changed) { try { await removeWrapperIntegration( Number(appId), nonSteam, wrapperPath, - shortcutTarget - ? (selectShortcutExecutable(transport, integration?.originalExecutable, originalExecutable) || undefined) - : undefined, - integration?.commandTokenAdded ?? false, - transport.kind, + integration.originalExecutable, + integration.commandTokenAdded, + transport, ); } catch { // Leave the owned integration in place rather than guessing at cleanup. @@ -203,7 +155,6 @@ export function usePerAppWorkarounds( appId, nonSteam, transport, - steam, result.wrapper_path || getDefaultWrapperPath(), ); } @@ -243,7 +194,7 @@ export function usePerAppWorkarounds( setSnapshot((current) => current ? { ...current, steam, - integrationInstalled: integrationIsInstalled(steam, nonSteam, current.transport, current.wrapperPath), + integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, current.transport, current.wrapperPath), } : current); }, (subscriptionError) => { @@ -278,22 +229,24 @@ export function usePerAppWorkarounds( setError(null); const nextState = { ...current.state, [field]: value } as WorkaroundState; try { + const shortcutExe = current.transport.kind === "flatpak" ? current.shortcutExe ?? null : null; const result = await setWorkaroundState( appId, nextState, - current.shortcutExe ?? null, + shortcutExe, current.commandTokenAdded, current.transport, ); if (!result.success || !result.state) throw new Error(result.error || "Could not save workaround state"); + const selectedTransport = result.transport || current.transport; applySnapshot({ ...current, state: result.state, wrapperPath: result.wrapper_path || current.wrapperPath, wrapperOwned: result.wrapper_owned === true, - shortcutExe: result.shortcut_exe, + shortcutExe: selectedTransport.kind === "flatpak" ? result.shortcut_exe : undefined, commandTokenAdded: result.command_token_added === true, - transport: result.transport || current.transport, + transport: selectedTransport, }); return true; } catch (updateError) { -- cgit v1.2.3 From 05bde72a64bd3fbf675e15e273043090f79d0a49 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:44:10 -0400 Subject: refactor: remove flatpak transport from workaround hook --- src/hooks/usePerAppWorkarounds.ts | 48 +++++++++++++++------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index 29e1181..30bbc6b 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -3,11 +3,9 @@ import { getWorkaroundState, removeWorkaroundState, setWorkaroundState, - type TargetTransport, type WorkaroundState, } from "../api/lsfgApi"; import { - assertKnownShortcutTarget, getDefaultWrapperPath, installWrapperIntegration, isWrapperIntegrationInstalled, @@ -45,8 +43,7 @@ export interface WorkaroundSnapshot { wrapperOwned: boolean; integrationInstalled: boolean; commandTokenAdded: boolean; - shortcutExe?: string | null; - transport: TargetTransport; + directFlatpak: boolean; } interface PerAppWorkarounds { @@ -65,29 +62,25 @@ function makeSnapshot( steam: SteamLaunchOptionsSnapshot, result: Awaited>, nonSteam: boolean, - transport: TargetTransport, + directFlatpak: boolean, ): WorkaroundSnapshot { if (!result.state) throw new Error("Workaround state is not initialized for this profile"); const wrapperPath = result.wrapper_path || getDefaultWrapperPath(); - const selectedTransport = result.transport || transport; - const shortcutExe = selectedTransport.kind === "flatpak" ? result.shortcut_exe : undefined; - assertKnownShortcutTarget(steam, nonSteam, selectedTransport, wrapperPath, shortcutExe); return { steam, state: result.state, wrapperPath, wrapperOwned: result.wrapper_owned === true, - integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, selectedTransport, wrapperPath), + integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, directFlatpak, wrapperPath), commandTokenAdded: result.command_token_added === true, - shortcutExe, - transport: selectedTransport, + directFlatpak, }; } async function adoptWorkaroundState( appId: string, nonSteam: boolean, - transport: TargetTransport, + directFlatpak: boolean, wrapperPath: string, ): Promise { let integration: Awaited> | null = null; @@ -97,17 +90,15 @@ async function adoptWorkaroundState( nonSteam, wrapperPath, false, - transport, + directFlatpak, ); const finalized = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, - integration.originalExecutable ?? null, integration.commandTokenAdded, - transport, ); if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); - return makeSnapshot(integration.snapshot, finalized, nonSteam, transport); + return makeSnapshot(integration.snapshot, finalized, nonSteam, directFlatpak); } catch (error) { let rollbackSucceeded = true; if (integration?.changed) { @@ -116,12 +107,10 @@ async function adoptWorkaroundState( Number(appId), nonSteam, wrapperPath, - integration.originalExecutable, integration.commandTokenAdded, - transport, + directFlatpak, ); } catch { - // Leave the owned integration in place rather than guessing at cleanup. rollbackSucceeded = false; } } @@ -136,7 +125,7 @@ async function adoptWorkaroundState( export function usePerAppWorkarounds( appId: string, nonSteam: boolean, - transport: TargetTransport = { kind: "host" }, + directFlatpak = false, ): PerAppWorkarounds { const [status, setStatus] = useState("loading"); const [snapshot, setSnapshot] = useState(null); @@ -154,12 +143,12 @@ export function usePerAppWorkarounds( return adoptWorkaroundState( appId, nonSteam, - transport, + directFlatpak, result.wrapper_path || getDefaultWrapperPath(), ); } - return makeSnapshot(steam, result, nonSteam, transport); - }, [appId, nonSteam, numericAppId, transport]); + return makeSnapshot(steam, result, nonSteam, directFlatpak); + }, [appId, directFlatpak, nonSteam, numericAppId]); const applySnapshot = useCallback((next: WorkaroundSnapshot) => { setSnapshot(next); @@ -194,7 +183,12 @@ export function usePerAppWorkarounds( setSnapshot((current) => current ? { ...current, steam, - integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, current.transport, current.wrapperPath), + integrationInstalled: isWrapperIntegrationInstalled( + steam, + nonSteam, + current.directFlatpak, + current.wrapperPath, + ), } : current); }, (subscriptionError) => { @@ -229,24 +223,18 @@ export function usePerAppWorkarounds( setError(null); const nextState = { ...current.state, [field]: value } as WorkaroundState; try { - const shortcutExe = current.transport.kind === "flatpak" ? current.shortcutExe ?? null : null; const result = await setWorkaroundState( appId, nextState, - shortcutExe, current.commandTokenAdded, - current.transport, ); if (!result.success || !result.state) throw new Error(result.error || "Could not save workaround state"); - const selectedTransport = result.transport || current.transport; applySnapshot({ ...current, state: result.state, wrapperPath: result.wrapper_path || current.wrapperPath, wrapperOwned: result.wrapper_owned === true, - shortcutExe: selectedTransport.kind === "flatpak" ? result.shortcut_exe : undefined, commandTokenAdded: result.command_token_added === true, - transport: selectedTransport, }); return true; } catch (updateError) { -- cgit v1.2.3 From 4dc1a3b7cc6b02ed06a8349d85b0eae36af10b0b Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:35:05 -0400 Subject: refactor: remove flatpak mode from steam workarounds --- src/hooks/usePerAppWorkarounds.ts | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index 30bbc6b..c2b8904 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -43,7 +43,6 @@ export interface WorkaroundSnapshot { wrapperOwned: boolean; integrationInstalled: boolean; commandTokenAdded: boolean; - directFlatpak: boolean; } interface PerAppWorkarounds { @@ -62,7 +61,6 @@ function makeSnapshot( steam: SteamLaunchOptionsSnapshot, result: Awaited>, nonSteam: boolean, - directFlatpak: boolean, ): WorkaroundSnapshot { if (!result.state) throw new Error("Workaround state is not initialized for this profile"); const wrapperPath = result.wrapper_path || getDefaultWrapperPath(); @@ -71,34 +69,26 @@ function makeSnapshot( state: result.state, wrapperPath, wrapperOwned: result.wrapper_owned === true, - integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, directFlatpak, wrapperPath), + integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, wrapperPath), commandTokenAdded: result.command_token_added === true, - directFlatpak, }; } async function adoptWorkaroundState( appId: string, nonSteam: boolean, - directFlatpak: boolean, wrapperPath: string, ): Promise { let integration: Awaited> | null = null; try { - integration = await installWrapperIntegration( - Number(appId), - nonSteam, - wrapperPath, - false, - directFlatpak, - ); + integration = await installWrapperIntegration(Number(appId), nonSteam, wrapperPath, false); const finalized = await setWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, integration.commandTokenAdded, ); if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); - return makeSnapshot(integration.snapshot, finalized, nonSteam, directFlatpak); + return makeSnapshot(integration.snapshot, finalized, nonSteam); } catch (error) { let rollbackSucceeded = true; if (integration?.changed) { @@ -108,7 +98,6 @@ async function adoptWorkaroundState( nonSteam, wrapperPath, integration.commandTokenAdded, - directFlatpak, ); } catch { rollbackSucceeded = false; @@ -122,11 +111,7 @@ async function adoptWorkaroundState( } } -export function usePerAppWorkarounds( - appId: string, - nonSteam: boolean, - directFlatpak = false, -): PerAppWorkarounds { +export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWorkarounds { const [status, setStatus] = useState("loading"); const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(null); @@ -143,12 +128,11 @@ export function usePerAppWorkarounds( return adoptWorkaroundState( appId, nonSteam, - directFlatpak, result.wrapper_path || getDefaultWrapperPath(), ); } - return makeSnapshot(steam, result, nonSteam, directFlatpak); - }, [appId, directFlatpak, nonSteam, numericAppId]); + return makeSnapshot(steam, result, nonSteam); + }, [appId, nonSteam, numericAppId]); const applySnapshot = useCallback((next: WorkaroundSnapshot) => { setSnapshot(next); @@ -183,12 +167,7 @@ export function usePerAppWorkarounds( setSnapshot((current) => current ? { ...current, steam, - integrationInstalled: isWrapperIntegrationInstalled( - steam, - nonSteam, - current.directFlatpak, - current.wrapperPath, - ), + integrationInstalled: isWrapperIntegrationInstalled(steam, nonSteam, current.wrapperPath), } : current); }, (subscriptionError) => { -- cgit v1.2.3 From 954b2abc47d8772211cb8ecee523900f823184e8 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 11:08:12 -0400 Subject: better uninstall cleanup --- src/hooks/usePerAppWorkarounds.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/hooks/usePerAppWorkarounds.ts') diff --git a/src/hooks/usePerAppWorkarounds.ts b/src/hooks/usePerAppWorkarounds.ts index c2b8904..ab33bb2 100644 --- a/src/hooks/usePerAppWorkarounds.ts +++ b/src/hooks/usePerAppWorkarounds.ts @@ -86,6 +86,7 @@ async function adoptWorkaroundState( appId, DEFAULT_WORKAROUND_STATE, integration.commandTokenAdded, + nonSteam, ); if (!finalized.success) throw new Error(finalized.error || "Could not finalize workaround state"); return makeSnapshot(integration.snapshot, finalized, nonSteam); @@ -206,6 +207,7 @@ export function usePerAppWorkarounds(appId: string, nonSteam: boolean): PerAppWo appId, nextState, current.commandTokenAdded, + nonSteam, ); if (!result.success || !result.state) throw new Error(result.error || "Could not save workaround state"); applySnapshot({ -- cgit v1.2.3