From 7d2322e637faae5ccfab58c54f7a13e6a5f7ea88 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 26 Sep 2025 12:16:22 -0400 Subject: feat: first arg steam path start, second mirror first --- main.py | 13 +++ src/api/index.ts | 5 ++ src/components/CustomPathOverride.tsx | 146 +++++++++++++++++++++++++++++----- 3 files changed, 144 insertions(+), 20 deletions(-) diff --git a/main.py b/main.py index 76a7b22..a22acca 100644 --- a/main.py +++ b/main.py @@ -429,5 +429,18 @@ class Plugin: decky.logger.error(str(e)) return {"status": "error", "message": str(e)} + async def get_path_defaults(self) -> dict: + try: + home_path = Path(decky.HOME) + except TypeError: + home_path = Path(str(decky.HOME)) + + steam_common = home_path / ".local" / "share" / "Steam" / "steamapps" / "common" + + return { + "home": str(home_path), + "steam_common": str(steam_common), + } + async def log_error(self, error: str) -> None: decky.logger.error(f"FRONTEND: {error}") diff --git a/src/api/index.ts b/src/api/index.ts index 11e4213..1cc3285 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -21,3 +21,8 @@ export const listInstalledGames = callable< >("list_installed_games"); export const logError = callable<[string], void>("log_error"); + +export const getPathDefaults = callable< + [], + { home: string; steam_common?: string } +>("get_path_defaults"); diff --git a/src/components/CustomPathOverride.tsx b/src/components/CustomPathOverride.tsx index 9e7d34b..9915efc 100644 --- a/src/components/CustomPathOverride.tsx +++ b/src/components/CustomPathOverride.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { ButtonItem, Field, PanelSectionRow, ToggleField } from "@decky/ui"; import { FileSelectionType, openFilePicker } from "@decky/api"; +import { getPathDefaults } from "../api"; import type { CustomOverrideConfig } from "../types/index"; interface CustomPathOverrideProps { @@ -8,9 +9,23 @@ interface CustomPathOverrideProps { } const DEFAULT_START_PATH = "/home"; +const DEFAULT_STEAM_LIBRARY_PATH = "/home/deck/.local/share/Steam/steamapps/common"; + +interface PathDefaults { + home: string; + steamCommon: string; +} + +const INITIAL_PATH_DEFAULTS: PathDefaults = { + home: DEFAULT_START_PATH, + steamCommon: DEFAULT_STEAM_LIBRARY_PATH, +}; const normalizePath = (path: string) => path.replace(/\\/g, "/"); +const stripTrailingSlash = (value: string) => + value.endsWith("/") ? value.slice(0, -1) : value; + const escapeForDoubleQuotes = (value: string) => value.replace(/[`"\\$]/g, (match) => `\\${match}`); @@ -31,6 +46,14 @@ const escapeForReplacement = (value: string) => const quoteForShell = (value: string) => `'${value.replace(/'/g, "'\\''")}'`; +const dirname = (path: string) => { + const normalized = normalizePath(path); + const parts = normalized.split("/"); + parts.pop(); + const dir = parts.join("/"); + return dir.length > 0 ? dir : "/"; +}; + const longestCommonPrefix = (left: string[], right: string[]) => { const length = Math.min(left.length, right.length); let idx = 0; @@ -129,6 +152,40 @@ export const CustomPathOverride = ({ onOverrideChange }: CustomPathOverrideProps const [launcherPath, setLauncherPath] = useState(null); const [overridePath, setOverridePath] = useState(null); const [isEnabled, setEnabled] = useState(false); + const [pathDefaults, setPathDefaults] = useState(INITIAL_PATH_DEFAULTS); + + useEffect(() => { + let cancelled = false; + + const fetchDefaults = async () => { + try { + const result = await getPathDefaults(); + if (!result) { + return; + } + + const home = result.home ? normalizePath(result.home) : INITIAL_PATH_DEFAULTS.home; + const steamCommonSource = result.steam_common + ? normalizePath(result.steam_common) + : normalizePath(`${stripTrailingSlash(home)}/.local/share/Steam/steamapps/common`); + + if (!cancelled) { + setPathDefaults({ + home, + steamCommon: steamCommonSource || INITIAL_PATH_DEFAULTS.steamCommon, + }); + } + } catch (err) { + console.error("CustomPathOverride -> getPathDefaults", err); + } + }; + + fetchDefaults(); + + return () => { + cancelled = true; + }; + }, []); const { config, error } = useMemo( () => buildOverride(launcherPath, overridePath), @@ -143,27 +200,58 @@ export const CustomPathOverride = ({ onOverrideChange }: CustomPathOverrideProps } }, [config, isEnabled, onOverrideChange]); + interface PickerArgs { + existing: string | null; + setter: (value: string) => void; + fallbackStart?: string | null; + } + const openPicker = useCallback( - async (existing: string | null, setter: (value: string) => void) => { - try { - const startPath = existing ? normalizePath(existing) : DEFAULT_START_PATH; - const result = await openFilePicker( - FileSelectionType.FILE, - startPath, - true, - false, - undefined, - undefined, - true - ); - if (result?.path) { - setter(normalizePath(result.path)); + async ({ existing, setter, fallbackStart }: PickerArgs) => { + const candidates = new Set(); + + if (existing) { + candidates.add(normalizePath(existing)); + } else { + if (fallbackStart) { + candidates.add(normalizePath(fallbackStart)); } - } catch (err) { - console.error("CustomPathOverride -> openPicker", err); + candidates.add(pathDefaults.steamCommon); + candidates.add(pathDefaults.home); + } + + let lastError: unknown = null; + + for (const candidate of candidates) { + if (!candidate) { + continue; + } + + try { + const result = await openFilePicker( + FileSelectionType.FILE, + candidate, + true, + true, + undefined, + undefined, + true + ); + + if (result?.path) { + setter(normalizePath(result.path)); + return; + } + } catch (err) { + lastError = err; + } + } + + if (lastError) { + console.error("CustomPathOverride -> openPicker", lastError); } }, - [] + [pathDefaults] ); const handleToggle = (value: boolean) => { @@ -193,7 +281,13 @@ export const CustomPathOverride = ({ onOverrideChange }: CustomPathOverrideProps openPicker(launcherPath, setLauncherPath)} + onClick={() => + openPicker({ + existing: launcherPath, + setter: setLauncherPath, + fallbackStart: pathDefaults.steamCommon, + }) + } description={launcherPath || "Pick the EXE Steam currently uses."} > Select Steam-provided EXE @@ -203,8 +297,20 @@ export const CustomPathOverride = ({ onOverrideChange }: CustomPathOverrideProps openPicker(overridePath, setOverridePath)} - description={overridePath || "Pick the executable that should run instead."} + disabled={!launcherPath} + onClick={() => + launcherPath && + openPicker({ + existing: overridePath, + setter: setOverridePath, + fallbackStart: launcherPath ? dirname(launcherPath) : pathDefaults.steamCommon, + }) + } + description={ + launcherPath + ? overridePath || "Pick the executable that should run instead." + : "Select the Steam-provided executable first." + } > Select Override EXE -- cgit v1.2.3