summaryrefslogtreecommitdiff
path: root/py_modules/lsfg_vk/plugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'py_modules/lsfg_vk/plugin.py')
-rw-r--r--py_modules/lsfg_vk/plugin.py285
1 files changed, 3 insertions, 282 deletions
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 7731558..cb59b4f 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -6,10 +6,7 @@ Vulkan layer for frame generation on Steam Deck.
"""
import os
-import json
import subprocess
-import urllib.request
-import ssl
import hashlib
from typing import Dict, Any
from pathlib import Path
@@ -34,13 +31,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 +60,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
@@ -74,38 +68,6 @@ class Plugin:
"""
return self.dll_detection_service.check_lossless_scaling_dll()
- async def check_lossless_scaling_dll_and_update_config(self) -> Dict[str, Any]:
- """Check for DLL and automatically update configuration if found
-
- This method should only be used during installation or when explicitly
- requested by the user, not for routine DLL detection checks.
-
- Returns:
- DllDetectionResponse dict with detection status and path info
- """
- 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
- update_result = self.configuration_service.update_dll_path(dll_path)
- if update_result.get("success"):
- result_dict["config_updated"] = True
- result_dict["message"] = f"DLL detected and configuration updated: {dll_path}"
- else:
- result_dict["config_updated"] = False
- result_dict["message"] = f"DLL detected but config update failed: {update_result.get('error', 'Unknown error')}"
- except Exception as e:
- result_dict["config_updated"] = False
- result_dict["message"] = f"DLL detected but config update failed: {str(e)}"
-
- return result_dict
-
async def get_dll_stats(self) -> Dict[str, Any]:
"""Get detailed statistics about the detected DLL
@@ -113,7 +75,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 +96,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 +126,6 @@ class Plugin:
"dll_sha256": None
}
- # Configuration methods
async def get_lsfg_config(self) -> Dict[str, Any]:
"""Read current lsfg script configuration
@@ -183,7 +141,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 +149,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 +159,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,24 +177,10 @@ 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]:
- """Update the DLL path in the configuration when detected
-
- Args:
- dll_path: Path to the detected Lossless.dll file
-
- Returns:
- ConfigurationResponse dict with success status
- """
- 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,212 +245,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
-
- Checks for the most recent release including pre-releases, not just the latest stable.
-
- Returns:
- Dict containing update information:
- {
- "update_available": bool,
- "current_version": str,
- "latest_version": str,
- "release_notes": str,
- "release_date": str,
- "download_url": str,
- "error": str (if error occurred)
- }
- """
- try:
- # Read current version from package.json
- package_json_path = Path(decky.DECKY_PLUGIN_DIR) / "package.json"
- current_version = "0.0.0"
-
- if package_json_path.exists():
- try:
- with open(package_json_path, 'r', encoding='utf-8') as f:
- package_data = json.load(f)
- current_version = package_data.get('version', '0.0.0')
- 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")
-
- release_data = releases_data[0]
-
- latest_version = release_data.get('tag_name', '').lstrip('v')
- 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 {
- "success": True,
- "update_available": update_available,
- "current_version": current_version,
- "latest_version": latest_version,
- "release_notes": release_notes,
- "release_date": release_date,
- "download_url": download_url
- }
-
- except Exception as e:
- decky.logger.error(f"Failed to fetch release info: {e}")
- return {
- "success": False,
- "error": f"Failed to check for updates: {str(e)}"
- }
-
- except Exception as e:
- return {
- "success": False,
- "error": f"Update check failed: {str(e)}"
- }
-
- async def download_plugin_update(self, download_url: str) -> Dict[str, Any]:
- """Download the plugin update zip file to ~/Downloads
-
- Args:
- download_url: URL to download the plugin zip from
-
- Returns:
- Dict containing download result:
- {
- "success": bool,
- "download_path": str,
- "error": str (if error occurred)
- }
- """
- 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 {
- "success": True,
- "download_path": str(download_path)
- }
- else:
- return {
- "success": False,
- "error": "Download completed but file is empty or missing"
- }
-
- except Exception as e:
- decky.logger.error(f"Download failed: {e}")
- return {
- "success": False,
- "error": f"Download failed: {str(e)}"
- }
-
- except Exception as e:
- return {
- "success": False,
- "error": f"Download preparation failed: {str(e)}"
- }
-
- def _compare_versions(self, current: str, latest: str) -> bool:
- """Compare two version strings to determine if an update is available
-
- Args:
- current: Current version string (e.g., "1.2.3")
- latest: Latest version string (e.g., "1.2.4")
-
- Returns:
- 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])
- latest_num = int(latest_parts[i])
-
- if latest_num > current_num:
- 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 +261,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 +350,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 +410,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 +430,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 +444,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 +455,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 +469,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 +481,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"))