summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-08 16:25:11 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-08 16:25:11 -0400
commit8352ee74e8a437c1f4a4dadb75d63049f45b164b (patch)
treebd59691eb01f5fbf4b5a6aede5ae4d0295e6643d /src/components
parentb3749ecc4b094a46d2ab9f638ee810f70840f67a (diff)
downloaddecky-lsfg-vk-8352ee74e8a437c1f4a4dadb75d63049f45b164b.tar.gz
decky-lsfg-vk-8352ee74e8a437c1f4a4dadb75d63049f45b164b.zip
add back workarounds sections, scope out of now playing
Diffstat (limited to 'src/components')
-rw-r--r--src/components/ConfigurationTab.tsx2
-rw-r--r--src/components/GameConfigurationControls.tsx9
-rw-r--r--src/components/NowPlayingTab.tsx2
-rw-r--r--src/components/WorkaroundsSection.tsx190
-rw-r--r--src/components/index.ts1
5 files changed, 203 insertions, 1 deletions
diff --git a/src/components/ConfigurationTab.tsx b/src/components/ConfigurationTab.tsx
index 6ba8f5d..2bb0f26 100644
--- a/src/components/ConfigurationTab.tsx
+++ b/src/components/ConfigurationTab.tsx
@@ -149,6 +149,8 @@ export function ConfigurationTab({
onConfigChange={onConfigChange}
autoFocusFpsMultiplier={focusFpsMultiplier}
onFpsMultiplierFocused={clearFpsFocusRequest}
+ showWorkarounds
+ workaroundTarget={selectedTarget || undefined}
/>
)}
{selectedTarget?.configured && (
diff --git a/src/components/GameConfigurationControls.tsx b/src/components/GameConfigurationControls.tsx
index 34b82c5..58ccd02 100644
--- a/src/components/GameConfigurationControls.tsx
+++ b/src/components/GameConfigurationControls.tsx
@@ -1,12 +1,16 @@
import { ConfigurationData } from "../config/configSchema";
+import type { GameTarget } from "../hooks/useGameConfiguration";
import { ConfigurationSection } from "./ConfigurationSection";
import { FpsMultiplierControl } from "./FpsMultiplierControl";
+import { WorkaroundsSection } from "./WorkaroundsSection";
interface Props {
config: ConfigurationData;
onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string | string[]) => Promise<void>;
autoFocusFpsMultiplier?: boolean;
onFpsMultiplierFocused?: () => void;
+ showWorkarounds?: boolean;
+ workaroundTarget?: Pick<GameTarget, "appid" | "nonSteam">;
}
export function GameConfigurationControls({
@@ -14,6 +18,8 @@ export function GameConfigurationControls({
onConfigChange,
autoFocusFpsMultiplier,
onFpsMultiplierFocused,
+ showWorkarounds = false,
+ workaroundTarget,
}: Props) {
return (
<>
@@ -24,6 +30,9 @@ export function GameConfigurationControls({
onAutoFocus={onFpsMultiplierFocused}
/>
<ConfigurationSection config={config} onConfigChange={onConfigChange} />
+ {showWorkarounds && workaroundTarget && (
+ <WorkaroundsSection appId={workaroundTarget.appid} nonSteam={workaroundTarget.nonSteam} />
+ )}
</>
);
}
diff --git a/src/components/NowPlayingTab.tsx b/src/components/NowPlayingTab.tsx
index 2ec12ca..188c56a 100644
--- a/src/components/NowPlayingTab.tsx
+++ b/src/components/NowPlayingTab.tsx
@@ -19,7 +19,7 @@ export function NowPlayingTab({ game, config, onConfigChange }: Props) {
/>
</PanelSectionRow>
</PanelSection>
- <GameConfigurationControls config={config} onConfigChange={onConfigChange} />
+ <GameConfigurationControls config={config} onConfigChange={onConfigChange} showWorkarounds={false} />
</Focusable>
);
}
diff --git a/src/components/WorkaroundsSection.tsx b/src/components/WorkaroundsSection.tsx
new file mode 100644
index 0000000..e990784
--- /dev/null
+++ b/src/components/WorkaroundsSection.tsx
@@ -0,0 +1,190 @@
+import { ButtonItem, Field, PanelSectionRow, SliderField, ToggleField } from "@decky/ui";
+import { useEffect, useState } from "react";
+import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
+import { usePerAppWorkarounds } from "../hooks/usePerAppWorkarounds";
+import t from "../i18n/i18n";
+import type { WorkaroundField } from "../utils/steamLaunchOptions";
+
+interface WorkaroundsSectionProps {
+ appId: string;
+ nonSteam: boolean;
+}
+
+const WORKAROUNDS_COLLAPSED_KEY = "lsfg-workarounds-collapsed";
+type ToggleWorkaroundField = Exclude<WorkaroundField, "dxvkFrameRate">;
+
+const TOGGLE_ROWS: readonly {
+ field: ToggleWorkaroundField;
+ labelKey: string;
+ label: string;
+ descriptionKey: string;
+ description: string;
+}[] = [
+ {
+ field: "disableGamescopeWsi",
+ labelKey: "CONFIG_DISABLE_GAMESCOPE_WSI",
+ label: "Disable Gamescope WSI",
+ descriptionKey: "CONFIG_DISABLE_GAMESCOPE_WSI_DESC",
+ description: "Adds ENABLE_GAMESCOPE_WSI=0 without changing HDR. Requires game restart to apply.",
+ },
+ {
+ field: "disableSteamdeckMode",
+ labelKey: "CONFIG_DISABLE_STEAMDECK_MODE",
+ label: "Disable Steam Deck Mode",
+ descriptionKey: "CONFIG_DISABLE_STEAMDECK_MODE_DESC",
+ description: "Disables a game-specific Steam Deck compatibility switch. Requires game restart to apply.",
+ },
+ {
+ field: "disableVkbasalt",
+ labelKey: "CONFIG_DISABLE_VKBASALT",
+ label: "Disable vkBasalt",
+ descriptionKey: "CONFIG_DISABLE_VKBASALT_DESC",
+ description: "Disables vkBasalt layer which can conflict with LSFG (Reshade, some Decky plugins)",
+ },
+ {
+ field: "enableZink",
+ labelKey: "CONFIG_ENABLE_ZINK",
+ label: "Force Zink for OpenGL Games",
+ descriptionKey: "CONFIG_ENABLE_ZINK_DESC",
+ description: "Uses Mesa's Zink OpenGL-to-Vulkan driver. May cause crashes or freezes with some games. Requires game restart to apply.",
+ },
+];
+
+function usePersistentCollapsed() {
+ const [collapsed, setCollapsed] = useState(() => {
+ try {
+ const saved = localStorage.getItem(WORKAROUNDS_COLLAPSED_KEY);
+ return saved !== null ? JSON.parse(saved) === true : true;
+ } catch {
+ return true;
+ }
+ });
+
+ useEffect(() => {
+ try {
+ localStorage.setItem(WORKAROUNDS_COLLAPSED_KEY, JSON.stringify(collapsed));
+ } catch {
+ // Persisting the view preference is optional.
+ }
+ }, [collapsed]);
+
+ return [collapsed, () => setCollapsed((value) => !value)] as const;
+}
+
+export function WorkaroundsSection({ appId, nonSteam }: WorkaroundsSectionProps) {
+ const [collapsed, toggleCollapsed] = usePersistentCollapsed();
+ const { status, snapshot, refresh, update, error } = usePerAppWorkarounds(appId, nonSteam);
+ const state = snapshot?.parsed.state;
+ const issues = snapshot?.parsed.issues || [];
+ const controlsDisabled = status !== "ready" || state === undefined;
+ const [fpsValue, setFpsValue] = useState<number | null>(null);
+ const effectiveFpsValue = fpsValue ?? state?.dxvkFrameRate ?? 0;
+ const fpsLabel = effectiveFpsValue > 0
+ ? `${effectiveFpsValue} FPS`
+ : t("CONFIG_BASE_FPS_CAP_OFF", "Off");
+
+ useEffect(() => {
+ setFpsValue(state?.dxvkFrameRate ?? null);
+ }, [state?.dxvkFrameRate, status]);
+
+ return (
+ <>
+ <style>
+ {`
+ .LSFG_WorkaroundsCollapseButton_Container > div > div > div > button,
+ .LSFG_WorkaroundsCollapseButton_Container > div > div > div > div > button {
+ height: 24px !important;
+ padding: 0 !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ }
+ `}
+ </style>
+ <PanelSectionRow>
+ <div
+ style={{
+ fontSize: "14px",
+ fontWeight: "bold",
+ marginTop: "8px",
+ marginBottom: "6px",
+ borderBottom: "1px solid rgba(255, 255, 255, 0.2)",
+ paddingBottom: "3px",
+ color: "white",
+ }}
+ >
+ {t("CONFIG_WORKAROUNDS_TITLE", "Workarounds")}
+ </div>
+ </PanelSectionRow>
+ <PanelSectionRow>
+ <div className="LSFG_WorkaroundsCollapseButton_Container" style={{ marginTop: "-2px", marginBottom: "4px" }}>
+ <ButtonItem
+ layout="below"
+ bottomSeparator={collapsed ? "standard" : "none"}
+ onClick={toggleCollapsed}
+ >
+ {collapsed ? <RiArrowDownSFill /> : <RiArrowUpSFill />}
+ </ButtonItem>
+ </div>
+ </PanelSectionRow>
+
+ {!collapsed && (
+ <>
+ {status === "loading" && (
+ <PanelSectionRow>
+ <Field label="Reading launch options..." />
+ </PanelSectionRow>
+ )}
+ {status === "error" && (
+ <>
+ <PanelSectionRow>
+ <Field
+ label="Launch options unavailable"
+ description={error || "Steam did not provide readable launch options."}
+ />
+ </PanelSectionRow>
+ <PanelSectionRow>
+ <ButtonItem layout="below" onClick={() => void refresh()}>Retry</ButtonItem>
+ </PanelSectionRow>
+ </>
+ )}
+ {status === "ready" && issues.length > 0 && (
+ <PanelSectionRow>
+ <Field
+ label="Launch options need attention"
+ description={`${issues.join(" ")} Adjusting a workaround will normalize its managed values.`}
+ />
+ </PanelSectionRow>
+ )}
+
+ <PanelSectionRow>
+ <SliderField
+ label={`${t("CONFIG_BASE_FPS_CAP", "Base FPS Cap")} (${fpsLabel})`}
+ description={t("CONFIG_BASE_FPS_CAP_DESC", "Base cap for DXVK-backed games before frame generation; 0 disables. Requires game restart to apply.")}
+ value={effectiveFpsValue}
+ min={0}
+ max={60}
+ step={1}
+ onChange={(value) => {
+ setFpsValue(value);
+ void update("dxvkFrameRate", value);
+ }}
+ disabled={controlsDisabled}
+ />
+ </PanelSectionRow>
+ {TOGGLE_ROWS.map((row) => (
+ <PanelSectionRow key={row.field}>
+ <ToggleField
+ label={t(row.labelKey, row.label)}
+ description={t(row.descriptionKey, row.description)}
+ checked={Boolean(state?.[row.field])}
+ onChange={(value) => void update(row.field, value)}
+ disabled={controlsDisabled}
+ />
+ </PanelSectionRow>
+ ))}
+ </>
+ )}
+ </>
+ );
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index 424a360..37a8edb 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -10,3 +10,4 @@ export { FlatpaksTab } from "./FlatpaksTab";
export { GameConfigurationSelector } from "./GameConfigurationSelector";
export { GameConfigurationControls } from "./GameConfigurationControls";
export { NowPlayingTab } from "./NowPlayingTab";
+export { WorkaroundsSection } from "./WorkaroundsSection";