summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--py_modules/lsfg_vk/steam_service.py26
-rw-r--r--src/hooks/useGameConfiguration.ts12
-rw-r--r--src/utils/steamLaunchOptions.ts10
-rw-r--r--tests/steamLaunchOptions.test.ts48
-rw-r--r--tests/test_steam_service.py19
5 files changed, 107 insertions, 8 deletions
diff --git a/py_modules/lsfg_vk/steam_service.py b/py_modules/lsfg_vk/steam_service.py
index a7c0083..a1b58a9 100644
--- a/py_modules/lsfg_vk/steam_service.py
+++ b/py_modules/lsfg_vk/steam_service.py
@@ -26,13 +26,33 @@ def _split_command(value: Optional[str]) -> Optional[list[str]]:
return None
-def classify_shortcut_transport(executable: Optional[str], _launch_options: Optional[str] = None) -> Dict[str, object]:
+def _is_usr_bin_start_dir(value: Optional[str]) -> bool:
+ return isinstance(value, str) and value.strip().rstrip("/") == "/usr/bin"
+
+
+def _effective_shortcut_executable(
+ executable: Optional[str],
+ start_dir: Optional[str],
+) -> Optional[str]:
+ if _split_command(executable) == ["flatpak"] and _is_usr_bin_start_dir(start_dir):
+ return "/usr/bin/flatpak"
+ return executable
+
+
+def classify_shortcut_transport(
+ executable: Optional[str],
+ _launch_options: Optional[str] = None,
+ start_dir: Optional[str] = None,
+) -> Dict[str, object]:
"""Recognize only the direct Flatpak executable Target.
+ Steam can serialize the direct target as ``Exe=flatpak`` with
+ ``StartDir=/usr/bin/``. Treat that split representation as the same
+ effective target without inspecting launch arguments or app IDs.
Launch scripts and wrapper commands remain host targets. Their launch
options are intentionally opaque to the plugin.
"""
- executable_tokens = _split_command(executable)
+ executable_tokens = _split_command(_effective_shortcut_executable(executable, start_dir))
return {"kind": "flatpak"} if tuple(executable_tokens or ()) in _DIRECT_FLATPAK_TARGETS else {"kind": "host"}
@@ -137,7 +157,7 @@ class SteamService(BaseService):
"appid": str(appid & 0xFFFFFFFF),
"name": name,
"nonSteam": True,
- "transport": classify_shortcut_transport(executable, arguments),
+ "transport": classify_shortcut_transport(executable, arguments, start_dir),
}
for key, value in (("executable", executable), ("arguments", arguments), ("startDir", start_dir)):
if value is not None:
diff --git a/src/hooks/useGameConfiguration.ts b/src/hooks/useGameConfiguration.ts
index a3976a0..b2867b3 100644
--- a/src/hooks/useGameConfiguration.ts
+++ b/src/hooks/useGameConfiguration.ts
@@ -3,13 +3,16 @@ import { useQuickAccessVisible } from "@decky/api";
import { Router } from "@decky/ui";
import { getGameConfigs, getInstalledGames, getWorkaroundState, removeWorkaroundState, resetGameConfig, resetAllGameConfigs, setWorkaroundState, updateGameConfig, type GameConfigEntry, type GlobalConfig, type InstalledGame, type WorkaroundState } from "../api/lsfgApi";
import { ConfigurationData, getDefaults } from "../config/configSchema";
-import { getDefaultWrapperPath, installWrapperIntegration, removeWrapperIntegration } from "../utils/steamLaunchOptions";
+import { getDefaultWrapperPath, installWrapperIntegration, normalizeShortcutTarget, removeWrapperIntegration } from "../utils/steamLaunchOptions";
import { showErrorToast } from "../utils/toastUtils";
export interface GameTarget extends InstalledGame { configured: boolean; }
-function shortcutTransport(executable: unknown): InstalledGame["transport"] {
- const target = typeof executable === "string" ? executable.trim() : "";
+function shortcutTransport(executable: unknown, startDir: unknown): InstalledGame["transport"] {
+ const target = normalizeShortcutTarget(
+ typeof executable === "string" ? executable : undefined,
+ typeof startDir === "string" ? startDir : undefined,
+ );
return target === "/usr/bin/flatpak"
|| target === '"~/.lsfg" "/usr/bin/flatpak"'
|| target === "~/.lsfg /usr/bin/flatpak"
@@ -29,11 +32,12 @@ async function getSteamShortcuts(): Promise<InstalledGame[]> {
const name = shortcut?.data?.strAppName;
if (!Number.isInteger(appid) || appid === 0 || typeof name !== "string" || !name) return [];
const executable = shortcut?.data?.strShortcutExe ?? shortcut?.data?.strExe ?? shortcut?.data?.exe;
+ const startDir = shortcut?.data?.strShortcutStartDir ?? shortcut?.data?.strStartDir ?? shortcut?.data?.startDir;
return [{
appid: String(appid >>> 0),
name,
nonSteam: true,
- transport: shortcutTransport(executable),
+ transport: shortcutTransport(executable, startDir),
}];
});
} catch {
diff --git a/src/utils/steamLaunchOptions.ts b/src/utils/steamLaunchOptions.ts
index bcd69d0..9f88a9f 100644
--- a/src/utils/steamLaunchOptions.ts
+++ b/src/utils/steamLaunchOptions.ts
@@ -57,12 +57,20 @@ function timer() {
};
}
+export function normalizeShortcutTarget(executable?: string | null, startDir?: string | null): string {
+ const target = typeof executable === "string" ? executable.trim() : "";
+ const normalizedStartDir = typeof startDir === "string" ? startDir.trim().replace(/\/+$/, "") : "";
+ return target === "flatpak" && normalizedStartDir === "/usr/bin"
+ ? DIRECT_FLATPAK_EXECUTABLE
+ : target;
+}
+
function snapshot(appId: number, nonSteam: boolean, details: SteamAppDetails): SteamLaunchOptionsSnapshot {
return {
appId,
nonSteam,
options: nonSteam ? details.strShortcutLaunchOptions || "" : details.strLaunchOptions || "",
- target: nonSteam ? details.strShortcutExe || "" : "",
+ target: nonSteam ? normalizeShortcutTarget(details.strShortcutExe, details.strShortcutStartDir) : "",
details,
};
}
diff --git a/tests/steamLaunchOptions.test.ts b/tests/steamLaunchOptions.test.ts
index 049f6be..1a6069f 100644
--- a/tests/steamLaunchOptions.test.ts
+++ b/tests/steamLaunchOptions.test.ts
@@ -8,6 +8,7 @@ import {
installWrapperIntegration,
installWrapperLaunchOption,
isLegacyWrapperToken,
+ normalizeShortcutTarget,
normalizeLaunchOptions,
readSteamLaunchOptions,
removeWrapperIntegration,
@@ -16,6 +17,12 @@ import {
const wrapper = "~/.lsfg";
+test("normalizes Steam's split direct Flatpak target representation", () => {
+ assert.equal(normalizeShortcutTarget("flatpak", "/usr/bin/"), "/usr/bin/flatpak");
+ assert.equal(normalizeShortcutTarget("flatpak", "/usr/bin"), "/usr/bin/flatpak");
+ assert.equal(normalizeShortcutTarget("flatpak", "/home/deck/"), "flatpak");
+});
+
test("inserts one wrapper immediately before an existing command macro", () => {
assert.deepEqual(installWrapperLaunchOption('gamemoderun %command% --profile "high quality"', wrapper), {
options: 'gamemoderun ~/.lsfg %command% --profile "high quality"',
@@ -144,6 +151,47 @@ test("reads the matching app-details field and installs/removes Steam integratio
}
});
+test("wraps a split direct Flatpak target while preserving its launch arguments", async () => {
+ const previousWindow = (globalThis as Record<string, unknown>).window;
+ const previousSteamClient = (globalThis as Record<string, unknown>).SteamClient;
+ let shortcutTarget = "flatpak";
+ const shortcutOptions = '"run" "--branch=master" "io.github.banjorecomp.banjorecomp"';
+ const targetWrites: string[] = [];
+ const apps = {
+ RegisterForAppDetails(_appId: number, callback: (details: SteamAppDetails) => void) {
+ callback({
+ strShortcutExe: shortcutTarget,
+ strShortcutStartDir: "/usr/bin/",
+ strShortcutLaunchOptions: shortcutOptions,
+ });
+ return { unregister() {} };
+ },
+ SetShortcutExe(_appId: number, executable: string) {
+ targetWrites.push(executable);
+ shortcutTarget = executable;
+ },
+ SetShortcutLaunchOptions() {},
+ };
+ (globalThis as Record<string, unknown>).window = { setTimeout, clearTimeout };
+ (globalThis as Record<string, unknown>).SteamClient = { Apps: apps };
+ try {
+ const installed = await installWrapperIntegration(46, true, wrapper, false, { kind: "flatpak" });
+ assert.equal(installed.originalExecutable, "/usr/bin/flatpak");
+ assert.equal(installed.snapshot.target, '"~/.lsfg" "/usr/bin/flatpak"');
+ assert.equal(installed.snapshot.options, shortcutOptions);
+ assert.deepEqual(targetWrites, ['"~/.lsfg" "/usr/bin/flatpak"']);
+
+ const restored = await removeWrapperIntegration(46, true, wrapper, installed.originalExecutable, false, { kind: "flatpak" });
+ assert.equal(restored.target, "/usr/bin/flatpak");
+ assert.deepEqual(targetWrites, ['"~/.lsfg" "/usr/bin/flatpak"', "/usr/bin/flatpak"]);
+ } finally {
+ if (previousWindow === undefined) delete (globalThis as Record<string, unknown>).window;
+ else (globalThis as Record<string, unknown>).window = previousWindow;
+ if (previousSteamClient === undefined) delete (globalThis as Record<string, unknown>).SteamClient;
+ else (globalThis as Record<string, unknown>).SteamClient = previousSteamClient;
+ }
+});
+
test("uses shortcut launch options for a host shortcut without changing its Target", async () => {
const previousWindow = (globalThis as Record<string, unknown>).window;
const previousSteamClient = (globalThis as Record<string, unknown>).SteamClient;
diff --git a/tests/test_steam_service.py b/tests/test_steam_service.py
index 95bd2a2..4f61f3c 100644
--- a/tests/test_steam_service.py
+++ b/tests/test_steam_service.py
@@ -21,6 +21,10 @@ class SteamTransportTests(unittest.TestCase):
classify_shortcut_transport("/usr/bin/flatpak", "run com.example.Game --fullscreen"),
{"kind": "flatpak"},
)
+ self.assertEqual(
+ classify_shortcut_transport("flatpak", "run com.example.Game --fullscreen", "/usr/bin/"),
+ {"kind": "flatpak"},
+ )
for executable, options in (
("flatpak", "run com.example.Game"),
("/usr/bin/flatpak run com.example.Game", "--fullscreen"),
@@ -40,6 +44,21 @@ class SteamTransportTests(unittest.TestCase):
)
self.assertEqual(classify_shortcut_transport("~/.lsfg", "run com.example.Game"), {"kind": "host"})
+ def test_split_flatpak_target_is_recognized_without_script_or_app_detection(self):
+ game = SteamService._shortcut_game(
+ {
+ "appid": 123456,
+ "AppName": "Split Flatpak shortcut",
+ "Exe": "flatpak",
+ "StartDir": "/usr/bin/",
+ "LaunchOptions": "run io.example.Game",
+ }
+ )
+
+ self.assertEqual(game["transport"], {"kind": "flatpak"})
+ self.assertEqual(game["executable"], "flatpak")
+ self.assertEqual(game["startDir"], "/usr/bin/")
+
def test_direct_flatpak_shortcut_keeps_arguments_without_an_app_id(self):
game = SteamService._shortcut_game(
{