diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-09-10 07:08:19 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2026-09-10 07:08:19 -0400 |
| commit | 28ebc17785a8cca52f289b74261d1f310fd35904 (patch) | |
| tree | 2a7f96f01f05e8207f1935fdadc2d2a4c3359968 | |
| parent | bc75bb93d5aa9aa176262a138f47d3a1d53afbcb (diff) | |
| download | decky-lsfg-vk-28ebc17785a8cca52f289b74261d1f310fd35904.tar.gz decky-lsfg-vk-28ebc17785a8cca52f289b74261d1f310fd35904.zip | |
fix: detect wrapped Flatpak shortcuts
| -rw-r--r-- | py_modules/lsfg_vk/steam_service.py | 21 | ||||
| -rw-r--r-- | src/components/Content.tsx | 1 | ||||
| -rw-r--r-- | src/components/SetupTab.tsx | 68 | ||||
| -rw-r--r-- | tests/test_steam_service.py | 33 |
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() |
