summaryrefslogtreecommitdiff
path: root/py_modules/lsfg_vk
diff options
context:
space:
mode:
authorxXJsonDeruloXx <danielhimebauch@gmail.com>2025-10-21 22:04:39 -0400
committerxXJsonDeruloXx <danielhimebauch@gmail.com>2025-10-21 22:04:39 -0400
commit450e70a16310b18ae62ec4cb74a11b23197bb529 (patch)
treeab39a553c57c0b4f12dce1b87d0ca2fb214c2537 /py_modules/lsfg_vk
parentf32c8760d15bf4713b3f9af1384219a44322febd (diff)
downloaddecky-lsfg-vk-450e70a16310b18ae62ec4cb74a11b23197bb529.tar.gz
decky-lsfg-vk-450e70a16310b18ae62ec4cb74a11b23197bb529.zip
rm more comments, ill recomb for myself later
Diffstat (limited to 'py_modules/lsfg_vk')
-rw-r--r--py_modules/lsfg_vk/__init__.py3
-rw-r--r--py_modules/lsfg_vk/base_service.py10
-rw-r--r--py_modules/lsfg_vk/config_schema_generated.py4
-rw-r--r--py_modules/lsfg_vk/configuration.py45
-rw-r--r--py_modules/lsfg_vk/constants.py8
-rw-r--r--py_modules/lsfg_vk/dll_detection.py15
-rw-r--r--py_modules/lsfg_vk/flatpak_service.py34
-rw-r--r--py_modules/lsfg_vk/installation.py8
-rw-r--r--py_modules/lsfg_vk/plugin.py67
9 files changed, 13 insertions, 181 deletions
diff --git a/py_modules/lsfg_vk/__init__.py b/py_modules/lsfg_vk/__init__.py
index 8343853..31c553b 100644
--- a/py_modules/lsfg_vk/__init__.py
+++ b/py_modules/lsfg_vk/__init__.py
@@ -5,10 +5,9 @@ This package provides services for installing and managing the lsfg-vk
Vulkan layer for Lossless Scaling frame generation.
"""
-# Import will be available once plugin.py exists
try:
from .plugin import Plugin
__all__ = ['Plugin']
except ImportError:
- # During development, plugin may not exist yet
__all__ = []
+
diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py
index 9c3dec3..262e2b0 100644
--- a/py_modules/lsfg_vk/base_service.py
+++ b/py_modules/lsfg_vk/base_service.py
@@ -12,7 +12,6 @@ import decky
from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME, CONFIG_DIR, CONFIG_FILENAME
-# Generic type for response dictionaries
ResponseType = TypeVar('ResponseType', bound=Dict[str, Any])
@@ -30,12 +29,11 @@ class BaseService:
else:
self.log = logger
- # Initialize common paths using pathlib
self.user_home = Path.home()
self.local_lib_dir = self.user_home / LOCAL_LIB
self.local_share_dir = self.user_home / VULKAN_LAYER_DIR
self.lsfg_script_path = self.user_home / SCRIPT_NAME
- self.lsfg_launch_script_path = self.user_home / SCRIPT_NAME # ~/lsfg launch script
+ self.lsfg_launch_script_path = self.user_home / SCRIPT_NAME
self.config_dir = self.user_home / CONFIG_DIR
self.config_file_path = self.config_dir / CONFIG_FILENAME
@@ -82,13 +80,11 @@ class BaseService:
OSError: If write fails
"""
try:
- # Write directly to the file
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
- f.flush() # Ensure data is written to disk
- os.fsync(f.fileno()) # Force filesystem sync
+ f.flush()
+ os.fsync(f.fileno())
- # Set permissions
path.chmod(mode)
self.log.info(f"Wrote to {path}")
diff --git a/py_modules/lsfg_vk/config_schema_generated.py b/py_modules/lsfg_vk/config_schema_generated.py
index 53e9693..3aa5f68 100644
--- a/py_modules/lsfg_vk/config_schema_generated.py
+++ b/py_modules/lsfg_vk/config_schema_generated.py
@@ -8,11 +8,9 @@ from enum import Enum
import sys
from pathlib import Path
-# Import shared configuration constants
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from shared_config import CONFIG_SCHEMA_DEF, ConfigFieldType
-# Field name constants for type-safe access
DLL = "dll"
NO_FP16 = "no_fp16"
MULTIPLIER = "multiplier"
@@ -63,7 +61,6 @@ def get_script_parsing_logic():
key = key.strip()
value = value.strip()
- # Auto-generated parsing logic:
if key == "DXVK_FRAME_RATE":
try:
script_values["dxvk_frame_rate"] = int(value)
@@ -162,7 +159,6 @@ def create_config_dict(**kwargs) -> ConfigurationData:
})
-# Field lists for dynamic operations
TOML_FIELDS = ['dll', 'no_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode']
SCRIPT_FIELDS = ['dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
ALL_FIELDS = ['dll', 'no_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index b61a06d..13fa925 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -22,9 +22,7 @@ class ConfigurationService(BaseService):
ConfigurationResponse with current configuration or error
"""
try:
- # Get TOML configuration (with defaults if file doesn't exist)
if not self.config_file_path.exists():
- # Return default configuration with DLL detection if file doesn't exist
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
toml_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -32,7 +30,6 @@ class ConfigurationService(BaseService):
content = self.config_file_path.read_text(encoding='utf-8')
toml_config = ConfigurationManager.parse_toml_content(content)
- # Get script environment variables (if script exists)
script_values = {}
if self.lsfg_script_path.exists():
try:
@@ -42,7 +39,6 @@ class ConfigurationService(BaseService):
except Exception as e:
self.log.warning(f"Failed to parse launch script: {str(e)}")
- # Merge TOML config with script values
config = ConfigurationManager.merge_config_with_script(toml_config, script_values)
return self._success_response(ConfigurationResponse, config=config)
@@ -54,7 +50,6 @@ class ConfigurationService(BaseService):
except Exception as e:
error_msg = f"Error parsing config file: {str(e)}"
self.log.error(error_msg)
- # Return defaults with DLL detection if parsing fails
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -75,7 +70,6 @@ class ConfigurationService(BaseService):
profile_data = self._get_profile_data()
current_profile = profile_data["current_profile"]
- # Update the current profile's config
return self.update_profile_config(current_profile, config)
except (OSError, IOError) as e:
@@ -97,10 +91,8 @@ class ConfigurationService(BaseService):
ConfigurationResponse with success status
"""
try:
- # Create configuration from keyword arguments using generated function
config = ConfigurationManager.create_config_from_args(**kwargs)
- # Update using the new profile-aware method
return self.update_config_from_dict(config)
except (OSError, IOError) as e:
@@ -124,18 +116,14 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Update global config (DLL path is global)
profile_data["global_config"]["dll"] = dll_path
- # Also update current profile's config for backward compatibility
current_profile = profile_data["current_profile"]
from .config_schema_generated import DLL
profile_data["profiles"][current_profile][DLL] = dll_path
- # Save to file
self._save_profile_data(profile_data)
- # Update launch script
script_result = self.update_lsfg_script_from_profile_data(profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -163,7 +151,6 @@ class ConfigurationService(BaseService):
try:
script_content = self._generate_script_content(config)
- # Write the script file
self._write_file(self.lsfg_script_path, script_content, 0o755)
self.log.info(f"Updated lsfg launch script at {self.lsfg_script_path}")
@@ -188,15 +175,11 @@ class ConfigurationService(BaseService):
"""
lines = [
"#!/bin/bash",
- "# lsfg-vk launch script generated by decky-lossless-scaling-vk plugin",
- "# This script sets up the environment for lsfg-vk to work with the plugin configuration"
]
- # Use auto-generated script generation logic
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(config))
- # Always add the LSFG_PROCESS export and execution line
lines.extend([
"export LSFG_PROCESS=decky-lsfg-vk",
'exec "$@"'
@@ -216,23 +199,18 @@ class ConfigurationService(BaseService):
current_profile = profile_data["current_profile"]
config = profile_data["profiles"].get(current_profile, ConfigurationManager.get_defaults())
- # Merge global config with profile config
merged_config = dict(config)
for field_name, value in profile_data["global_config"].items():
merged_config[field_name] = value
lines = [
"#!/bin/bash",
- "# lsfg-vk launch script generated by decky-lossless-scaling-vk plugin",
f"# Current profile: {current_profile}",
- "# This script sets up the environment for lsfg-vk to work with the plugin configuration"
]
- # Use auto-generated script generation logic
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(merged_config))
- # Export LSFG_PROCESS with current profile name
lines.extend([
f"export LSFG_PROCESS={current_profile}",
'exec "$@"'
@@ -243,7 +221,6 @@ class ConfigurationService(BaseService):
def _get_profile_data(self) -> ProfileData:
"""Get current profile data from config file"""
if not self.config_file_path.exists():
- # Return default profile structure if file doesn't exist
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
default_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -263,13 +240,10 @@ class ConfigurationService(BaseService):
"""Save profile data to config file"""
toml_content = ConfigurationManager.generate_toml_content_multi_profile(profile_data)
- # Ensure config directory exists
self.config_dir.mkdir(parents=True, exist_ok=True)
- # Write the updated config directly to preserve inode for file watchers
self._write_file(self.config_file_path, toml_content, 0o644)
- # Profile management methods
def get_profiles(self) -> ProfilesResponse:
"""Get list of all profiles and current profile
@@ -303,14 +277,11 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Use current profile as source if not specified
if not source_profile:
source_profile = profile_data["current_profile"]
- # Create the new profile
new_profile_data = ConfigurationManager.create_profile(profile_data, profile_name, source_profile)
- # Save to file
self._save_profile_data(new_profile_data)
self.log.info(f"Created profile '{profile_name}' from '{source_profile}'")
@@ -340,13 +311,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Delete the profile
new_profile_data = ConfigurationManager.delete_profile(profile_data, profile_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script if current profile changed
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -379,13 +347,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Rename the profile
new_profile_data = ConfigurationManager.rename_profile(profile_data, old_name, new_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script if current profile changed
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -417,13 +382,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Set current profile
new_profile_data = ConfigurationManager.set_current_profile(profile_data, profile_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script with new current profile
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -461,24 +423,19 @@ class ConfigurationService(BaseService):
f"Profile '{profile_name}' does not exist",
config=None)
- # Update the profile's config
- profile_data["profiles"][profile_name] = config
+ new_profile_data = ConfigurationManager.update_profile_config(profile_data, profile_name, config)
- # Update global config fields if they're in the config
for field_name in ["dll", "no_fp16"]:
if field_name in config:
profile_data["global_config"][field_name] = config[field_name]
- # Save to file
self._save_profile_data(profile_data)
- # Update launch script if this is the current profile
if profile_name == profile_data["current_profile"]:
script_result = self.update_lsfg_script_from_profile_data(profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
- # Log with dynamic field listing
field_values = ", ".join(f"{k}={repr(v)}" for k, v in config.items())
self.log.info(f"Updated profile '{profile_name}' configuration: {field_values}")
diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py
index 79d8585..3d8e44a 100644
--- a/py_modules/lsfg_vk/constants.py
+++ b/py_modules/lsfg_vk/constants.py
@@ -4,36 +4,30 @@ Constants for the lsfg-vk plugin.
from pathlib import Path
-# Directory paths
LOCAL_LIB = ".local/lib"
LOCAL_SHARE_BASE = ".local/share"
VULKAN_LAYER_DIR = ".local/share/vulkan/implicit_layer.d"
CONFIG_DIR = ".config/lsfg-vk"
-# File names
SCRIPT_NAME = "lsfg"
CONFIG_FILENAME = "conf.toml"
LIB_FILENAME = "liblsfg-vk.so"
JSON_FILENAME = "VkLayer_LS_frame_generation.json"
ZIP_FILENAME = "lsfg-vk_noui.zip"
-# Flatpak files
FLATPAK_23_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_23.08.flatpak"
FLATPAK_24_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_24.08.flatpak"
FLATPAK_25_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak"
-# File extensions
SO_EXT = ".so"
JSON_EXT = ".json"
-# Directory for the zip file
BIN_DIR = "bin"
-# Lossless Scaling paths
STEAM_COMMON_PATH = Path("steamapps/common/Lossless Scaling")
LOSSLESS_DLL_NAME = "Lossless.dll"
-# Environment variable names
ENV_LSFG_DLL_PATH = "LSFG_DLL_PATH"
ENV_XDG_DATA_HOME = "XDG_DATA_HOME"
ENV_HOME = "HOME"
+
diff --git a/py_modules/lsfg_vk/dll_detection.py b/py_modules/lsfg_vk/dll_detection.py
index e547405..f7ba444 100644
--- a/py_modules/lsfg_vk/dll_detection.py
+++ b/py_modules/lsfg_vk/dll_detection.py
@@ -31,27 +31,22 @@ class DllDetectionService(BaseService):
DllDetectionResponse with detection status and path information
"""
try:
- # Check environment variable first
dll_path = self._check_env_dll_path()
if dll_path:
return dll_path
- # Check XDG_DATA_HOME path
xdg_path = self._check_xdg_data_home()
if xdg_path:
return xdg_path
- # Check HOME/.local/share path
home_path = self._check_home_local_share()
if home_path:
return home_path
- # Check all Steam library folders (including SD cards)
steam_libraries_path = self._check_steam_library_folders()
if steam_libraries_path:
return steam_libraries_path
- # DLL not found in any expected location
return {
"detected": False,
"path": None,
@@ -164,25 +159,20 @@ class DllDetectionService(BaseService):
"""
library_paths = []
- # Try different possible Steam installation locations
steam_paths = []
- # XDG_DATA_HOME path
data_dir = os.getenv(ENV_XDG_DATA_HOME)
if data_dir and data_dir.strip():
steam_paths.append(Path(data_dir.strip()) / "Steam")
- # HOME/.local/share path (most common on Steam Deck)
home_dir = os.getenv(ENV_HOME)
if home_dir and home_dir.strip():
steam_paths.append(Path(home_dir.strip()) / ".local" / "share" / "Steam")
for steam_path in steam_paths:
if steam_path.exists():
- # Add the main Steam directory as a library
library_paths.append(str(steam_path))
- # Parse libraryfolders.vdf for additional libraries
vdf_path = steam_path / "steamapps" / "libraryfolders.vdf"
if vdf_path.exists():
try:
@@ -191,7 +181,6 @@ class DllDetectionService(BaseService):
except Exception as e:
self.log.warning(f"Failed to parse {vdf_path}: {str(e)}")
- # Remove duplicates while preserving order
seen = set()
unique_paths = []
for path in library_paths:
@@ -217,17 +206,13 @@ class DllDetectionService(BaseService):
with open(vdf_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
- # Look for "path" entries in the VDF file
- # The format is typically: "path" "/path/to/library"
path_pattern = r'"path"\s*"([^"]+)"'
matches = re.findall(path_pattern, content, re.IGNORECASE)
for path_match in matches:
- # Convert Windows paths to Unix paths if needed
path = path_match.replace('\\\\', '/').replace('\\', '/')
library_path = Path(path)
- # Verify the library folder exists and has a steamapps directory
if library_path.exists() and (library_path / "steamapps").exists():
library_paths.append(str(library_path))
self.log.info(f"Found additional Steam library: {library_path}")
diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py
index 0e3977f..c9be0ec 100644
--- a/py_modules/lsfg_vk/flatpak_service.py
+++ b/py_modules/lsfg_vk/flatpak_service.py
@@ -50,22 +50,18 @@ class FlatpakService(BaseService):
self.extension_id_23_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/23.08"
self.extension_id_24_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/24.08"
self.extension_id_25_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/25.08"
- self.flatpak_command = None # Will be set when flatpak is detected
+ self.flatpak_command = None
def _get_clean_env(self):
"""Get a clean environment without PyInstaller's bundled libraries"""
- # Create a clean environment without PyInstaller's bundled libraries
env = os.environ.copy()
- # Remove LD_LIBRARY_PATH that might point to PyInstaller's bundled libs
if 'LD_LIBRARY_PATH' in env:
del env['LD_LIBRARY_PATH']
- # Ensure PATH includes standard binary locations
standard_paths = ['/usr/bin', '/usr/local/bin', '/bin']
current_path = env.get('PATH', '')
- # Add standard paths if they're not already there
path_parts = current_path.split(':') if current_path else []
for std_path in standard_paths:
if std_path not in path_parts:
@@ -82,21 +78,17 @@ class FlatpakService(BaseService):
env = self._get_clean_env()
- # Log environment info for debugging
self.log.info(f"Running flatpak with PATH: {env.get('PATH')}")
self.log.info(f"LD_LIBRARY_PATH removed: {'LD_LIBRARY_PATH' not in env}")
- # Run the command with the clean environment
return subprocess.run([self.flatpak_command] + args, env=env, **kwargs)
def check_flatpak_available(self) -> bool:
"""Check if flatpak command is available and store the working command"""
- # Log environment info for debugging
self.log.info(f"PATH: {os.environ.get('PATH', 'Not set')}")
self.log.info(f"HOME: {os.environ.get('HOME', 'Not set')}")
self.log.info(f"USER: {os.environ.get('USER', 'Not set')}")
- # Try common flatpak installation paths, starting with the standard command
flatpak_paths = [
"flatpak",
"/usr/bin/flatpak",
@@ -132,7 +124,6 @@ class FlatpakService(BaseService):
error_msg,
installed_23_08=False, installed_24_08=False, installed_25_08=False)
- # Get list of installed runtimes
result = self._run_flatpak_command(
["list", "--runtime"],
capture_output=True, text=True, check=True
@@ -140,7 +131,6 @@ class FlatpakService(BaseService):
installed_runtimes = result.stdout
- # Check for all versions by looking for the base extension name and version
base_extension_name = "org.freedesktop.Platform.VulkanLayer.lsfgvk"
installed_23_08 = False
installed_24_08 = False
@@ -187,20 +177,18 @@ class FlatpakService(BaseService):
if not self.check_flatpak_available():
return self._error_response(BaseResponse, "Flatpak is not available on this system")
- # Get the path to the flatpak file
plugin_dir = Path(__file__).parent.parent.parent
if version == "23.08":
filename = FLATPAK_23_08_FILENAME
elif version == "24.08":
filename = FLATPAK_24_08_FILENAME
- else: # 25.08
+ else:
filename = FLATPAK_25_08_FILENAME
flatpak_path = plugin_dir / BIN_DIR / filename
if not flatpak_path.exists():
return self._error_response(BaseResponse, f"Flatpak file not found: {flatpak_path}")
- # Install the extension
result = self._run_flatpak_command(
["install", "--user", "--noninteractive", str(flatpak_path)],
capture_output=True, text=True
@@ -232,10 +220,9 @@ class FlatpakService(BaseService):
extension_id = self.extension_id_23_08
elif version == "24.08":
extension_id = self.extension_id_24_08
- else: # 25.08
+ else:
extension_id = self.extension_id_25_08
- # Uninstall the extension
result = self._run_flatpak_command(
["uninstall", "--user", "--noninteractive", extension_id],
capture_output=True, text=True
@@ -265,7 +252,6 @@ class FlatpakService(BaseService):
error_msg,
apps=[], total_apps=0)
- # Get list of installed apps
result = self._run_flatpak_command(
["list", "--app"],
capture_output=True, text=True, check=True
@@ -276,7 +262,6 @@ class FlatpakService(BaseService):
if not line.strip():
continue
- # Parse flatpak list output (Name\tApp ID\tVersion\tBranch\tInstallation)
parts = line.split('\t')
if len(parts) >= 2:
app_name = parts[0].strip()
@@ -318,9 +303,6 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # More precise checking - look for exact filesystem entries
- # Flatpak output format typically shows filesystem entries like:
- # filesystems=/path1;/path2;/path3
filesystem_section = ""
in_context = False
@@ -334,15 +316,12 @@ class FlatpakService(BaseService):
filesystem_section = line
break
- # Check each required filesystem override
has_config_fs = config_path in filesystem_section
has_dll_fs = dll_path in filesystem_section
has_lsfg_fs = lsfg_path in filesystem_section
- # All three filesystem overrides must be present
filesystem_override = has_config_fs and has_dll_fs and has_lsfg_fs
- # Check for environment override in the [Environment] section
env_override = False
in_environment = False
@@ -377,14 +356,12 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # Set all filesystem overrides
filesystem_overrides = [
f"--filesystem={dll_path}",
f"--filesystem={config_path}:rw",
f"--filesystem={lsfg_path}:rw"
]
- # Apply filesystem overrides
for override in filesystem_overrides:
result = self._run_flatpak_command(
["override", "--user", override, app_id],
@@ -395,7 +372,6 @@ class FlatpakService(BaseService):
return self._error_response(FlatpakOverrideResponse, error_msg,
app_id=app_id, operation="set")
- # Set environment override
result = self._run_flatpak_command(
["override", "--user", f"--env=LSFG_CONFIG={config_path}/conf.toml", app_id],
capture_output=True, text=True
@@ -430,7 +406,6 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # First, try to reset all overrides for this app to clean slate
reset_result = self._run_flatpak_command(
["override", "--user", "--reset", app_id],
capture_output=True, text=True
@@ -442,10 +417,8 @@ class FlatpakService(BaseService):
f"All overrides reset for {app_id}",
app_id=app_id, operation="remove")
- # If reset fails, try individual removal (fallback)
self.log.debug(f"Reset failed, trying individual removal: {reset_result.stderr}")
- # Remove all filesystem overrides individually
filesystem_overrides = [
f"--nofilesystem={dll_path}",
f"--nofilesystem={config_path}",
@@ -463,7 +436,6 @@ class FlatpakService(BaseService):
if result.returncode != 0:
removal_errors.append(f"{override}: {result.stderr}")
- # Remove environment override
result = self._run_flatpak_command(
["override", "--user", "--unset-env=LSFG_CONFIG", app_id],
capture_output=True, text=True
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index a3b000f..4329d49 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -26,7 +26,6 @@ class InstallationService(BaseService):
def __init__(self, logger=None):
super().__init__(logger)
- # File paths using constants
self.lib_file = self.local_lib_dir / LIB_FILENAME
self.json_file = self.local_share_dir / JSON_FILENAME
@@ -37,26 +36,20 @@ class InstallationService(BaseService):
InstallationResponse with success status and message/error
"""
try:
- # Get the path to the zip file - need to go up to plugin root from py_modules/lsfg_vk/
plugin_dir = Path(__file__).parent.parent.parent
zip_path = plugin_dir / BIN_DIR / ZIP_FILENAME
- # Check if the zip file exists
if not zip_path.exists():
error_msg = f"{ZIP_FILENAME} not found at {zip_path}"
self.log.error(error_msg)
return self._error_response(InstallationResponse, error_msg, message="")
- # Create directories if they don't exist
self._ensure_directories()
- # Extract and install files
self._extract_and_install_files(zip_path)
- # Create the config file
self._create_config_file()
- # Create the lsfg launch script
self._create_lsfg_launch_script()
self.log.info("lsfg-vk installed successfully")
@@ -67,7 +60,6 @@ class InstallationService(BaseService):
self.log.error(error_msg)
return self._error_response(InstallationResponse, str(e), message="")
except Exception as e:
- # Catch unexpected errors but log them separately
error_msg = f"Unexpected error installing lsfg-vk: {str(e)}"
self.log.error(error_msg)
return self._error_response(InstallationResponse, str(e), message="")
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 7731558..09128a1 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -34,13 +34,11 @@ class Plugin:
def __init__(self):
"""Initialize the plugin with all necessary services"""
- # Initialize services - they will use decky.logger by default
self.installation_service = InstallationService()
self.dll_detection_service = DllDetectionService()
self.configuration_service = ConfigurationService()
self.flatpak_service = FlatpakService()
- # Installation methods
async def install_lsfg_vk(self) -> Dict[str, Any]:
"""Install lsfg-vk by extracting the zip file to ~/.local
@@ -65,7 +63,6 @@ class Plugin:
"""
return self.installation_service.uninstall()
- # DLL detection methods
async def check_lossless_scaling_dll(self) -> Dict[str, Any]:
"""Check if Lossless Scaling DLL is available at the expected paths
@@ -85,14 +82,12 @@ class Plugin:
"""
result = self.dll_detection_service.check_lossless_scaling_dll()
- # Convert to dict to allow modification
result_dict = dict(result)
- # If DLL was detected, automatically update the configuration
if result.get("detected") and result.get("path"):
try:
dll_path = result["path"]
- if dll_path: # Type guard
+ if dll_path:
update_result = self.configuration_service.update_dll_path(dll_path)
if update_result.get("success"):
result_dict["config_updated"] = True
@@ -113,7 +108,6 @@ class Plugin:
Dict containing DLL path, SHA256 hash, and other stats
"""
try:
- # First check if DLL is detected
dll_result = self.dll_detection_service.check_lossless_scaling_dll()
if not dll_result.get("detected") or not dll_result.get("path"):
@@ -135,11 +129,9 @@ class Plugin:
dll_path_obj = Path(dll_path)
- # Calculate SHA256 hash
sha256_hash = hashlib.sha256()
try:
with open(dll_path_obj, "rb") as f:
- # Read file in chunks to handle large files efficiently
for chunk in iter(lambda: f.read(4096), b""):
sha256_hash.update(chunk)
dll_sha256 = sha256_hash.hexdigest()
@@ -167,7 +159,6 @@ class Plugin:
"dll_sha256": None
}
- # Configuration methods
async def get_lsfg_config(self) -> Dict[str, Any]:
"""Read current lsfg script configuration
@@ -183,7 +174,6 @@ class Plugin:
Dict with field names, types, defaults, and profile information
"""
try:
- # Get profile information
profiles_response = self.configuration_service.get_profiles()
schema_data = {
@@ -192,7 +182,6 @@ class Plugin:
"defaults": ConfigurationManager.get_defaults()
}
- # Add profile information if available
if profiles_response.get("success"):
schema_data["profiles"] = profiles_response.get("profiles", [])
schema_data["current_profile"] = profiles_response.get("current_profile")
@@ -203,7 +192,6 @@ class Plugin:
return schema_data
except (ValueError, KeyError, AttributeError) as e:
- # Fallback to basic schema without profile info
self.configuration_service.log.warning(f"Failed to get full schema, using fallback: {e}")
return {
"field_names": ConfigurationManager.get_field_names(),
@@ -222,10 +210,8 @@ class Plugin:
Returns:
ConfigurationResponse dict with success status
"""
- # Validate and extract configuration from the config dict
validated_config = ConfigurationManager.validate_config(config)
- # Use dynamic parameter passing based on schema
return self.configuration_service.update_config_from_dict(validated_config)
async def update_dll_path(self, dll_path: str) -> Dict[str, Any]:
@@ -239,7 +225,6 @@ class Plugin:
"""
return self.configuration_service.update_dll_path(dll_path)
- # Profile management methods
async def get_profiles(self) -> Dict[str, Any]:
"""Get list of all profiles and current profile
@@ -304,12 +289,10 @@ class Plugin:
Returns:
ConfigurationResponse dict with success status
"""
- # Validate and extract configuration from the config dict
validated_config = ConfigurationManager.validate_config(config)
return self.configuration_service.update_profile_config(profile_name, validated_config)
- # Self-updater methods
async def check_for_plugin_update(self) -> Dict[str, Any]:
"""Check for plugin updates by comparing current version with most recent GitHub release
@@ -328,7 +311,6 @@ class Plugin:
}
"""
try:
- # Read current version from package.json
package_json_path = Path(decky.DECKY_PLUGIN_DIR) / "package.json"
current_version = "0.0.0"
@@ -340,21 +322,16 @@ class Plugin:
except Exception as e:
decky.logger.warning(f"Failed to read package.json: {e}")
- # Fetch most recent release from GitHub (including pre-releases)
api_url = "https://api.github.com/repos/xXJSONDeruloXx/decky-lsfg-vk/releases"
try:
- # Create SSL context that doesn't verify certificates
- # This is needed on Steam Deck where certificate verification often fails
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- # Use urllib to fetch all releases (sorted by most recent first)
with urllib.request.urlopen(api_url, context=ssl_context) as response:
releases_data = json.loads(response.read().decode('utf-8'))
- # Get the most recent release (first item in the array)
if not releases_data:
raise Exception("No releases found")
@@ -364,14 +341,12 @@ class Plugin:
release_notes = release_data.get('body', '')
release_date = release_data.get('published_at', '')
- # Find the plugin zip download URL
download_url = ""
for asset in release_data.get('assets', []):
if asset.get('name', '').endswith('.zip'):
download_url = asset.get('browser_download_url', '')
break
- # Compare versions
update_available = self._compare_versions(current_version, latest_version)
return {
@@ -412,31 +387,24 @@ class Plugin:
}
"""
try:
- # Create download path
downloads_dir = Path.home() / "Downloads"
downloads_dir.mkdir(exist_ok=True)
download_path = downloads_dir / "decky-lsfg-vk.zip"
- # Remove existing file if it exists
if download_path.exists():
download_path.unlink()
- # Download the file
decky.logger.info(f"Downloading plugin update from {download_url}")
try:
- # Create SSL context that doesn't verify certificates
- # This is needed on Steam Deck where certificate verification often fails
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
- # Use urllib to download the file with SSL context
with urllib.request.urlopen(download_url, context=ssl_context) as response:
with open(download_path, 'wb') as f:
f.write(response.read())
- # Verify the file was downloaded successfully
if download_path.exists() and download_path.stat().st_size > 0:
decky.logger.info(f"Plugin update downloaded successfully to {download_path}")
return {
@@ -473,16 +441,13 @@ class Plugin:
True if latest version is newer than current version
"""
try:
- # Remove 'v' prefix if present and split by dots
current_parts = current.lstrip('v').split('.')
latest_parts = latest.lstrip('v').split('.')
- # Pad with zeros if needed to ensure equal length
max_len = max(len(current_parts), len(latest_parts))
current_parts.extend(['0'] * (max_len - len(current_parts)))
latest_parts.extend(['0'] * (max_len - len(latest_parts)))
- # Compare each part numerically
for i in range(max_len):
try:
current_num = int(current_parts[i])
@@ -492,24 +457,18 @@ class Plugin:
return True
elif latest_num < current_num:
return False
- # If equal, continue to next part
except ValueError:
- # If conversion fails, do string comparison
if latest_parts[i] > current_parts[i]:
return True
elif latest_parts[i] < current_parts[i]:
return False
- # All parts are equal
return False
except (IndexError, AttributeError, TypeError) as e:
- # If comparison fails, assume no update available
self.configuration_service.log.warning(f"Version comparison failed: {e}")
return False
- # Plugin lifecycle methods
- # Launch option methods
async def get_launch_option(self) -> Dict[str, Any]:
"""Get the launch option that users need to set for their games
@@ -522,7 +481,6 @@ class Plugin:
"explanation": "The lsfg script is created during installation and sets up the environment for the plugin"
}
- # File content methods
async def get_config_file_content(self) -> Dict[str, Any]:
"""Get the current config file content
@@ -612,7 +570,6 @@ class Plugin:
"error": str(e)
}
- # Flatpak management methods
async def check_flatpak_extension_status(self) -> Dict[str, Any]:
"""Check status of lsfg-vk Flatpak runtime extensions
@@ -673,9 +630,6 @@ class Plugin:
"""
return self.flatpak_service.remove_app_override(app_id)
- # Decky Loader lifecycle methods
-
- # Lifecycle methods
async def _main(self):
"""
Main entry point for the plugin.
@@ -696,13 +650,12 @@ class Plugin:
async def _uninstall(self):
"""
- Cleanup tasks when the plugin is uninstalled.
+ Called when the plugin is uninstalled.
This method is called by Decky Loader when the plugin is being uninstalled.
- It automatically cleans up any lsfg-vk files that were installed and
- uninstalls any flatpak extensions.
+ Performs cleanup of plugin files and flatpak extensions.
"""
- decky.logger.info("decky-lsfg-vk plugin uninstalled - starting cleanup")
+ decky.logger.info("decky-lsfg-vk plugin being uninstalled")
# Clean up lsfg-vk files when the plugin is uninstalled
self.installation_service.cleanup_on_uninstall()
@@ -711,11 +664,9 @@ class Plugin:
try:
decky.logger.info("Checking for flatpak extensions to uninstall")
- # Get current extension status
extension_status = self.flatpak_service.get_extension_status()
if extension_status.get("success"):
- # Uninstall 23.08 runtime if installed
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")
@@ -724,7 +675,6 @@ class Plugin:
else:
decky.logger.warning(f"Failed to uninstall flatpak runtime 23.08: {result.get('error')}")
- # Uninstall 24.08 runtime if installed
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")
@@ -739,7 +689,6 @@ class Plugin:
except Exception as e:
decky.logger.error(f"Error during flatpak cleanup: {e}")
- # Don't fail the uninstall if flatpak cleanup fails
decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed")
@@ -752,21 +701,13 @@ class Plugin:
"""
decky.logger.info("Running decky-lsfg-vk plugin migrations")
- # Migrate logs from old location
- # ~/.config/decky-lossless-scaling-vk/lossless-scaling-vk.log -> decky.DECKY_LOG_DIR/lossless-scaling-vk.log
decky.migrate_logs(os.path.join(decky.DECKY_USER_HOME,
".config", "decky-lossless-scaling-vk", "lossless-scaling-vk.log"))
- # Migrate settings from old locations
- # ~/homebrew/settings/lossless-scaling-vk.json -> decky.DECKY_SETTINGS_DIR/lossless-scaling-vk.json
- # ~/.config/decky-lossless-scaling-vk/ -> decky.DECKY_SETTINGS_DIR/
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"))
- # Migrate runtime data from old locations
- # ~/homebrew/lossless-scaling-vk/ -> decky.DECKY_RUNTIME_DIR/
- # ~/.local/share/decky-lossless-scaling-vk/ -> decky.DECKY_RUNTIME_DIR/
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"))