summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-10 07:08:19 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-10 07:08:19 -0400
commit28ebc17785a8cca52f289b74261d1f310fd35904 (patch)
tree2a7f96f01f05e8207f1935fdadc2d2a4c3359968
parentbc75bb93d5aa9aa176262a138f47d3a1d53afbcb (diff)
downloaddecky-lsfg-vk-28ebc17785a8cca52f289b74261d1f310fd35904.tar.gz
decky-lsfg-vk-28ebc17785a8cca52f289b74261d1f310fd35904.zip
fix: detect wrapped Flatpak shortcuts
-rw-r--r--py_modules/lsfg_vk/steam_service.py21
-rw-r--r--src/components/Content.tsx1
-rw-r--r--src/components/SetupTab.tsx68
-rw-r--r--tests/test_steam_service.py33
4 files changed, 79 insertions, 44 deletions
diff --git a/py_modules/lsfg_vk/steam_service.py b/py_modules/lsfg_vk/steam_service.py
index b3bdb69..50d722b 100644
--- a/py_modules/lsfg_vk/steam_service.py
+++ b/py_modules/lsfg_vk/steam_service.py
@@ -4,10 +4,15 @@ from pathlib import Path
from typing import Dict, Optional, Tuple
from .base_service import BaseService
-from .constants import STEAM_LOSSLESS_SCALING_APP_ID, STEAM_LOSSLESS_SCALING_BRANCH
+from .constants import (
+ STEAM_LOSSLESS_SCALING_APP_ID,
+ STEAM_LOSSLESS_SCALING_BRANCH,
+ WRAPPER_FILENAME,
+)
_FLATPAK_APP_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9-]*(?:\.[A-Za-z0-9][A-Za-z0-9-]*)+$")
+_WRAPPER_TOKEN = f"~/{WRAPPER_FILENAME}"
def _split_command(value: Optional[str]) -> Optional[list[str]]:
@@ -19,17 +24,27 @@ def _split_command(value: Optional[str]) -> Optional[list[str]]:
return None
+def _is_managed_wrapper(value: str) -> bool:
+ """Recognize the wrapper Target while keeping arbitrary launchers as host games."""
+ if value in {_WRAPPER_TOKEN, f"$HOME/{WRAPPER_FILENAME}"}:
+ return True
+ path = Path(value)
+ return path.is_absolute() and path.name == WRAPPER_FILENAME
+
+
def classify_shortcut_transport(
executable: Optional[str],
launch_options: Optional[str] = None,
) -> Dict[str, object]:
- """Classify only direct Flatpak invocations; leave shell launchers on host."""
+ """Classify direct Flatpak invocations, including the managed wrapper Target."""
executable_tokens = _split_command(executable)
option_tokens = _split_command(launch_options)
if executable_tokens is None or option_tokens is None or not executable_tokens:
return {"kind": "host"}
- if executable_tokens[0] != "/usr/bin/flatpak":
+ direct_flatpak = executable_tokens[0] == "/usr/bin/flatpak"
+ managed_wrapper = len(executable_tokens) == 1 and _is_managed_wrapper(executable_tokens[0])
+ if not direct_flatpak and not managed_wrapper:
return {"kind": "host"}
arguments = [*executable_tokens[1:], *option_tokens]
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 58512d5..d4f54e9 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -103,7 +103,6 @@ export function Content() {
isUninstalling={isUninstalling}
onInstall={onInstall}
onUninstall={onUninstall}
- flatpakRelevant={targets.some((target) => target.transport.kind === "flatpak")}
/>
);
diff --git a/src/components/SetupTab.tsx b/src/components/SetupTab.tsx
index aec0e93..6bf1fad 100644
--- a/src/components/SetupTab.tsx
+++ b/src/components/SetupTab.tsx
@@ -1,4 +1,4 @@
-import { ButtonItem, Field, PanelSection, PanelSectionRow, ToggleField } from "@decky/ui";
+import { Field, PanelSection, PanelSectionRow, ToggleField } from "@decky/ui";
import { useEffect, useState } from "react";
import {
getFlatpakSupportStatus,
@@ -20,12 +20,10 @@ interface SetupTabProps {
isUninstalling: boolean;
onInstall: () => void;
onUninstall: () => void;
- flatpakRelevant: boolean;
}
-function FlatpakSupportDiagnostics({ relevant }: { relevant: boolean }) {
+function FlatpakSupportDiagnostics() {
const [status, setStatus] = useState<FlatpakExtensionStatus | null>(null);
- const [advanced, setAdvanced] = useState(false);
const [operation, setOperation] = useState<string | null>(null);
const refresh = async () => {
@@ -45,10 +43,10 @@ function FlatpakSupportDiagnostics({ relevant }: { relevant: boolean }) {
};
useEffect(() => {
- if (relevant) void refresh();
- }, [relevant]);
+ void refresh();
+ }, []);
- if (!relevant || !status?.available) return null;
+ if (!status?.available) return null;
const runExtensionOperation = async (version: string, enabled: boolean) => {
const operationKey = `${enabled ? "enable" : "disable"}-${version}`;
@@ -69,41 +67,32 @@ function FlatpakSupportDiagnostics({ relevant }: { relevant: boolean }) {
};
return (
- <PanelSection title="Flatpak support">
+ <PanelSection title="Flatpak runtimes">
<PanelSectionRow>
<Field
- label="Runtime extension support"
- description={status.message || "Flatpak is available for classified targets."}
+ label="LSFG-VK runtime extensions"
+ description={status.message || "Toggle a branch to install or uninstall it."}
/>
</PanelSectionRow>
- <PanelSectionRow>
- <ButtonItem layout="below" onClick={() => setAdvanced((value) => !value)}>
- {advanced ? "Hide runtime details" : "Show runtime details"}
- </ButtonItem>
- </PanelSectionRow>
- {advanced && (
- <>
- {status.supported_branches.map((branch) => (
- <PanelSectionRow key={branch}>
- <ToggleField
- label={branch}
- description={
- operation === `enable-${branch}`
- ? "Installing..."
- : operation === `disable-${branch}`
- ? "Uninstalling..."
- : status.installed_branches.includes(branch)
- ? "Installed"
- : "Not installed"
- }
- checked={status.installed_branches.includes(branch)}
- onChange={(enabled) => handleExtensionToggle(branch, enabled)}
- disabled={operation !== null}
- />
- </PanelSectionRow>
- ))}
- </>
- )}
+ {status.supported_branches.map((branch) => (
+ <PanelSectionRow key={branch}>
+ <ToggleField
+ label={branch}
+ description={
+ operation === `enable-${branch}`
+ ? "Installing..."
+ : operation === `disable-${branch}`
+ ? "Uninstalling..."
+ : status.installed_branches.includes(branch)
+ ? "Installed"
+ : "Not installed"
+ }
+ checked={status.installed_branches.includes(branch)}
+ onChange={(enabled) => handleExtensionToggle(branch, enabled)}
+ disabled={operation !== null}
+ />
+ </PanelSectionRow>
+ ))}
</PanelSection>
);
}
@@ -118,7 +107,6 @@ export function SetupTab({
isUninstalling,
onInstall,
onUninstall,
- flatpakRelevant,
}: SetupTabProps) {
return (
<>
@@ -137,7 +125,7 @@ export function SetupTab({
onUninstall={onUninstall}
/>
</PanelSection>
- <FlatpakSupportDiagnostics relevant={flatpakRelevant} />
+ <FlatpakSupportDiagnostics />
</>
);
}
diff --git a/tests/test_steam_service.py b/tests/test_steam_service.py
index 849bb01..9924186 100644
--- a/tests/test_steam_service.py
+++ b/tests/test_steam_service.py
@@ -51,6 +51,24 @@ class SteamTransportTests(unittest.TestCase):
),
{"kind": "host"},
)
+ self.assertEqual(
+ classify_shortcut_transport(
+ "~/.lsfg",
+ "run --branch=stable --arch=x86_64 com.example.PCSX2",
+ ),
+ {"kind": "flatpak", "flatpakAppId": "com.example.PCSX2"},
+ )
+ self.assertEqual(
+ classify_shortcut_transport(
+ "/home/deck/.lsfg",
+ "run com.example.PCSX2",
+ ),
+ {"kind": "flatpak", "flatpakAppId": "com.example.PCSX2"},
+ )
+ self.assertEqual(
+ classify_shortcut_transport("~/.lsfg", "--profile high"),
+ {"kind": "host"},
+ )
def test_shortcut_data_preserves_transport_inputs(self):
game = SteamService._shortcut_game(
@@ -72,6 +90,21 @@ class SteamTransportTests(unittest.TestCase):
self.assertEqual(game["arguments"], "run net.pcsx2.PCSX2 --fullscreen")
self.assertEqual(game["startDir"], "/home/deck/Games")
+ def test_wrapped_flatpak_shortcut_remains_a_flatpak_target(self):
+ game = SteamService._shortcut_game(
+ {
+ "appid": 987654,
+ "AppName": "Wrapped Flatpak",
+ "Exe": "~/.lsfg",
+ "LaunchOptions": "run --branch=stable --arch=x86_64 com.example.Game",
+ }
+ )
+
+ self.assertEqual(game["transport"], {
+ "kind": "flatpak",
+ "flatpakAppId": "com.example.Game",
+ })
+
if __name__ == "__main__":
unittest.main()