summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lsfg_vk/__init__.py14
-rw-r--r--lsfg_vk/base_service.py103
-rw-r--r--lsfg_vk/configuration.py175
-rw-r--r--lsfg_vk/constants.py54
-rw-r--r--lsfg_vk/dll_detection.py120
-rw-r--r--lsfg_vk/installation.py225
-rw-r--r--lsfg_vk/plugin.py159
-rw-r--r--lsfg_vk/types.py71
8 files changed, 0 insertions, 921 deletions
diff --git a/lsfg_vk/__init__.py b/lsfg_vk/__init__.py
deleted file mode 100644
index 8343853..0000000
--- a/lsfg_vk/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
-lsfg-vk plugin package for Decky Loader.
-
-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/lsfg_vk/base_service.py b/lsfg_vk/base_service.py
deleted file mode 100644
index b547759..0000000
--- a/lsfg_vk/base_service.py
+++ /dev/null
@@ -1,103 +0,0 @@
-"""
-Base service class with common functionality.
-"""
-
-import os
-import shutil
-import tempfile
-from pathlib import Path
-from typing import Any, Optional
-
-from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME
-
-
-class BaseService:
- """Base service class with common functionality"""
-
- def __init__(self, logger: Optional[Any] = None):
- """Initialize base service
-
- Args:
- logger: Logger instance, defaults to decky.logger if None
- """
- if logger is None:
- import decky
- self.log = decky.logger
- 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
-
- def _ensure_directories(self) -> None:
- """Create necessary directories if they don't exist"""
- self.local_lib_dir.mkdir(parents=True, exist_ok=True)
- self.local_share_dir.mkdir(parents=True, exist_ok=True)
- self.log.info(f"Ensured directories exist: {self.local_lib_dir}, {self.local_share_dir}")
-
- def _remove_if_exists(self, path: Path) -> bool:
- """Remove a file if it exists
-
- Args:
- path: Path to the file to remove
-
- Returns:
- True if file was removed, False if it didn't exist
-
- Raises:
- OSError: If removal fails
- """
- if path.exists():
- try:
- path.unlink()
- self.log.info(f"Removed {path}")
- return True
- except OSError as e:
- self.log.error(f"Failed to remove {path}: {e}")
- raise
- else:
- self.log.info(f"File not found: {path}")
- return False
-
- def _atomic_write(self, path: Path, content: str, mode: int = 0o644) -> None:
- """Write content to a file atomically
-
- Args:
- path: Target file path
- content: Content to write
- mode: File permissions (default: 0o644)
-
- Raises:
- OSError: If write fails
- """
- # Create temporary file in the same directory to ensure atomic move
- temp_path = None
- try:
- with tempfile.NamedTemporaryFile(
- mode='w',
- dir=path.parent,
- delete=False,
- prefix=f'.{path.name}.',
- suffix='.tmp'
- ) as temp_file:
- temp_file.write(content)
- temp_path = Path(temp_file.name)
-
- # Set permissions before moving
- temp_path.chmod(mode)
-
- # Atomic move
- temp_path.replace(path)
- self.log.info(f"Atomically wrote to {path}")
-
- except Exception:
- # Clean up temp file if something went wrong
- if temp_path and temp_path.exists():
- try:
- temp_path.unlink()
- except OSError:
- pass # Best effort cleanup
- raise
diff --git a/lsfg_vk/configuration.py b/lsfg_vk/configuration.py
deleted file mode 100644
index f5e2981..0000000
--- a/lsfg_vk/configuration.py
+++ /dev/null
@@ -1,175 +0,0 @@
-"""
-Configuration service for lsfg script management.
-"""
-
-import re
-from pathlib import Path
-from typing import Dict, Any
-
-from .base_service import BaseService
-from .constants import LSFG_SCRIPT_TEMPLATE
-from .types import ConfigurationResponse, ConfigurationData
-
-
-class ConfigurationService(BaseService):
- """Service for managing lsfg script configuration"""
-
- def get_config(self) -> ConfigurationResponse:
- """Read current lsfg script configuration
-
- Returns:
- ConfigurationResponse with current configuration or error
- """
- try:
- if not self.lsfg_script_path.exists():
- return {
- "success": False,
- "config": None,
- "message": None,
- "error": "lsfg script not found"
- }
-
- content = self.lsfg_script_path.read_text()
- config = self._parse_script_content(content)
-
- self.log.info(f"Parsed lsfg config: {config}")
-
- return {
- "success": True,
- "config": config,
- "message": None,
- "error": None
- }
-
- except (OSError, IOError) as e:
- error_msg = f"Error reading lsfg config: {str(e)}"
- self.log.error(error_msg)
- return {
- "success": False,
- "config": None,
- "message": None,
- "error": str(e)
- }
-
- def _parse_script_content(self, content: str) -> ConfigurationData:
- """Parse script content to extract configuration values
-
- Args:
- content: Script file content
-
- Returns:
- ConfigurationData with parsed values
- """
- config: ConfigurationData = {
- "enable_lsfg": False,
- "multiplier": 2,
- "flow_scale": 1.0,
- "hdr": False,
- "perf_mode": False,
- "immediate_mode": False
- }
-
- lines = content.split('\n')
- for line in lines:
- line = line.strip()
-
- # Parse ENABLE_LSFG
- if match := re.match(r'^(#\s*)?export\s+ENABLE_LSFG=(\d+)', line):
- config["enable_lsfg"] = not bool(match.group(1)) and match.group(2) == '1'
-
- # Parse LSFG_MULTIPLIER
- elif match := re.match(r'^export\s+LSFG_MULTIPLIER=(\d+)', line):
- try:
- config["multiplier"] = int(match.group(1))
- except ValueError:
- pass
-
- # Parse LSFG_FLOW_SCALE
- elif match := re.match(r'^export\s+LSFG_FLOW_SCALE=([0-9]*\.?[0-9]+)', line):
- try:
- config["flow_scale"] = float(match.group(1))
- except ValueError:
- pass
-
- # Parse LSFG_HDR
- elif match := re.match(r'^(#\s*)?export\s+LSFG_HDR=(\d+)', line):
- config["hdr"] = not bool(match.group(1)) and match.group(2) == '1'
-
- # Parse LSFG_PERF_MODE
- elif match := re.match(r'^(#\s*)?export\s+LSFG_PERF_MODE=(\d+)', line):
- config["perf_mode"] = not bool(match.group(1)) and match.group(2) == '1'
-
- # Parse MESA_VK_WSI_PRESENT_MODE
- elif match := re.match(r'^(#\s*)?export\s+MESA_VK_WSI_PRESENT_MODE=([^\s#]+)', line):
- config["immediate_mode"] = not bool(match.group(1)) and match.group(2) == 'immediate'
-
- return config
-
- def update_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
- hdr: bool, perf_mode: bool, immediate_mode: bool) -> ConfigurationResponse:
- """Update lsfg script configuration
-
- Args:
- enable_lsfg: Whether to enable LSFG
- multiplier: LSFG multiplier value
- flow_scale: LSFG flow scale value
- hdr: Whether to enable HDR
- perf_mode: Whether to enable performance mode
- immediate_mode: Whether to enable immediate present mode (disable vsync)
-
- Returns:
- ConfigurationResponse with success status
- """
- try:
- # Generate script content using template
- script_content = self._generate_script_content(
- enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode
- )
-
- # Write the updated script atomically
- self._atomic_write(self.lsfg_script_path, script_content, 0o755)
-
- self.log.info(f"Updated lsfg script configuration: enable={enable_lsfg}, "
- f"multiplier={multiplier}, flow_scale={flow_scale}, hdr={hdr}, "
- f"perf_mode={perf_mode}, immediate_mode={immediate_mode}")
-
- return {
- "success": True,
- "config": None,
- "message": "lsfg configuration updated successfully",
- "error": None
- }
-
- except (OSError, IOError) as e:
- error_msg = f"Error updating lsfg config: {str(e)}"
- self.log.error(error_msg)
- return {
- "success": False,
- "config": None,
- "message": None,
- "error": str(e)
- }
-
- def _generate_script_content(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
- hdr: bool, perf_mode: bool, immediate_mode: bool) -> str:
- """Generate script content from configuration parameters
-
- Args:
- enable_lsfg: Whether to enable LSFG
- multiplier: LSFG multiplier value
- flow_scale: LSFG flow scale value
- hdr: Whether to enable HDR
- perf_mode: Whether to enable performance mode
- immediate_mode: Whether to enable immediate present mode
-
- Returns:
- Generated script content
- """
- return LSFG_SCRIPT_TEMPLATE.format(
- enable_lsfg="export ENABLE_LSFG=1" if enable_lsfg else "# export ENABLE_LSFG=1",
- multiplier=multiplier,
- flow_scale=flow_scale,
- hdr="export LSFG_HDR=1" if hdr else "# export LSFG_HDR=1",
- perf_mode="export LSFG_PERF_MODE=1" if perf_mode else "# export LSFG_PERF_MODE=1",
- immediate_mode="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" if immediate_mode else "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync"
- )
diff --git a/lsfg_vk/constants.py b/lsfg_vk/constants.py
deleted file mode 100644
index 28246c2..0000000
--- a/lsfg_vk/constants.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-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"
-
-# File names
-SCRIPT_NAME = "lsfg"
-LIB_FILENAME = "liblsfg-vk.so"
-JSON_FILENAME = "VkLayer_LS_frame_generation.json"
-ZIP_FILENAME = "lsfg-vk_archlinux.zip"
-
-# 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"
-
-# Script template
-LSFG_SCRIPT_TEMPLATE = """#!/bin/bash
-
-{enable_lsfg}
-export LSFG_MULTIPLIER={multiplier}
-export LSFG_FLOW_SCALE={flow_scale}
-{hdr}
-{perf_mode}
-{immediate_mode}
-
-# Execute the passed command with the environment variables set
-exec "$@"
-"""
-
-# Environment variable names
-ENV_LSFG_DLL_PATH = "LSFG_DLL_PATH"
-ENV_XDG_DATA_HOME = "XDG_DATA_HOME"
-ENV_HOME = "HOME"
-
-# Default configuration values
-DEFAULT_MULTIPLIER = 2
-DEFAULT_FLOW_SCALE = 1.0
-DEFAULT_ENABLE_LSFG = True
-DEFAULT_HDR = False
-DEFAULT_PERF_MODE = False
-DEFAULT_IMMEDIATE_MODE = False
diff --git a/lsfg_vk/dll_detection.py b/lsfg_vk/dll_detection.py
deleted file mode 100644
index f1dace9..0000000
--- a/lsfg_vk/dll_detection.py
+++ /dev/null
@@ -1,120 +0,0 @@
-"""
-DLL detection service for Lossless Scaling.
-"""
-
-import os
-from pathlib import Path
-from typing import Dict, Any
-
-from .base_service import BaseService
-from .constants import (
- ENV_LSFG_DLL_PATH, ENV_XDG_DATA_HOME, ENV_HOME,
- STEAM_COMMON_PATH, LOSSLESS_DLL_NAME
-)
-from .types import DllDetectionResponse
-
-
-class DllDetectionService(BaseService):
- """Service for detecting Lossless Scaling DLL"""
-
- def check_lossless_scaling_dll(self) -> DllDetectionResponse:
- """Check if Lossless Scaling DLL is available at the expected paths
-
- Returns:
- 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
-
- # DLL not found in any expected location
- return {
- "detected": False,
- "path": None,
- "source": None,
- "message": "Lossless Scaling DLL not found in expected locations",
- "error": None
- }
-
- except Exception as e:
- error_msg = f"Error checking Lossless Scaling DLL: {str(e)}"
- self.log.error(error_msg)
- return {
- "detected": False,
- "path": None,
- "source": None,
- "message": None,
- "error": str(e)
- }
-
- def _check_env_dll_path(self) -> DllDetectionResponse | None:
- """Check LSFG_DLL_PATH environment variable
-
- Returns:
- DllDetectionResponse if found, None otherwise
- """
- dll_path = os.getenv(ENV_LSFG_DLL_PATH)
- if dll_path and dll_path.strip():
- dll_path_obj = Path(dll_path.strip())
- if dll_path_obj.exists():
- self.log.info(f"Found DLL via {ENV_LSFG_DLL_PATH}: {dll_path_obj}")
- return {
- "detected": True,
- "path": str(dll_path_obj),
- "source": f"{ENV_LSFG_DLL_PATH} environment variable",
- "message": None,
- "error": None
- }
- return None
-
- def _check_xdg_data_home(self) -> DllDetectionResponse | None:
- """Check XDG_DATA_HOME Steam directory
-
- Returns:
- DllDetectionResponse if found, None otherwise
- """
- data_dir = os.getenv(ENV_XDG_DATA_HOME)
- if data_dir and data_dir.strip():
- dll_path = Path(data_dir.strip()) / "Steam" / STEAM_COMMON_PATH / LOSSLESS_DLL_NAME
- if dll_path.exists():
- self.log.info(f"Found DLL via {ENV_XDG_DATA_HOME}: {dll_path}")
- return {
- "detected": True,
- "path": str(dll_path),
- "source": f"{ENV_XDG_DATA_HOME} Steam directory",
- "message": None,
- "error": None
- }
- return None
-
- def _check_home_local_share(self) -> DllDetectionResponse | None:
- """Check HOME/.local/share Steam directory
-
- Returns:
- DllDetectionResponse if found, None otherwise
- """
- home_dir = os.getenv(ENV_HOME)
- if home_dir and home_dir.strip():
- dll_path = Path(home_dir.strip()) / ".local" / "share" / "Steam" / STEAM_COMMON_PATH / LOSSLESS_DLL_NAME
- if dll_path.exists():
- self.log.info(f"Found DLL via {ENV_HOME}/.local/share: {dll_path}")
- return {
- "detected": True,
- "path": str(dll_path),
- "source": f"{ENV_HOME}/.local/share Steam directory",
- "message": None,
- "error": None
- }
- return None
diff --git a/lsfg_vk/installation.py b/lsfg_vk/installation.py
deleted file mode 100644
index 1d0e96f..0000000
--- a/lsfg_vk/installation.py
+++ /dev/null
@@ -1,225 +0,0 @@
-"""
-Installation service for lsfg-vk.
-"""
-
-import os
-import shutil
-import zipfile
-import tempfile
-from pathlib import Path
-from typing import Dict, Any
-
-from .base_service import BaseService
-from .constants import (
- LIB_FILENAME, JSON_FILENAME, ZIP_FILENAME, BIN_DIR,
- SO_EXT, JSON_EXT, LSFG_SCRIPT_TEMPLATE,
- DEFAULT_MULTIPLIER, DEFAULT_FLOW_SCALE, DEFAULT_ENABLE_LSFG,
- DEFAULT_HDR, DEFAULT_PERF_MODE, DEFAULT_IMMEDIATE_MODE
-)
-from .types import InstallationResponse, UninstallationResponse, InstallationCheckResponse
-
-
-class InstallationService(BaseService):
- """Service for handling lsfg-vk installation and uninstallation"""
-
- 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
-
- def install(self) -> InstallationResponse:
- """Install lsfg-vk by extracting the zip file to ~/.local
-
- Returns:
- 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 {"success": False, "error": 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 lsfg script
- self._create_lsfg_script()
-
- self.log.info("lsfg-vk installed successfully")
- return {"success": True, "message": "lsfg-vk installed successfully", "error": None}
-
- except (OSError, zipfile.BadZipFile, shutil.Error) as e:
- error_msg = f"Error installing lsfg-vk: {str(e)}"
- self.log.error(error_msg)
- return {"success": False, "error": 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 {"success": False, "error": str(e), "message": ""}
-
- def _extract_and_install_files(self, zip_path: Path) -> None:
- """Extract zip file and install files to appropriate locations
-
- Args:
- zip_path: Path to the zip file to extract
-
- Raises:
- zipfile.BadZipFile: If zip file is corrupted
- OSError: If file operations fail
- """
- # Destination mapping for file types
- dest_map = {
- SO_EXT: self.local_lib_dir,
- JSON_EXT: self.local_share_dir
- }
-
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
- with tempfile.TemporaryDirectory() as temp_dir:
- temp_path = Path(temp_dir)
- zip_ref.extractall(temp_path)
-
- # Process extracted files
- for root, dirs, files in os.walk(temp_path):
- root_path = Path(root)
- for file in files:
- src_file = root_path / file
- file_path = Path(file)
-
- # Check if we know where this file type should go
- dst_dir = dest_map.get(file_path.suffix)
- if dst_dir:
- dst_file = dst_dir / file
- shutil.copy2(src_file, dst_file)
- self.log.info(f"Copied {file} to {dst_file}")
-
- def _create_lsfg_script(self) -> None:
- """Create the lsfg script in home directory with default configuration"""
- script_content = LSFG_SCRIPT_TEMPLATE.format(
- enable_lsfg="export ENABLE_LSFG=1" if DEFAULT_ENABLE_LSFG else "# export ENABLE_LSFG=1",
- multiplier=DEFAULT_MULTIPLIER,
- flow_scale=DEFAULT_FLOW_SCALE,
- hdr="export LSFG_HDR=1" if DEFAULT_HDR else "# export LSFG_HDR=1",
- perf_mode="export LSFG_PERF_MODE=1" if DEFAULT_PERF_MODE else "# export LSFG_PERF_MODE=1",
- immediate_mode="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" if DEFAULT_IMMEDIATE_MODE else "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync"
- )
-
- # Use atomic write to prevent corruption
- self._atomic_write(self.lsfg_script_path, script_content, 0o755)
- self.log.info(f"Created executable lsfg script at {self.lsfg_script_path}")
-
- def check_installation(self) -> InstallationCheckResponse:
- """Check if lsfg-vk is already installed
-
- Returns:
- InstallationCheckResponse with installation status and file paths
- """
- try:
- lib_exists = self.lib_file.exists()
- json_exists = self.json_file.exists()
- script_exists = self.lsfg_script_path.exists()
-
- self.log.info(f"Installation check: lib={lib_exists}, json={json_exists}, script={script_exists}")
-
- return {
- "installed": lib_exists and json_exists,
- "lib_exists": lib_exists,
- "json_exists": json_exists,
- "script_exists": script_exists,
- "lib_path": str(self.lib_file),
- "json_path": str(self.json_file),
- "script_path": str(self.lsfg_script_path),
- "error": None
- }
-
- except Exception as e:
- error_msg = f"Error checking lsfg-vk installation: {str(e)}"
- self.log.error(error_msg)
- return {
- "installed": False,
- "lib_exists": False,
- "json_exists": False,
- "script_exists": False,
- "lib_path": str(self.lib_file),
- "json_path": str(self.json_file),
- "script_path": str(self.lsfg_script_path),
- "error": str(e)
- }
-
- def uninstall(self) -> UninstallationResponse:
- """Uninstall lsfg-vk by removing the installed files
-
- Returns:
- UninstallationResponse with success status and removed files list
- """
- try:
- removed_files = []
- files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path]
-
- for file_path in files_to_remove:
- if self._remove_if_exists(file_path):
- removed_files.append(str(file_path))
-
- if not removed_files:
- return {
- "success": True,
- "message": "No lsfg-vk files found to remove",
- "removed_files": None,
- "error": None
- }
-
- self.log.info("lsfg-vk uninstalled successfully")
- return {
- "success": True,
- "message": f"lsfg-vk uninstalled successfully. Removed {len(removed_files)} files.",
- "removed_files": removed_files,
- "error": None
- }
-
- except OSError as e:
- error_msg = f"Error uninstalling lsfg-vk: {str(e)}"
- self.log.error(error_msg)
- return {
- "success": False,
- "message": "",
- "removed_files": None,
- "error": str(e)
- }
-
- def cleanup_on_uninstall(self) -> None:
- """Clean up lsfg-vk files when the plugin is uninstalled"""
- try:
- self.log.info("Checking for lsfg-vk files to clean up:")
- self.log.info(f" Library file: {self.lib_file}")
- self.log.info(f" JSON file: {self.json_file}")
- self.log.info(f" lsfg script: {self.lsfg_script_path}")
-
- removed_files = []
- files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path]
-
- for file_path in files_to_remove:
- try:
- if self._remove_if_exists(file_path):
- removed_files.append(str(file_path))
- except OSError as e:
- self.log.error(f"Failed to remove {file_path}: {e}")
-
- if removed_files:
- self.log.info(f"Cleaned up {len(removed_files)} lsfg-vk files during plugin uninstall: {removed_files}")
- else:
- self.log.info("No lsfg-vk files found to clean up during plugin uninstall")
-
- except Exception as e:
- self.log.error(f"Error cleaning up lsfg-vk files during uninstall: {str(e)}")
- import traceback
- self.log.error(f"Traceback: {traceback.format_exc()}")
diff --git a/lsfg_vk/plugin.py b/lsfg_vk/plugin.py
deleted file mode 100644
index 000830f..0000000
--- a/lsfg_vk/plugin.py
+++ /dev/null
@@ -1,159 +0,0 @@
-"""
-Main plugin class for the lsfg-vk Decky Loader plugin.
-
-This plugin provides services for installing and managing the lsfg-vk
-Vulkan layer for Lossless Scaling frame generation on Steam Deck.
-"""
-
-import os
-from typing import Dict, Any
-
-from .installation import InstallationService
-from .dll_detection import DllDetectionService
-from .configuration import ConfigurationService
-
-
-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
- methods (_main, _unload, _uninstall, _migration).
- """
-
- 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()
-
- # Installation methods
- async def install_lsfg_vk(self) -> Dict[str, Any]:
- """Install lsfg-vk by extracting the zip file to ~/.local
-
- Returns:
- InstallationResponse dict with success status and message/error
- """
- 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
- """
- 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
- """
- 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
-
- Returns:
- DllDetectionResponse dict with detection status and path info
- """
- return self.dll_detection_service.check_lossless_scaling_dll()
-
- # Configuration methods
- 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 update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float,
- hdr: bool, perf_mode: bool, immediate_mode: bool) -> Dict[str, Any]:
- """Update lsfg script configuration
-
- Args:
- enable_lsfg: Whether to enable LSFG
- multiplier: LSFG multiplier value (typically 2-4)
- flow_scale: LSFG flow scale value (typically 0.5-2.0)
- hdr: Whether to enable HDR
- perf_mode: Whether to enable performance mode
- immediate_mode: Whether to enable immediate present mode (disable vsync)
-
- Returns:
- ConfigurationResponse dict with success status
- """
- return self.configuration_service.update_config(
- enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode
- )
-
- # Plugin lifecycle methods
- async def _main(self):
- """
- Asyncio-compatible long-running code, executed in a task when the plugin is loaded.
-
- This method is called by Decky Loader when the plugin starts up.
- Currently just logs that the plugin has loaded successfully.
- """
- import decky
- decky.logger.info("Lossless Scaling VK plugin loaded!")
-
- async def _unload(self):
- """
- Function called first during the unload process.
-
- This method is called by Decky Loader when the plugin is being unloaded.
- Use this for cleanup that should happen when the plugin stops.
- """
- import decky
- decky.logger.info("Lossless Scaling VK plugin unloading")
-
- async def _uninstall(self):
- """
- Function called after `_unload` during uninstall.
-
- This method is called by Decky Loader when the plugin is being uninstalled.
- It automatically cleans up any lsfg-vk files that were installed.
- """
- import decky
- decky.logger.info("Lossless Scaling VK plugin uninstalled - starting cleanup")
-
- # Clean up lsfg-vk files when the plugin is uninstalled
- self.installation_service.cleanup_on_uninstall()
-
- decky.logger.info("Lossless Scaling 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.
- """
- import decky
- decky.logger.info("Running Lossless Scaling 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"))
-
- decky.logger.info("Lossless Scaling VK plugin migrations completed")
diff --git a/lsfg_vk/types.py b/lsfg_vk/types.py
deleted file mode 100644
index f0ec892..0000000
--- a/lsfg_vk/types.py
+++ /dev/null
@@ -1,71 +0,0 @@
-"""
-Type definitions for the lsfg-vk plugin responses.
-"""
-
-from typing import TypedDict, Optional, List
-
-
-class BaseResponse(TypedDict):
- """Base response structure"""
- success: bool
-
-
-class ErrorResponse(BaseResponse):
- """Response structure for errors"""
- error: str
-
-
-class MessageResponse(BaseResponse):
- """Response structure with message"""
- message: str
-
-
-class InstallationResponse(BaseResponse):
- """Response for installation operations"""
- message: str
- error: Optional[str]
-
-
-class UninstallationResponse(BaseResponse):
- """Response for uninstallation operations"""
- message: str
- removed_files: Optional[List[str]]
- error: Optional[str]
-
-
-class InstallationCheckResponse(TypedDict):
- """Response for installation check"""
- installed: bool
- lib_exists: bool
- json_exists: bool
- script_exists: bool
- lib_path: str
- json_path: str
- script_path: str
- error: Optional[str]
-
-
-class DllDetectionResponse(TypedDict):
- """Response for DLL detection"""
- detected: bool
- path: Optional[str]
- source: Optional[str]
- message: Optional[str]
- error: Optional[str]
-
-
-class ConfigurationData(TypedDict):
- """Configuration data structure"""
- enable_lsfg: bool
- multiplier: int
- flow_scale: float
- hdr: bool
- perf_mode: bool
- immediate_mode: bool
-
-
-class ConfigurationResponse(BaseResponse):
- """Response for configuration operations"""
- config: Optional[ConfigurationData]
- message: Optional[str]
- error: Optional[str]