From e64e87e9eb9e3c183ad7807dffa356f47d14e825 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:33:30 -0400 Subject: fix: migrate flatpak integration to v2 --- py_modules/lsfg_vk/plugin.py | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index cb59b4f..bfb5102 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -440,36 +440,19 @@ class Plugin: # Clean up lsfg-vk files when the plugin is uninstalled self.installation_service.cleanup_on_uninstall() - # Also clean up flatpak extensions if they are installed try: - decky.logger.info("Checking for flatpak extensions to uninstall") - extension_status = self.flatpak_service.get_extension_status() - - if extension_status.get("success"): - if extension_status.get("installed_23_08"): - decky.logger.info("Uninstalling lsfg-vk flatpak runtime 23.08") - result = self.flatpak_service.uninstall_extension("23.08") - if result.get("success"): - decky.logger.info("Successfully uninstalled flatpak runtime 23.08") - else: - decky.logger.warning(f"Failed to uninstall flatpak runtime 23.08: {result.get('error')}") - - if extension_status.get("installed_24_08"): - decky.logger.info("Uninstalling lsfg-vk flatpak runtime 24.08") - result = self.flatpak_service.uninstall_extension("24.08") - if result.get("success"): - decky.logger.info("Successfully uninstalled flatpak runtime 24.08") - else: - decky.logger.warning(f"Failed to uninstall flatpak runtime 24.08: {result.get('error')}") - - decky.logger.info("Flatpak extension cleanup completed") - else: - decky.logger.info(f"Could not check flatpak status for cleanup: {extension_status.get('error')}") - - except Exception as e: - decky.logger.error(f"Error during flatpak cleanup: {e}") - + for version, key in ( + ("24.08", "installed_24_08"), + ("25.08", "installed_25_08"), + ): + if extension_status.get(key): + result = self.flatpak_service.uninstall_extension(version) + if not result.get("success"): + decky.logger.warning(result.get("error")) + except Exception as error: + decky.logger.error(f"Error during Flatpak cleanup: {error}") + decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") async def _migration(self): @@ -492,4 +475,9 @@ class Plugin: os.path.join(decky.DECKY_HOME, "lossless-scaling-vk"), os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk")) + try: + self.flatpak_service.migrate_v2() + except Exception as error: + decky.logger.warning(f"Flatpak v2 migration skipped: {error}") + decky.logger.info("decky-lsfg-vk plugin migrations completed") -- cgit v1.2.3 From fa9d621f51b794acaa0f18b7acf306fc71836b5a Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Sat, 5 Sep 2026 15:34:37 -0400 Subject: fix: migrate existing v1 installs automatically --- py_modules/lsfg_vk/plugin.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index bfb5102..fc2d378 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -475,9 +475,15 @@ class Plugin: os.path.join(decky.DECKY_HOME, "lossless-scaling-vk"), os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk")) - try: - self.flatpak_service.migrate_v2() - except Exception as error: - decky.logger.warning(f"Flatpak v2 migration skipped: {error}") + if self.installation_service.needs_v2_migration(): + result = self.installation_service.install() + if not result.get("success"): + decky.logger.warning(f"Native v2 migration failed: {result.get('error')}") + + if self.installation_service.check_installation().get("installed"): + try: + self.flatpak_service.migrate_v2() + except Exception as error: + decky.logger.warning(f"Flatpak v2 migration skipped: {error}") decky.logger.info("decky-lsfg-vk plugin migrations completed") -- cgit v1.2.3 From ed605b881998d76717073af4459fb54189edada8 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:57:36 -0400 Subject: chore: clean v2 migration code --- py_modules/lsfg_vk/plugin.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index fc2d378..a9eb6a6 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -6,7 +6,6 @@ Vulkan layer for frame generation on Steam Deck. """ import os -import subprocess import hashlib from typing import Dict, Any from pathlib import Path @@ -37,7 +36,7 @@ class Plugin: self.flatpak_service = FlatpakService() async def install_lsfg_vk(self) -> Dict[str, Any]: - """Install lsfg-vk by extracting the zip file to ~/.local + """Install the bundled lsfg-vk runtime to ~/.local Returns: InstallationResponse dict with success status and message/error @@ -362,7 +361,7 @@ class Plugin: """Install lsfg-vk Flatpak runtime extension Args: - version: Runtime version to install ("23.08" or "24.08") + version: Runtime version to install ("24.08" or "25.08") Returns: BaseResponse dict with success status and message/error @@ -373,7 +372,7 @@ class Plugin: """Uninstall lsfg-vk Flatpak runtime extension Args: - version: Runtime version to uninstall ("23.08" or "24.08") + version: Runtime version to uninstall ("24.08" or "25.08") Returns: BaseResponse dict with success status and message/error -- cgit v1.2.3 From e8e469f99078858dc953663cba6f3428e80b1c5d Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sat, 5 Sep 2026 23:44:37 -0400 Subject: refactor: delegate runtime checks to lsfg-vk --- py_modules/lsfg_vk/plugin.py | 93 +++++++------------------------------------- 1 file changed, 13 insertions(+), 80 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index a9eb6a6..6a8dfc5 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -6,17 +6,16 @@ Vulkan layer for frame generation on Steam Deck. """ import os -import hashlib from typing import Dict, Any from pathlib import Path import decky from .installation import InstallationService -from .dll_detection import DllDetectionService from .configuration import ConfigurationService from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService +from .runtime_service import RuntimeService class Plugin: @@ -24,15 +23,15 @@ class Plugin: Main plugin class for lsfg-vk management. This class provides a unified interface for installation, configuration, - and DLL detection services. It implements the Decky Loader plugin lifecycle + and Flatpak management services. It implements the Decky Loader plugin lifecycle methods (_main, _unload, _uninstall, _migration). """ def __init__(self): """Initialize the plugin with all necessary services""" - self.installation_service = InstallationService() - self.dll_detection_service = DllDetectionService() - self.configuration_service = ConfigurationService() + self.runtime_service = RuntimeService() + self.installation_service = InstallationService(runtime_service=self.runtime_service) + self.configuration_service = ConfigurationService(runtime_service=self.runtime_service) self.flatpak_service = FlatpakService() async def install_lsfg_vk(self) -> Dict[str, Any]: @@ -59,72 +58,6 @@ class Plugin: """ return self.installation_service.uninstall() - async def check_lossless_scaling_dll(self) -> Dict[str, Any]: - """Check if Lossless Scaling DLL is available at the expected paths - - Returns: - DllDetectionResponse dict with detection status and path info - """ - return self.dll_detection_service.check_lossless_scaling_dll() - - async def get_dll_stats(self) -> Dict[str, Any]: - """Get detailed statistics about the detected DLL - - Returns: - Dict containing DLL path, SHA256 hash, and other stats - """ - try: - dll_result = self.dll_detection_service.check_lossless_scaling_dll() - - if not dll_result.get("detected") or not dll_result.get("path"): - return { - "success": False, - "error": "DLL not detected", - "dll_path": None, - "dll_sha256": None - } - - dll_path = dll_result["path"] - if dll_path is None: - return { - "success": False, - "error": "DLL path is None", - "dll_path": None, - "dll_sha256": None - } - - dll_path_obj = Path(dll_path) - - sha256_hash = hashlib.sha256() - try: - with open(dll_path_obj, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - sha256_hash.update(chunk) - dll_sha256 = sha256_hash.hexdigest() - except Exception as e: - return { - "success": False, - "error": f"Failed to calculate SHA256: {str(e)}", - "dll_path": dll_path, - "dll_sha256": None - } - - return { - "success": True, - "dll_path": dll_path, - "dll_sha256": dll_sha256, - "dll_source": dll_result.get("source"), - "error": None - } - - except Exception as e: - return { - "success": False, - "error": f"Failed to get DLL stats: {str(e)}", - "dll_path": None, - "dll_sha256": None - } - async def get_lsfg_config(self) -> Dict[str, Any]: """Read current lsfg script configuration @@ -353,7 +286,7 @@ class Plugin: """Check status of lsfg-vk Flatpak runtime extensions Returns: - FlatpakExtensionStatus dict with installation status for both runtime versions + FlatpakExtensionStatus dict with installation status for all supported runtime versions """ return self.flatpak_service.get_extension_status() @@ -361,7 +294,7 @@ class Plugin: """Install lsfg-vk Flatpak runtime extension Args: - version: Runtime version to install ("24.08" or "25.08") + version: Runtime version to install ("23.08", "24.08", or "25.08") Returns: BaseResponse dict with success status and message/error @@ -372,7 +305,7 @@ class Plugin: """Uninstall lsfg-vk Flatpak runtime extension Args: - version: Runtime version to uninstall ("24.08" or "25.08") + version: Runtime version to uninstall ("23.08", "24.08", or "25.08") Returns: BaseResponse dict with success status and message/error @@ -442,6 +375,7 @@ class Plugin: try: extension_status = self.flatpak_service.get_extension_status() for version, key in ( + ("23.08", "installed_23_08"), ("24.08", "installed_24_08"), ("25.08", "installed_25_08"), ): @@ -479,10 +413,9 @@ class Plugin: if not result.get("success"): decky.logger.warning(f"Native v2 migration failed: {result.get('error')}") - if self.installation_service.check_installation().get("installed"): - try: - self.flatpak_service.migrate_v2() - except Exception as error: - decky.logger.warning(f"Flatpak v2 migration skipped: {error}") + try: + self.flatpak_service.migrate_v2() + except Exception as error: + decky.logger.warning(f"Flatpak v2 migration skipped: {error}") decky.logger.info("decky-lsfg-vk plugin migrations completed") -- cgit v1.2.3 From 89e64a7dbddb8f713dd2ca37131924e8bd8ab51f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sat, 5 Sep 2026 23:58:16 -0400 Subject: feat: select Lossless Scaling lsfg-vk branch --- py_modules/lsfg_vk/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 6a8dfc5..1676566 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -16,6 +16,7 @@ from .configuration import ConfigurationService from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService from .runtime_service import RuntimeService +from .steam_service import SteamService class Plugin: @@ -33,6 +34,7 @@ class Plugin: self.installation_service = InstallationService(runtime_service=self.runtime_service) self.configuration_service = ConfigurationService(runtime_service=self.runtime_service) self.flatpak_service = FlatpakService() + self.steam_service = SteamService() async def install_lsfg_vk(self) -> Dict[str, Any]: """Install the bundled lsfg-vk runtime to ~/.local @@ -320,6 +322,12 @@ class Plugin: """ return self.flatpak_service.get_flatpak_apps() + async def get_lossless_scaling_branch_status(self) -> Dict[str, Any]: + return self.steam_service.get_branch_status() + + async def select_lossless_scaling_branch(self) -> Dict[str, Any]: + return self.steam_service.select_branch() + async def set_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: """Set lsfg-vk overrides for a Flatpak app -- cgit v1.2.3 From a9fd67d05d6819a839a60b581c1dc6eb2792a9be Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 00:16:19 -0400 Subject: refactor: make Steam branch integration read-only --- py_modules/lsfg_vk/plugin.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 1676566..f764086 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -325,9 +325,6 @@ class Plugin: async def get_lossless_scaling_branch_status(self) -> Dict[str, Any]: return self.steam_service.get_branch_status() - async def select_lossless_scaling_branch(self) -> Dict[str, Any]: - return self.steam_service.select_branch() - async def set_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: """Set lsfg-vk overrides for a Flatpak app -- cgit v1.2.3 From ad2b182777bfd0a5ceef6e654df75ff13eb8b503 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 15:08:45 -0400 Subject: refactor: offload configuration to lsfg-vk --- py_modules/lsfg_vk/plugin.py | 89 +++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 47 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index f764086..3b9a97f 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -31,10 +31,13 @@ class Plugin: def __init__(self): """Initialize the plugin with all necessary services""" self.runtime_service = RuntimeService() - self.installation_service = InstallationService(runtime_service=self.runtime_service) + self.steam_service = SteamService() + self.installation_service = InstallationService( + runtime_service=self.runtime_service, + steam_service=self.steam_service, + ) self.configuration_service = ConfigurationService(runtime_service=self.runtime_service) self.flatpak_service = FlatpakService() - self.steam_service = SteamService() async def install_lsfg_vk(self) -> Dict[str, Any]: """Install the bundled lsfg-vk runtime to ~/.local @@ -74,33 +77,11 @@ class Plugin: Returns: Dict with field names, types, defaults, and profile information """ - try: - profiles_response = self.configuration_service.get_profiles() - - schema_data = { - "field_names": ConfigurationManager.get_field_names(), - "field_types": {name: field_type.value for name, field_type in ConfigurationManager.get_field_types().items()}, - "defaults": ConfigurationManager.get_defaults() - } - - if profiles_response.get("success"): - schema_data["profiles"] = profiles_response.get("profiles", []) - schema_data["current_profile"] = profiles_response.get("current_profile") - else: - schema_data["profiles"] = ["decky-lsfg-vk"] - schema_data["current_profile"] = "decky-lsfg-vk" - - return schema_data - - except (ValueError, KeyError, AttributeError) as e: - self.configuration_service.log.warning(f"Failed to get full schema, using fallback: {e}") - return { - "field_names": ConfigurationManager.get_field_names(), - "field_types": {name: field_type.value for name, field_type in ConfigurationManager.get_field_types().items()}, - "defaults": ConfigurationManager.get_defaults(), - "profiles": ["decky-lsfg-vk"], - "current_profile": "decky-lsfg-vk" - } + return { + "field_names": ConfigurationManager.get_field_names(), + "field_types": ConfigurationManager.get_field_types(), + "defaults": ConfigurationManager.get_defaults(), + } async def update_lsfg_config(self, config: Dict[str, Any]) -> Dict[str, Any]: """Update lsfg TOML configuration using object-based API (single source of truth) @@ -111,19 +92,35 @@ class Plugin: Returns: ConfigurationResponse dict with success status """ - validated_config = ConfigurationManager.validate_config(config) - - return self.configuration_service.update_config_from_dict(validated_config) + return self.configuration_service.update_config_from_dict(config) + + async def get_game_configs(self) -> Dict[str, Any]: + return self.configuration_service.get_game_configs() + + async def get_installed_games(self) -> Dict[str, Any]: + return self.steam_service.get_installed_games() - async def get_profiles(self) -> Dict[str, Any]: + async def get_game_config(self, appid: str) -> Dict[str, Any]: + return self.configuration_service.get_game_config(appid) + + async def update_game_config(self, appid: str, config: Dict[str, Any]) -> Dict[str, Any]: + return self.configuration_service.update_game_config(appid, config) + + async def reset_game_config(self, appid: str) -> Dict[str, Any]: + return self.configuration_service.reset_game_config(appid) + + async def reset_all_game_configs(self) -> Dict[str, Any]: + return self.configuration_service.reset_all_game_configs() + + async def _legacy_get_profiles(self) -> Dict[str, Any]: """Get list of all profiles and current profile Returns: ProfilesResponse dict with profile list and current profile """ - return self.configuration_service.get_profiles() + return self.configuration_service.get_game_configs() - async def create_profile(self, profile_name: str, source_profile: str = None) -> Dict[str, Any]: + async def _legacy_create_profile(self, profile_name: str, source_profile: str = None) -> Dict[str, Any]: """Create a new profile Args: @@ -133,9 +130,9 @@ class Plugin: Returns: ProfileResponse dict with success status """ - return self.configuration_service.create_profile(profile_name, source_profile) + return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - async def delete_profile(self, profile_name: str) -> Dict[str, Any]: + async def _legacy_delete_profile(self, profile_name: str) -> Dict[str, Any]: """Delete a profile Args: @@ -144,9 +141,9 @@ class Plugin: Returns: ProfileResponse dict with success status """ - return self.configuration_service.delete_profile(profile_name) + return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - async def rename_profile(self, old_name: str, new_name: str) -> Dict[str, Any]: + async def _legacy_rename_profile(self, old_name: str, new_name: str) -> Dict[str, Any]: """Rename a profile Args: @@ -156,9 +153,9 @@ class Plugin: Returns: ProfileResponse dict with success status """ - return self.configuration_service.rename_profile(old_name, new_name) + return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - async def set_current_profile(self, profile_name: str) -> Dict[str, Any]: + async def _legacy_set_current_profile(self, profile_name: str) -> Dict[str, Any]: """Set the current active profile Args: @@ -167,9 +164,9 @@ class Plugin: Returns: ProfileResponse dict with success status """ - return self.configuration_service.set_current_profile(profile_name) + return {"success": False, "error": "There is no globally selected profile"} - async def update_profile_config(self, profile_name: str, config: Dict[str, Any]) -> Dict[str, Any]: + async def _legacy_update_profile_config(self, profile_name: str, config: Dict[str, Any]) -> Dict[str, Any]: """Update configuration for a specific profile Args: @@ -179,9 +176,7 @@ class Plugin: Returns: ConfigurationResponse dict with success status """ - validated_config = ConfigurationManager.validate_config(config) - - return self.configuration_service.update_profile_config(profile_name, validated_config) + return {"success": False, "error": "Use update_game_config with a Steam AppID"} async def get_launch_option(self) -> Dict[str, Any]: """Get the launch option that users need to set for their games @@ -192,7 +187,7 @@ class Plugin: return { "launch_option": "~/lsfg %command%", "instructions": "Add this to your game's launch options in Steam Properties", - "explanation": "The lsfg script is created during installation and sets up the environment for the plugin" + "explanation": "The lsfg script points games at the upstream configuration; profiles are selected by Steam AppID" } async def get_config_file_content(self) -> Dict[str, Any]: -- cgit v1.2.3 From c9be32287ad5b72fcd86b10fa726d09dbd97d7fd Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 17:09:54 -0400 Subject: refactor: organize plugin into native tabs --- py_modules/lsfg_vk/plugin.py | 48 ++------------------------------------------ 1 file changed, 2 insertions(+), 46 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 3b9a97f..8c788a8 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -103,8 +103,8 @@ class Plugin: async def get_game_config(self, appid: str) -> Dict[str, Any]: return self.configuration_service.get_game_config(appid) - async def update_game_config(self, appid: str, config: Dict[str, Any]) -> Dict[str, Any]: - return self.configuration_service.update_game_config(appid, config) + async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]: + return self.configuration_service.update_game_config(appid, game_name, config) async def reset_game_config(self, appid: str) -> Dict[str, Any]: return self.configuration_service.reset_game_config(appid) @@ -178,18 +178,6 @@ class Plugin: """ return {"success": False, "error": "Use update_game_config with a Steam AppID"} - async def get_launch_option(self) -> Dict[str, Any]: - """Get the launch option that users need to set for their games - - Returns: - Dict containing the launch option string and instructions - """ - return { - "launch_option": "~/lsfg %command%", - "instructions": "Add this to your game's launch options in Steam Properties", - "explanation": "The lsfg script points games at the upstream configuration; profiles are selected by Steam AppID" - } - async def get_config_file_content(self) -> Dict[str, Any]: """Get the current config file content @@ -221,38 +209,6 @@ class Plugin: "error": f"Error reading config file: {str(e)}" } - async def get_launch_script_content(self) -> Dict[str, Any]: - """Get the content of the launch script file - - Returns: - FileContentResponse dict with file content or error information - """ - try: - script_path = self.installation_service.get_launch_script_path() - - if not os.path.exists(script_path): - return { - "success": False, - "error": f"Launch script not found at {script_path}", - "path": str(script_path) - } - - with open(script_path, 'r') as file: - content = file.read() - - return { - "success": True, - "content": content, - "path": str(script_path) - } - - except Exception as e: - decky.logger.error(f"Error reading launch script: {e}") - return { - "success": False, - "error": str(e) - } - async def check_fgmod_directory(self) -> Dict[str, Any]: """Check if the fgmod directory exists in the home directory -- cgit v1.2.3 From 7bc5f685186b1ff03ce0f7c99e7bec411beabaeb Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Sun, 6 Sep 2026 22:04:54 -0400 Subject: feat: make ui suck less, frfr --- py_modules/lsfg_vk/plugin.py | 128 ------------------------------------------- 1 file changed, 128 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 8c788a8..6977b9c 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -7,13 +7,11 @@ Vulkan layer for frame generation on Steam Deck. import os from typing import Dict, Any -from pathlib import Path import decky from .installation import InstallationService from .configuration import ConfigurationService -from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService from .runtime_service import RuntimeService from .steam_service import SteamService @@ -63,46 +61,12 @@ class Plugin: """ return self.installation_service.uninstall() - async def get_lsfg_config(self) -> Dict[str, Any]: - """Read current lsfg script configuration - - Returns: - ConfigurationResponse dict with current configuration or error - """ - return self.configuration_service.get_config() - - async def get_config_schema(self) -> Dict[str, Any]: - """Get configuration schema information for frontend - - Returns: - Dict with field names, types, defaults, and profile information - """ - return { - "field_names": ConfigurationManager.get_field_names(), - "field_types": ConfigurationManager.get_field_types(), - "defaults": ConfigurationManager.get_defaults(), - } - - async def update_lsfg_config(self, config: Dict[str, Any]) -> Dict[str, Any]: - """Update lsfg TOML configuration using object-based API (single source of truth) - - Args: - config: Configuration data dictionary containing all settings - - Returns: - ConfigurationResponse dict with success status - """ - return self.configuration_service.update_config_from_dict(config) - async def get_game_configs(self) -> Dict[str, Any]: return self.configuration_service.get_game_configs() async def get_installed_games(self) -> Dict[str, Any]: return self.steam_service.get_installed_games() - async def get_game_config(self, appid: str) -> Dict[str, Any]: - return self.configuration_service.get_game_config(appid) - async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]: return self.configuration_service.update_game_config(appid, game_name, config) @@ -112,72 +76,6 @@ class Plugin: async def reset_all_game_configs(self) -> Dict[str, Any]: return self.configuration_service.reset_all_game_configs() - async def _legacy_get_profiles(self) -> Dict[str, Any]: - """Get list of all profiles and current profile - - Returns: - ProfilesResponse dict with profile list and current profile - """ - return self.configuration_service.get_game_configs() - - async def _legacy_create_profile(self, profile_name: str, source_profile: str = None) -> Dict[str, Any]: - """Create a new profile - - Args: - profile_name: Name for the new profile - source_profile: Optional source profile to copy from (default: current profile) - - Returns: - ProfileResponse dict with success status - """ - return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - - async def _legacy_delete_profile(self, profile_name: str) -> Dict[str, Any]: - """Delete a profile - - Args: - profile_name: Name of the profile to delete - - Returns: - ProfileResponse dict with success status - """ - return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - - async def _legacy_rename_profile(self, old_name: str, new_name: str) -> Dict[str, Any]: - """Rename a profile - - Args: - old_name: Current profile name - new_name: New profile name - - Returns: - ProfileResponse dict with success status - """ - return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"} - - async def _legacy_set_current_profile(self, profile_name: str) -> Dict[str, Any]: - """Set the current active profile - - Args: - profile_name: Name of the profile to set as current - - Returns: - ProfileResponse dict with success status - """ - return {"success": False, "error": "There is no globally selected profile"} - - async def _legacy_update_profile_config(self, profile_name: str, config: Dict[str, Any]) -> Dict[str, Any]: - """Update configuration for a specific profile - - Args: - profile_name: Name of the profile to update - config: Configuration data dictionary containing settings - - Returns: - ConfigurationResponse dict with success status - """ - return {"success": False, "error": "Use update_game_config with a Steam AppID"} - async def get_config_file_content(self) -> Dict[str, Any]: """Get the current config file content @@ -209,32 +107,6 @@ class Plugin: "error": f"Error reading config file: {str(e)}" } - async def check_fgmod_directory(self) -> Dict[str, Any]: - """Check if the fgmod directory exists in the home directory - - Returns: - Dict with exists status and directory path - """ - try: - home_path = Path(decky.DECKY_USER_HOME) - fgmod_path = home_path / "fgmod" - - exists = fgmod_path.exists() and fgmod_path.is_dir() - - return { - "success": True, - "exists": exists, - "path": str(fgmod_path) - } - - except Exception as e: - decky.logger.error(f"Error checking fgmod directory: {e}") - return { - "success": False, - "exists": False, - "error": str(e) - } - async def check_flatpak_extension_status(self) -> Dict[str, Any]: """Check status of lsfg-vk Flatpak runtime extensions -- cgit v1.2.3 From 54acc36f16e1336772354f979a618cce65061f82 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 7 Sep 2026 20:57:26 -0400 Subject: refactor: make runtime installation explicit --- py_modules/lsfg_vk/plugin.py | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 6977b9c..0472e42 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -236,14 +236,4 @@ class Plugin: os.path.join(decky.DECKY_HOME, "lossless-scaling-vk"), os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk")) - if self.installation_service.needs_v2_migration(): - result = self.installation_service.install() - if not result.get("success"): - decky.logger.warning(f"Native v2 migration failed: {result.get('error')}") - - try: - self.flatpak_service.migrate_v2() - except Exception as error: - decky.logger.warning(f"Flatpak v2 migration skipped: {error}") - decky.logger.info("decky-lsfg-vk plugin migrations completed") -- cgit v1.2.3 From 1b932fd69c3dba925e0cbf027e05508b2daf5e8c Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 01:01:42 -0400 Subject: add back launcher script and correct pathing --- py_modules/lsfg_vk/plugin.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 0472e42..f80c635 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -6,7 +6,7 @@ Vulkan layer for frame generation on Steam Deck. """ import os -from typing import Dict, Any +from typing import Any, Dict, Optional import decky @@ -15,6 +15,7 @@ from .configuration import ConfigurationService from .flatpak_service import FlatpakService from .runtime_service import RuntimeService from .steam_service import SteamService +from .wrapper_service import WrapperService class Plugin: @@ -36,6 +37,7 @@ class Plugin: ) self.configuration_service = ConfigurationService(runtime_service=self.runtime_service) self.flatpak_service = FlatpakService() + self.wrapper_service = WrapperService() async def install_lsfg_vk(self) -> Dict[str, Any]: """Install the bundled lsfg-vk runtime to ~/.local @@ -76,6 +78,21 @@ class Plugin: async def reset_all_game_configs(self) -> Dict[str, Any]: return self.configuration_service.reset_all_game_configs() + async def get_workaround_state(self, appid: str) -> Dict[str, Any]: + return self.wrapper_service.get(appid) + + async def set_workaround_state( + self, + appid: str, + state: Dict[str, Any], + shortcut_exe: Optional[str] = None, + command_token_added: bool = False, + ) -> Dict[str, Any]: + return self.wrapper_service.set(appid, state, shortcut_exe, command_token_added) + + async def remove_workaround_state(self, appid: str) -> Dict[str, Any]: + return self.wrapper_service.remove(appid) + async def get_config_file_content(self) -> Dict[str, Any]: """Get the current config file content @@ -177,6 +194,9 @@ class Plugin: This method is called by Decky Loader when the plugin is loaded. Any initialization code should go here. """ + repair = self.wrapper_service.repair() + if not repair.get("success"): + decky.logger.error(f"Could not repair lsfg workaround wrapper: {repair.get('error')}") decky.logger.info("decky-lsfg-vk plugin loaded") async def _unload(self): @@ -198,6 +218,9 @@ class Plugin: decky.logger.info("decky-lsfg-vk plugin being uninstalled") # Clean up lsfg-vk files when the plugin is uninstalled + # Launch integrations are removed with their profiles. Keep the + # generated pass-through wrapper if it is still referenced elsewhere; + # InstallationService only removes files owned by the runtime bundle. self.installation_service.cleanup_on_uninstall() try: -- cgit v1.2.3 From cc1e6f47dd9838b066822162a607d2859c043aff Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 19:16:30 -0400 Subject: refactor: unify flatpak targets with steam profiles --- py_modules/lsfg_vk/plugin.py | 109 ++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 70 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index f80c635..cb2d3df 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -67,7 +67,24 @@ class Plugin: return self.configuration_service.get_game_configs() async def get_installed_games(self) -> Dict[str, Any]: - return self.steam_service.get_installed_games() + result = self.steam_service.get_installed_games() + if not result.get("success"): + return result + + support_cache: Dict[str, Dict[str, Any]] = {} + for game in result.get("games", []): + transport = game.get("transport") if isinstance(game, dict) else None + if not isinstance(transport, dict) or transport.get("kind") != "flatpak": + continue + flatpak_app_id = transport.get("flatpakAppId") + if not isinstance(flatpak_app_id, str) or not flatpak_app_id: + continue + if flatpak_app_id not in support_cache: + support_cache[flatpak_app_id] = self.flatpak_service.resolve_app_support( + flatpak_app_id + ) + game["flatpakSupport"] = support_cache[flatpak_app_id] + return result async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]: return self.configuration_service.update_game_config(appid, game_name, config) @@ -87,8 +104,15 @@ class Plugin: state: Dict[str, Any], shortcut_exe: Optional[str] = None, command_token_added: bool = False, + transport: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: - return self.wrapper_service.set(appid, state, shortcut_exe, command_token_added) + return self.wrapper_service.set( + appid, + state, + shortcut_exe, + command_token_added, + transport, + ) async def remove_workaround_state(self, appid: str) -> Dict[str, Any]: return self.wrapper_service.remove(appid) @@ -124,68 +148,20 @@ class Plugin: "error": f"Error reading config file: {str(e)}" } - async def check_flatpak_extension_status(self) -> Dict[str, Any]: - """Check status of lsfg-vk Flatpak runtime extensions - - Returns: - FlatpakExtensionStatus dict with installation status for all supported runtime versions - """ - return self.flatpak_service.get_extension_status() - - async def install_flatpak_extension(self, version: str) -> Dict[str, Any]: - """Install lsfg-vk Flatpak runtime extension - - Args: - version: Runtime version to install ("23.08", "24.08", or "25.08") - - Returns: - BaseResponse dict with success status and message/error - """ - return self.flatpak_service.install_extension(version) - - async def uninstall_flatpak_extension(self, version: str) -> Dict[str, Any]: - """Uninstall lsfg-vk Flatpak runtime extension - - Args: - version: Runtime version to uninstall ("23.08", "24.08", or "25.08") - - Returns: - BaseResponse dict with success status and message/error - """ - return self.flatpak_service.uninstall_extension(version) - - async def get_flatpak_apps(self) -> Dict[str, Any]: - """Get list of installed Flatpak apps and their lsfg-vk override status - - Returns: - FlatpakAppInfo dict with apps list and override status - """ - return self.flatpak_service.get_flatpak_apps() - async def get_lossless_scaling_branch_status(self) -> Dict[str, Any]: return self.steam_service.get_branch_status() - async def set_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: - """Set lsfg-vk overrides for a Flatpak app - - Args: - app_id: Flatpak application ID - - Returns: - FlatpakOverrideResponse dict with operation result - """ - return self.flatpak_service.set_app_override(app_id) + async def get_flatpak_support_status(self) -> Dict[str, Any]: + return self.flatpak_service.get_flatpak_support_status() - async def remove_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: - """Remove lsfg-vk overrides for a Flatpak app - - Args: - app_id: Flatpak application ID - - Returns: - FlatpakOverrideResponse dict with operation result - """ - return self.flatpak_service.remove_app_override(app_id) + async def ensure_flatpak_support(self, flatpak_app_id: str) -> Dict[str, Any]: + return self.flatpak_service.ensure_app_support(flatpak_app_id) + + async def repair_flatpak_support(self, flatpak_app_id: str) -> Dict[str, Any]: + return self.flatpak_service.ensure_app_support(flatpak_app_id) + + async def remove_plugin_owned_flatpak_extensions(self) -> Dict[str, Any]: + return self.flatpak_service.remove_plugin_owned_extensions() async def _main(self): """ @@ -224,16 +200,9 @@ class Plugin: self.installation_service.cleanup_on_uninstall() try: - extension_status = self.flatpak_service.get_extension_status() - for version, key in ( - ("23.08", "installed_23_08"), - ("24.08", "installed_24_08"), - ("25.08", "installed_25_08"), - ): - if extension_status.get(key): - result = self.flatpak_service.uninstall_extension(version) - if not result.get("success"): - decky.logger.warning(result.get("error")) + result = self.flatpak_service.remove_plugin_owned_extensions() + if not result.get("success"): + decky.logger.warning(result.get("error")) except Exception as error: decky.logger.error(f"Error during Flatpak cleanup: {error}") -- cgit v1.2.3 From fb4d053213bdbda271a54b517a11a89c4780f80a Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 9 Sep 2026 20:45:20 -0400 Subject: fix: restore profile and Flatpak controls --- py_modules/lsfg_vk/plugin.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index cb2d3df..76a4250 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -160,6 +160,9 @@ class Plugin: async def repair_flatpak_support(self, flatpak_app_id: str) -> Dict[str, Any]: return self.flatpak_service.ensure_app_support(flatpak_app_id) + async def set_flatpak_extension_enabled(self, version: str, enabled: bool) -> Dict[str, Any]: + return self.flatpak_service.set_extension_enabled(version, enabled) + async def remove_plugin_owned_flatpak_extensions(self) -> Dict[str, Any]: return self.flatpak_service.remove_plugin_owned_extensions() -- cgit v1.2.3 From bc75bb93d5aa9aa176262a138f47d3a1d53afbcb Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 00:25:58 -0400 Subject: fix: simplify Flatpak runtime toggles --- py_modules/lsfg_vk/plugin.py | 3 --- 1 file changed, 3 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 76a4250..c576cc2 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -162,9 +162,6 @@ class Plugin: async def set_flatpak_extension_enabled(self, version: str, enabled: bool) -> Dict[str, Any]: return self.flatpak_service.set_extension_enabled(version, enabled) - - async def remove_plugin_owned_flatpak_extensions(self) -> Dict[str, Any]: - return self.flatpak_service.remove_plugin_owned_extensions() async def _main(self): """ -- cgit v1.2.3 From 450d3e5e6612d467a00bb937538fded640c66ecb Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 07:24:05 -0400 Subject: refactor: simplify migration implementation --- py_modules/lsfg_vk/plugin.py | 164 +++++++++++++------------------------------ 1 file changed, 47 insertions(+), 117 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index c576cc2..071b19b 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -1,34 +1,18 @@ -""" -Main plugin class for the lsfg-vk Decky Loader plugin. - -This plugin provides services for installing and managing the lsfg-vk -Vulkan layer for frame generation on Steam Deck. -""" - import os from typing import Any, Dict, Optional import decky -from .installation import InstallationService from .configuration import ConfigurationService from .flatpak_service import FlatpakService +from .installation import InstallationService from .runtime_service import RuntimeService from .steam_service import SteamService from .wrapper_service import WrapperService class Plugin: - """ - Main plugin class for lsfg-vk management. - - This class provides a unified interface for installation, configuration, - and Flatpak management services. It implements the Decky Loader plugin lifecycle - methods (_main, _unload, _uninstall, _migration). - """ - def __init__(self): - """Initialize the plugin with all necessary services""" self.runtime_service = RuntimeService() self.steam_service = SteamService() self.installation_service = InstallationService( @@ -39,63 +23,45 @@ class Plugin: self.flatpak_service = FlatpakService() self.wrapper_service = WrapperService() - async def install_lsfg_vk(self) -> Dict[str, Any]: - """Install the bundled lsfg-vk runtime to ~/.local - - Returns: - InstallationResponse dict with success status and message/error - """ + async def install_lsfg_vk(self): return self.installation_service.install() - async def check_lsfg_vk_installed(self) -> Dict[str, Any]: - """Check if lsfg-vk is already installed - - Returns: - InstallationCheckResponse dict with installation status and paths - """ + async def check_lsfg_vk_installed(self): return self.installation_service.check_installation() - async def uninstall_lsfg_vk(self) -> Dict[str, Any]: - """Uninstall lsfg-vk by removing the installed files - - Returns: - UninstallationResponse dict with success status and removed files - """ + async def uninstall_lsfg_vk(self): return self.installation_service.uninstall() - async def get_game_configs(self) -> Dict[str, Any]: + async def get_game_configs(self): return self.configuration_service.get_game_configs() - async def get_installed_games(self) -> Dict[str, Any]: + async def get_installed_games(self): result = self.steam_service.get_installed_games() if not result.get("success"): return result - - support_cache: Dict[str, Dict[str, Any]] = {} + cache: Dict[str, Dict[str, Any]] = {} for game in result.get("games", []): - transport = game.get("transport") if isinstance(game, dict) else None - if not isinstance(transport, dict) or transport.get("kind") != "flatpak": + transport = game.get("transport", {}) + if transport.get("kind") != "flatpak": continue - flatpak_app_id = transport.get("flatpakAppId") - if not isinstance(flatpak_app_id, str) or not flatpak_app_id: + app_id = transport.get("flatpakAppId") + if not app_id: continue - if flatpak_app_id not in support_cache: - support_cache[flatpak_app_id] = self.flatpak_service.resolve_app_support( - flatpak_app_id - ) - game["flatpakSupport"] = support_cache[flatpak_app_id] + if app_id not in cache: + cache[app_id] = self.flatpak_service.resolve_app_support(app_id) + game["flatpakSupport"] = cache[app_id] return result - async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]: + async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]): return self.configuration_service.update_game_config(appid, game_name, config) - async def reset_game_config(self, appid: str) -> Dict[str, Any]: + async def reset_game_config(self, appid: str): return self.configuration_service.reset_game_config(appid) - async def reset_all_game_configs(self) -> Dict[str, Any]: + async def reset_all_game_configs(self): return self.configuration_service.reset_all_game_configs() - async def get_workaround_state(self, appid: str) -> Dict[str, Any]: + async def get_workaround_state(self, appid: str): return self.wrapper_service.get(appid) async def set_workaround_state( @@ -105,7 +71,7 @@ class Plugin: shortcut_exe: Optional[str] = None, command_token_added: bool = False, transport: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: + ): return self.wrapper_service.set( appid, state, @@ -114,118 +80,82 @@ class Plugin: transport, ) - async def remove_workaround_state(self, appid: str) -> Dict[str, Any]: + async def remove_workaround_state(self, appid: str): return self.wrapper_service.remove(appid) - async def get_config_file_content(self) -> Dict[str, Any]: - """Get the current config file content - - Returns: - Dict containing the config file content or error message - """ + async def get_config_file_content(self): + path = self.configuration_service.config_file_path try: - config_path = self.configuration_service.config_file_path - if not config_path.exists(): + if not path.exists(): return { "success": False, "content": None, - "path": str(config_path), - "error": "Config file does not exist" + "path": str(path), + "error": "Config file does not exist", } - - content = config_path.read_text(encoding='utf-8') return { "success": True, - "content": content, - "path": str(config_path), - "error": None + "content": path.read_text(encoding="utf-8"), + "path": str(path), + "error": None, } - except Exception as e: + except Exception as error: return { "success": False, "content": None, - "path": str(config_path) if 'config_path' in locals() else "unknown", - "error": f"Error reading config file: {str(e)}" + "path": str(path), + "error": f"Error reading config file: {error}", } - async def get_lossless_scaling_branch_status(self) -> Dict[str, Any]: + async def get_lossless_scaling_branch_status(self): return self.steam_service.get_branch_status() - async def get_flatpak_support_status(self) -> Dict[str, Any]: + async def get_flatpak_support_status(self): return self.flatpak_service.get_flatpak_support_status() - async def ensure_flatpak_support(self, flatpak_app_id: str) -> Dict[str, Any]: + async def ensure_flatpak_support(self, flatpak_app_id: str): return self.flatpak_service.ensure_app_support(flatpak_app_id) - async def repair_flatpak_support(self, flatpak_app_id: str) -> Dict[str, Any]: + async def repair_flatpak_support(self, flatpak_app_id: str): return self.flatpak_service.ensure_app_support(flatpak_app_id) - async def set_flatpak_extension_enabled(self, version: str, enabled: bool) -> Dict[str, Any]: + async def set_flatpak_extension_enabled(self, version: str, enabled: bool): return self.flatpak_service.set_extension_enabled(version, enabled) - + async def _main(self): - """ - Main entry point for the plugin. - - This method is called by Decky Loader when the plugin is loaded. - Any initialization code should go here. - """ repair = self.wrapper_service.repair() if not repair.get("success"): decky.logger.error(f"Could not repair lsfg workaround wrapper: {repair.get('error')}") decky.logger.info("decky-lsfg-vk plugin loaded") async def _unload(self): - """ - Cleanup tasks when the plugin is unloaded. - - This method is called by Decky Loader when the plugin is being unloaded. - Any cleanup code should go here. - """ decky.logger.info("decky-lsfg-vk plugin unloaded") async def _uninstall(self): - """ - Called when the plugin is uninstalled. - - This method is called by Decky Loader when the plugin is being uninstalled. - Performs cleanup of plugin files and flatpak extensions. - """ decky.logger.info("decky-lsfg-vk plugin being uninstalled") - - # Clean up lsfg-vk files when the plugin is uninstalled - # Launch integrations are removed with their profiles. Keep the - # generated pass-through wrapper if it is still referenced elsewhere; - # InstallationService only removes files owned by the runtime bundle. self.installation_service.cleanup_on_uninstall() - try: result = self.flatpak_service.remove_plugin_owned_extensions() if not result.get("success"): decky.logger.warning(result.get("error")) except Exception as error: decky.logger.error(f"Error during Flatpak cleanup: {error}") - decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") async def _migration(self): - """ - Migrations that should be performed before entering `_main()`. - - This method is called by Decky Loader for plugin migrations. - Currently migrates logs, settings, and runtime data from old locations. - """ decky.logger.info("Running decky-lsfg-vk plugin migrations") - - decky.migrate_logs(os.path.join(decky.DECKY_USER_HOME, - ".config", "decky-lossless-scaling-vk", "lossless-scaling-vk.log")) - + decky.migrate_logs(os.path.join( + decky.DECKY_USER_HOME, + ".config", + "decky-lossless-scaling-vk", + "lossless-scaling-vk.log", + )) decky.migrate_settings( os.path.join(decky.DECKY_HOME, "settings", "lossless-scaling-vk.json"), - os.path.join(decky.DECKY_USER_HOME, ".config", "decky-lossless-scaling-vk")) - + os.path.join(decky.DECKY_USER_HOME, ".config", "decky-lossless-scaling-vk"), + ) decky.migrate_runtime( os.path.join(decky.DECKY_HOME, "lossless-scaling-vk"), - os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk")) - + os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk"), + ) decky.logger.info("decky-lsfg-vk plugin migrations completed") -- cgit v1.2.3 From de74f0d2499159ed1cf8f628a166302146ae1f13 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 10 Sep 2026 12:58:44 -0400 Subject: fix flatpak disable leftovers --- py_modules/lsfg_vk/plugin.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 071b19b..13df7a4 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -107,6 +107,43 @@ class Plugin: "error": f"Error reading config file: {error}", } + async def get_debug_file_contents(self): + files = ( + ("config", "LSFG-VK configuration", self.configuration_service.config_file_path), + ("workarounds", "Per-app workarounds", self.wrapper_service.sidecar_path), + ("wrapper", "Generated launch wrapper", self.wrapper_service.wrapper_path), + ("flatpak_extensions", "Flatpak extension ownership", self.flatpak_service.ownership_path), + ) + contents = [] + for file_id, label, path in files: + item = { + "id": file_id, + "label": label, + "path": str(path), + "exists": False, + "content": None, + "error": None, + } + try: + if path.is_symlink(): + item["error"] = "Path is a symlink; refusing to read it" + elif not path.exists(): + item["error"] = "File does not exist" + elif not path.is_file(): + item["error"] = "Path is not a regular file" + else: + item["exists"] = True + item["content"] = path.read_text(encoding="utf-8") + except Exception as error: + item["error"] = f"Error reading file: {error}" + contents.append(item) + return { + "success": True, + "message": "Debug file contents retrieved", + "error": None, + "files": contents, + } + async def get_lossless_scaling_branch_status(self): return self.steam_service.get_branch_status() -- cgit v1.2.3 From 769cd7f03990bf1d03c1b57f0929be9c3158e463 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:38:41 -0400 Subject: refactor: expose explicit flatpak preparation --- py_modules/lsfg_vk/plugin.py | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 13df7a4..a253d8a 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -36,21 +36,7 @@ class Plugin: return self.configuration_service.get_game_configs() async def get_installed_games(self): - result = self.steam_service.get_installed_games() - if not result.get("success"): - return result - cache: Dict[str, Dict[str, Any]] = {} - for game in result.get("games", []): - transport = game.get("transport", {}) - if transport.get("kind") != "flatpak": - continue - app_id = transport.get("flatpakAppId") - if not app_id: - continue - if app_id not in cache: - cache[app_id] = self.flatpak_service.resolve_app_support(app_id) - game["flatpakSupport"] = cache[app_id] - return result + return self.steam_service.get_installed_games() async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]): return self.configuration_service.update_game_config(appid, game_name, config) @@ -70,14 +56,12 @@ class Plugin: state: Dict[str, Any], shortcut_exe: Optional[str] = None, command_token_added: bool = False, - transport: Optional[Dict[str, Any]] = None, ): return self.wrapper_service.set( appid, state, shortcut_exe, command_token_added, - transport, ) async def remove_workaround_state(self, appid: str): @@ -112,7 +96,7 @@ class Plugin: ("config", "LSFG-VK configuration", self.configuration_service.config_file_path), ("workarounds", "Per-app workarounds", self.wrapper_service.sidecar_path), ("wrapper", "Generated launch wrapper", self.wrapper_service.wrapper_path), - ("flatpak_extensions", "Flatpak extension ownership", self.flatpak_service.ownership_path), + ("flatpak", "Flatpak ownership state", self.flatpak_service.ownership_path), ) contents = [] for file_id, label, path in files: @@ -150,11 +134,14 @@ class Plugin: async def get_flatpak_support_status(self): return self.flatpak_service.get_flatpak_support_status() - async def ensure_flatpak_support(self, flatpak_app_id: str): - return self.flatpak_service.ensure_app_support(flatpak_app_id) + async def get_flatpak_apps(self): + return self.flatpak_service.get_flatpak_apps() - async def repair_flatpak_support(self, flatpak_app_id: str): - return self.flatpak_service.ensure_app_support(flatpak_app_id) + async def prepare_flatpak_app(self, flatpak_app_id: str): + return self.flatpak_service.prepare_app(flatpak_app_id) + + async def remove_flatpak_app(self, flatpak_app_id: str): + return self.flatpak_service.remove_app_override(flatpak_app_id) async def set_flatpak_extension_enabled(self, version: str, enabled: bool): return self.flatpak_service.set_extension_enabled(version, enabled) @@ -170,13 +157,13 @@ class Plugin: async def _uninstall(self): decky.logger.info("decky-lsfg-vk plugin being uninstalled") - self.installation_service.cleanup_on_uninstall() try: - result = self.flatpak_service.remove_plugin_owned_extensions() + result = self.flatpak_service.remove_plugin_owned_environment() if not result.get("success"): decky.logger.warning(result.get("error")) except Exception as error: decky.logger.error(f"Error during Flatpak cleanup: {error}") + self.installation_service.cleanup_on_uninstall() decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") async def _migration(self): -- cgit v1.2.3 From 868438873136c1b0a6c323b1a55e69e6b2f855ca Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:41:19 -0400 Subject: refactor: simplify workaround state api --- py_modules/lsfg_vk/plugin.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index a253d8a..854800a 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict, Optional +from typing import Any, Dict import decky @@ -54,15 +54,9 @@ class Plugin: self, appid: str, state: Dict[str, Any], - shortcut_exe: Optional[str] = None, command_token_added: bool = False, ): - return self.wrapper_service.set( - appid, - state, - shortcut_exe, - command_token_added, - ) + return self.wrapper_service.set(appid, state, command_token_added) async def remove_workaround_state(self, appid: str): return self.wrapper_service.remove(appid) -- cgit v1.2.3 From c448f8b703c293c78ffe82c5f10c242ca47d751a Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:55:49 -0400 Subject: refactor: make flatpak lifecycle app-centric --- py_modules/lsfg_vk/plugin.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 854800a..e00788c 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -30,6 +30,14 @@ class Plugin: return self.installation_service.check_installation() async def uninstall_lsfg_vk(self): + flatpak = self.flatpak_service.remove_plugin_owned_environment() + if not flatpak.get("success"): + return { + "success": False, + "message": "", + "error": flatpak.get("error") or "Could not clean up Flatpak support", + "removed_files": None, + } return self.installation_service.uninstall() async def get_game_configs(self): @@ -125,9 +133,6 @@ class Plugin: async def get_lossless_scaling_branch_status(self): return self.steam_service.get_branch_status() - async def get_flatpak_support_status(self): - return self.flatpak_service.get_flatpak_support_status() - async def get_flatpak_apps(self): return self.flatpak_service.get_flatpak_apps() @@ -137,9 +142,6 @@ class Plugin: async def remove_flatpak_app(self, flatpak_app_id: str): return self.flatpak_service.remove_app_override(flatpak_app_id) - async def set_flatpak_extension_enabled(self, version: str, enabled: bool): - return self.flatpak_service.set_extension_enabled(version, enabled) - async def _main(self): repair = self.wrapper_service.repair() if not repair.get("success"): -- cgit v1.2.3 From 7b54ba042d32cd67618703ff5c5ada0ae5dccb36 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 10 Sep 2026 16:30:35 -0400 Subject: feat: expose flatpak profile APIs --- py_modules/lsfg_vk/plugin.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index e00788c..d20fabf 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -4,6 +4,7 @@ from typing import Any, Dict import decky from .configuration import ConfigurationService +from .flatpak_profile_service import FlatpakProfileService from .flatpak_service import FlatpakService from .installation import InstallationService from .runtime_service import RuntimeService @@ -21,6 +22,10 @@ class Plugin: ) self.configuration_service = ConfigurationService(runtime_service=self.runtime_service) self.flatpak_service = FlatpakService() + self.flatpak_profile_service = FlatpakProfileService( + self.flatpak_service, + self.configuration_service, + ) self.wrapper_service = WrapperService() async def install_lsfg_vk(self): @@ -38,6 +43,7 @@ class Plugin: "error": flatpak.get("error") or "Could not clean up Flatpak support", "removed_files": None, } + self.configuration_service.reset_all_flatpak_configs() return self.installation_service.uninstall() async def get_game_configs(self): @@ -134,13 +140,25 @@ class Plugin: return self.steam_service.get_branch_status() async def get_flatpak_apps(self): - return self.flatpak_service.get_flatpak_apps() + return self.flatpak_profile_service.get_apps() + + async def enable_flatpak_app(self, flatpak_app_id: str): + return self.flatpak_profile_service.enable_app(flatpak_app_id) + + async def update_flatpak_config(self, flatpak_app_id: str, config: Dict[str, Any]): + return self.flatpak_profile_service.update_config(flatpak_app_id, config) - async def prepare_flatpak_app(self, flatpak_app_id: str): - return self.flatpak_service.prepare_app(flatpak_app_id) + async def get_flatpak_workaround_state(self, flatpak_app_id: str): + return self.flatpak_profile_service.get_workaround_state(flatpak_app_id) + + async def set_flatpak_workaround_state(self, flatpak_app_id: str, state: Dict[str, Any]): + return self.flatpak_profile_service.set_workaround_state(flatpak_app_id, state) async def remove_flatpak_app(self, flatpak_app_id: str): - return self.flatpak_service.remove_app_override(flatpak_app_id) + return self.flatpak_profile_service.remove_app(flatpak_app_id) + + async def get_running_flatpak_apps(self): + return self.flatpak_profile_service.get_running_apps() async def _main(self): repair = self.wrapper_service.repair() @@ -155,7 +173,9 @@ class Plugin: decky.logger.info("decky-lsfg-vk plugin being uninstalled") try: result = self.flatpak_service.remove_plugin_owned_environment() - if not result.get("success"): + if result.get("success"): + self.configuration_service.reset_all_flatpak_configs() + else: decky.logger.warning(result.get("error")) except Exception as error: decky.logger.error(f"Error during Flatpak cleanup: {error}") -- cgit v1.2.3 From 954b2abc47d8772211cb8ecee523900f823184e8 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 11:08:12 -0400 Subject: better uninstall cleanup --- py_modules/lsfg_vk/plugin.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index d20fabf..918c3f8 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -34,21 +34,35 @@ class Plugin: async def check_lsfg_vk_installed(self): return self.installation_service.check_installation() - async def uninstall_lsfg_vk(self): + def _cleanup_runtime_state(self): flatpak = self.flatpak_service.remove_plugin_owned_environment() if not flatpak.get("success"): + return flatpak.get("error") or "Could not clean up Flatpak support" + profiles = self.configuration_service.reset_all_flatpak_configs() + if not profiles.get("success"): + return profiles.get("error") or "Could not remove Flatpak profiles" + wrapper = self.wrapper_service.purge() + if not wrapper.get("success"): + return wrapper.get("error") or "Could not remove workaround state" + return None + + async def uninstall_lsfg_vk(self): + error = self._cleanup_runtime_state() + if error: return { "success": False, "message": "", - "error": flatpak.get("error") or "Could not clean up Flatpak support", + "error": error, "removed_files": None, } - self.configuration_service.reset_all_flatpak_configs() return self.installation_service.uninstall() async def get_game_configs(self): return self.configuration_service.get_game_configs() + async def update_global_config(self, config: Dict[str, Any]): + return self.configuration_service.update_global_config(config) + async def get_installed_games(self): return self.steam_service.get_installed_games() @@ -64,13 +78,17 @@ class Plugin: async def get_workaround_state(self, appid: str): return self.wrapper_service.get(appid) + async def get_workaround_apps(self): + return self.wrapper_service.list_apps() + async def set_workaround_state( self, appid: str, state: Dict[str, Any], command_token_added: bool = False, + non_steam: bool = False, ): - return self.wrapper_service.set(appid, state, command_token_added) + return self.wrapper_service.set(appid, state, command_token_added, non_steam) async def remove_workaround_state(self, appid: str): return self.wrapper_service.remove(appid) @@ -172,13 +190,13 @@ class Plugin: async def _uninstall(self): decky.logger.info("decky-lsfg-vk plugin being uninstalled") try: - result = self.flatpak_service.remove_plugin_owned_environment() - if result.get("success"): - self.configuration_service.reset_all_flatpak_configs() - else: - decky.logger.warning(result.get("error")) + error = self._cleanup_runtime_state() + if error: + decky.logger.warning(f"Preserving lsfg-vk files because uninstall cleanup failed: {error}") + return except Exception as error: - decky.logger.error(f"Error during Flatpak cleanup: {error}") + decky.logger.error(f"Error during lsfg-vk cleanup: {error}") + return self.installation_service.cleanup_on_uninstall() decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed") -- cgit v1.2.3 From c7ca9c7ec2660d279f5e6957ef882390a8783667 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 11:47:16 -0400 Subject: handle decky uninstall to avoid unlaunchable game wrappers --- py_modules/lsfg_vk/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 918c3f8..a1fd40f 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -34,14 +34,14 @@ class Plugin: async def check_lsfg_vk_installed(self): return self.installation_service.check_installation() - def _cleanup_runtime_state(self): + def _cleanup_runtime_state(self, preserve_wrapper: bool = False): flatpak = self.flatpak_service.remove_plugin_owned_environment() if not flatpak.get("success"): return flatpak.get("error") or "Could not clean up Flatpak support" profiles = self.configuration_service.reset_all_flatpak_configs() if not profiles.get("success"): return profiles.get("error") or "Could not remove Flatpak profiles" - wrapper = self.wrapper_service.purge() + wrapper = self.wrapper_service.neutralize() if preserve_wrapper else self.wrapper_service.purge() if not wrapper.get("success"): return wrapper.get("error") or "Could not remove workaround state" return None @@ -190,7 +190,7 @@ class Plugin: async def _uninstall(self): decky.logger.info("decky-lsfg-vk plugin being uninstalled") try: - error = self._cleanup_runtime_state() + error = self._cleanup_runtime_state(preserve_wrapper=True) if error: decky.logger.warning(f"Preserving lsfg-vk files because uninstall cleanup failed: {error}") return -- cgit v1.2.3 From f3074fbe1427411dc3b5597d2e87918383299e3f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Sep 2026 13:47:52 -0400 Subject: feat: non steam tab, faster bulk actions --- py_modules/lsfg_vk/plugin.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'py_modules/lsfg_vk/plugin.py') diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index a1fd40f..c63e3a9 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -72,6 +72,9 @@ class Plugin: async def reset_game_config(self, appid: str): return self.configuration_service.reset_game_config(appid) + async def reset_game_configs(self, appids): + return self.configuration_service.reset_game_configs(appids) + async def reset_all_game_configs(self): return self.configuration_service.reset_all_game_configs() -- cgit v1.2.3