summaryrefslogtreecommitdiff
path: root/src/components/WorkaroundsSection.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/WorkaroundsSection.tsx')
-rw-r--r--src/components/WorkaroundsSection.tsx190
1 files changed, 190 insertions, 0 deletions
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>
+ ))}
+ </>
+ )}
+ </>
+ );
+}