summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2025-09-26 12:16:22 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2025-09-26 12:16:22 -0400
commit7d2322e637faae5ccfab58c54f7a13e6a5f7ea88 (patch)
treee92c7e73d497bf66faff14aed29b6372ba0c53ce
parentcbed25162a1058e67180aafb8fbd424bf2573e95 (diff)
downloadDecky-Framegen-7d2322e637faae5ccfab58c54f7a13e6a5f7ea88.tar.gz
Decky-Framegen-7d2322e637faae5ccfab58c54f7a13e6a5f7ea88.zip
feat: first arg steam path start, second mirror first
-rw-r--r--main.py13
-rw-r--r--src/api/index.ts5
-rw-r--r--src/components/CustomPathOverride.tsx146
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<string | null>(null);
const [overridePath, setOverridePath] = useState<string | null>(null);
const [isEnabled, setEnabled] = useState(false);
+ const [pathDefaults, setPathDefaults] = useState<PathDefaults>(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<string>();
+
+ 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
<PanelSectionRow>
<ButtonItem
layout="below"
- onClick={() => 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
<PanelSectionRow>
<ButtonItem
layout="below"
- onClick={() => 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
</ButtonItem>