From 28ebc17785a8cca52f289b74261d1f310fd35904 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 07:08:19 -0400 Subject: fix: detect wrapped Flatpak shortcuts --- py_modules/lsfg_vk/steam_service.py | 21 ++++++++++-- src/components/Content.tsx | 1 - src/components/SetupTab.tsx | 68 +++++++++++++++---------------------- 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(null); - const [advanced, setAdvanced] = useState(false); const [operation, setOperation] = useState(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 ( - + - - setAdvanced((value) => !value)}> - {advanced ? "Hide runtime details" : "Show runtime details"} - - - {advanced && ( - <> - {status.supported_branches.map((branch) => ( - - handleExtensionToggle(branch, enabled)} - disabled={operation !== null} - /> - - ))} - - )} + {status.supported_branches.map((branch) => ( + + handleExtensionToggle(branch, enabled)} + disabled={operation !== null} + /> + + ))} ); } @@ -118,7 +107,6 @@ export function SetupTab({ isUninstalling, onInstall, onUninstall, - flatpakRelevant, }: SetupTabProps) { return ( <> @@ -137,7 +125,7 @@ export function SetupTab({ onUninstall={onUninstall} /> - + ); } 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() -- cgit v1.2.3