diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-07-17 14:22:56 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-07-17 14:22:56 -0400 |
| commit | 6b701637ad308513b678c80baceec6c79e339ce9 (patch) | |
| tree | 1d7e306ad3c93b36515f5cb0622023be7ada0560 /py_modules | |
| parent | 8a71f528ef34447c43e67db67d071e38ff084ff1 (diff) | |
| download | decky-lsfg-vk-6b701637ad308513b678c80baceec6c79e339ce9.tar.gz decky-lsfg-vk-6b701637ad308513b678c80baceec6c79e339ce9.zip | |
initial conf FE and BE hooks
Diffstat (limited to 'py_modules')
| -rw-r--r-- | py_modules/lsfg_vk/base_service.py | 7 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/config_schema.py | 230 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/configuration.py | 184 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/constants.py | 2 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/installation.py | 53 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/plugin.py | 52 |
6 files changed, 294 insertions, 234 deletions
diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py index b547759..92ac152 100644 --- a/py_modules/lsfg_vk/base_service.py +++ b/py_modules/lsfg_vk/base_service.py @@ -8,7 +8,7 @@ import tempfile from pathlib import Path from typing import Any, Optional -from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME +from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME, CONFIG_DIR, CONFIG_FILENAME class BaseService: @@ -31,12 +31,15 @@ class BaseService: 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.config_dir = self.user_home / CONFIG_DIR + self.config_file_path = self.config_dir / CONFIG_FILENAME 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}") + self.config_dir.mkdir(parents=True, exist_ok=True) + self.log.info(f"Ensured directories exist: {self.local_lib_dir}, {self.local_share_dir}, {self.config_dir}") def _remove_if_exists(self, path: Path) -> bool: """Remove a file if it exists diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 0f1bdae..f054abf 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -1,16 +1,18 @@ """ Centralized configuration schema for lsfg-vk. -This module defines the complete configuration structure, including: +This module defines the complete configuration structure for TOML-based config files, including: - Field definitions with types, defaults, and metadata -- Script generation logic +- TOML generation logic - Validation rules - Type definitions """ -from typing import TypedDict, Dict, Any, Union, Callable, cast -from dataclasses import dataclass, field +import re +from typing import TypedDict, Dict, Any, Union, cast +from dataclasses import dataclass from enum import Enum +from pathlib import Path class ConfigFieldType(Enum): @@ -18,6 +20,7 @@ class ConfigFieldType(Enum): BOOLEAN = "boolean" INTEGER = "integer" FLOAT = "float" + STRING = "string" @dataclass @@ -25,110 +28,72 @@ class ConfigField: """Configuration field definition""" name: str field_type: ConfigFieldType - default: Union[bool, int, float] + default: Union[bool, int, float, str] description: str - script_template: str # Template for script generation - script_comment: str = "" # Comment to add when disabled - def get_script_line(self, value: Union[bool, int, float]) -> str: - """Generate script line for this field""" - if self.field_type == ConfigFieldType.BOOLEAN: - if value: - return self.script_template.format(value=1) - else: - return f"# {self.script_template.format(value=1)}" - else: - return self.script_template.format(value=value) + def get_toml_value(self, value: Union[bool, int, float, str]) -> Union[bool, int, float, str]: + """Get the value for TOML output""" + return value # Configuration schema definition CONFIG_SCHEMA: Dict[str, ConfigField] = { - "enable_lsfg": ConfigField( - name="enable_lsfg", + "enable": ConfigField( + name="enable", field_type=ConfigFieldType.BOOLEAN, default=True, - description="Enables the frame generation layer", - script_template="export ENABLE_LSFG={value}", - script_comment="# export ENABLE_LSFG=1" + description="enable/disable lsfg on every game" + ), + + "dll": ConfigField( + name="dll", + field_type=ConfigFieldType.STRING, + default="/games/Lossless Scaling/Lossless.dll", + description="specify where Lossless.dll is stored" ), "multiplier": ConfigField( name="multiplier", field_type=ConfigFieldType.INTEGER, default=2, - description="Traditional FPS multiplier value", - script_template="export LSFG_MULTIPLIER={value}" + description="change the fps multiplier" ), "flow_scale": ConfigField( name="flow_scale", field_type=ConfigFieldType.FLOAT, default=0.8, - description="Lowers the internal motion estimation resolution", - script_template="export LSFG_FLOW_SCALE={value}" - ), - - "hdr": ConfigField( - name="hdr", - field_type=ConfigFieldType.BOOLEAN, - default=False, - description="Enable HDR mode (only if Game supports HDR)", - script_template="export LSFG_HDR={value}", - script_comment="# export LSFG_HDR=1" + description="change the flow scale (lower = faster)" ), - "perf_mode": ConfigField( - name="perf_mode", + "performance_mode": ConfigField( + name="performance_mode", field_type=ConfigFieldType.BOOLEAN, default=True, - description="Use lighter model for FG", - script_template="export LSFG_PERF_MODE={value}", - script_comment="# export LSFG_PERF_MODE=1" + description="toggle performance mode (2x-8x performance increase)" ), - "immediate_mode": ConfigField( - name="immediate_mode", + "hdr_mode": ConfigField( + name="hdr_mode", field_type=ConfigFieldType.BOOLEAN, default=False, - description="Reduce input lag (Experimental, will cause issues in many games)", - script_template="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync", - script_comment="# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" - ), - - "disable_vkbasalt": ConfigField( - name="disable_vkbasalt", - field_type=ConfigFieldType.BOOLEAN, - default=True, - description="Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this", - script_template="export DISABLE_VKBASALT={value}", - script_comment="# export DISABLE_VKBASALT=1" - ), - - "frame_cap": ConfigField( - name="frame_cap", - field_type=ConfigFieldType.INTEGER, - default=0, - description="Limit base game FPS (0 = disabled)", - script_template="export DXVK_FRAME_RATE={value}", - script_comment="# export DXVK_FRAME_RATE=60" + description="enable hdr mode (doesn't support scrgb)" ) } class ConfigurationData(TypedDict): """Type-safe configuration data structure""" - enable_lsfg: bool + enable: bool + dll: str multiplier: int flow_scale: float - hdr: bool - perf_mode: bool - immediate_mode: bool - disable_vkbasalt: bool - frame_cap: int + performance_mode: bool + hdr_mode: bool class ConfigurationManager: - """Centralized configuration management""" + """Centralized configuration management for TOML-based config""" @staticmethod def get_defaults() -> ConfigurationData: @@ -166,63 +131,104 @@ class ConfigurationManager: validated[field_name] = int(value) elif field_def.field_type == ConfigFieldType.FLOAT: validated[field_name] = float(value) + elif field_def.field_type == ConfigFieldType.STRING: + validated[field_name] = str(value) else: validated[field_name] = value return cast(ConfigurationData, validated) @staticmethod - def generate_script_content(config: ConfigurationData) -> str: - """Generate lsfg script content from configuration""" - script_lines = ["#!/bin/bash", ""] + def generate_toml_content(config: ConfigurationData) -> str: + """Generate TOML configuration file content""" + lines = ["[global]"] - # Generate script lines for each field - for field_name in CONFIG_SCHEMA.keys(): - field_def = CONFIG_SCHEMA[field_name] + for field_name, field_def in CONFIG_SCHEMA.items(): value = config[field_name] + lines.append(f"# {field_def.description}") - if field_def.field_type == ConfigFieldType.BOOLEAN: - if value: - script_lines.append(field_def.script_template.format(value=1)) - else: - script_lines.append(field_def.script_comment) + # Format value based on type + if isinstance(value, bool): + lines.append(f"{field_name} = {str(value).lower()}") + elif isinstance(value, str): + lines.append(f'{field_name} = "{value}"') else: - # For frame_cap, special handling for 0 value - if field_name == "frame_cap" and value == 0: - script_lines.append(field_def.script_comment) - else: - script_lines.append(field_def.script_template.format(value=value)) - - # Add script footer - script_lines.extend([ - "", - "# Execute the passed command with the environment variables set", - 'exec "$@"' - ]) + lines.append(f"{field_name} = {value}") + + lines.append("") # Empty line for readability - return "\n".join(script_lines) + return "\n".join(lines) @staticmethod - def get_update_signature() -> list[tuple[str, type]]: - """Get the function signature for update_config method""" - signature = [] - for field_name, field_def in CONFIG_SCHEMA.items(): - if field_def.field_type == ConfigFieldType.BOOLEAN: - signature.append((field_name, bool)) - elif field_def.field_type == ConfigFieldType.INTEGER: - signature.append((field_name, int)) - elif field_def.field_type == ConfigFieldType.FLOAT: - signature.append((field_name, float)) - return signature + def parse_toml_content(content: str) -> ConfigurationData: + """Parse TOML content into configuration data using simple regex parsing""" + config = ConfigurationManager.get_defaults() + + try: + # Look for [global] section + lines = content.split('\n') + in_global_section = False + + for line in lines: + line = line.strip() + + # Skip comments and empty lines + if not line or line.startswith('#'): + continue + + # Check for section headers + if line.startswith('[') and line.endswith(']'): + section = line[1:-1].strip() + in_global_section = (section == 'global') + continue + + # Only parse lines in the global section + if not in_global_section: + continue + + # Parse key = value lines + if '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + # Remove quotes from string values + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + elif value.startswith("'") and value.endswith("'"): + value = value[1:-1] + + # Convert to appropriate type based on field definition + if key in CONFIG_SCHEMA: + field_def = CONFIG_SCHEMA[key] + try: + if field_def.field_type == ConfigFieldType.BOOLEAN: + config[key] = value.lower() in ('true', '1', 'yes', 'on') + elif field_def.field_type == ConfigFieldType.INTEGER: + config[key] = int(value) + elif field_def.field_type == ConfigFieldType.FLOAT: + config[key] = float(value) + elif field_def.field_type == ConfigFieldType.STRING: + config[key] = value + except (ValueError, TypeError): + # If conversion fails, keep default value + pass + + return config + + except Exception: + # If parsing fails completely, return defaults + return ConfigurationManager.get_defaults() @staticmethod - def create_config_from_args(*args) -> ConfigurationData: - """Create configuration from ordered arguments""" - field_names = ConfigurationManager.get_field_names() - if len(args) != len(field_names): - raise ValueError(f"Expected {len(field_names)} arguments, got {len(args)}") - + def create_config_from_args(enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> ConfigurationData: + """Create configuration from individual arguments""" return cast(ConfigurationData, { - field_name: args[i] - for i, field_name in enumerate(field_names) + "enable": enable, + "dll": dll, + "multiplier": multiplier, + "flow_scale": flow_scale, + "performance_mode": performance_mode, + "hdr_mode": hdr_mode }) diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index 8be7b47..4d76a30 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -1,8 +1,7 @@ """ -Configuration service for lsfg script management. +Configuration service for TOML-based lsfg configuration management. """ -import re from pathlib import Path from typing import Dict, Any @@ -12,25 +11,27 @@ from .types import ConfigurationResponse class ConfigurationService(BaseService): - """Service for managing lsfg script configuration""" + """Service for managing TOML-based lsfg configuration""" def get_config(self) -> ConfigurationResponse: - """Read current lsfg script configuration + """Read current TOML configuration Returns: ConfigurationResponse with current configuration or error """ try: - if not self.lsfg_script_path.exists(): + if not self.config_file_path.exists(): + # Return default configuration if file doesn't exist + config = ConfigurationManager.get_defaults() return { - "success": False, - "config": None, - "message": None, - "error": "lsfg script not found" + "success": True, + "config": config, + "message": "Using default configuration (config file not found)", + "error": None } - content = self.lsfg_script_path.read_text() - config = self._parse_script_content(content) + content = self.config_file_path.read_text(encoding='utf-8') + config = ConfigurationManager.parse_toml_content(content) return { "success": True, @@ -48,83 +49,29 @@ class ConfigurationService(BaseService): "message": None, "error": str(e) } + except Exception as e: + error_msg = f"Error parsing config file: {str(e)}" + self.log.error(error_msg) + # Return defaults if parsing fails + config = ConfigurationManager.get_defaults() + return { + "success": True, + "config": config, + "message": f"Using default configuration due to parse error: {str(e)}", + "error": None + } - 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 - """ - # Start with defaults - config = ConfigurationManager.get_defaults() - - 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' - - # Parse DISABLE_VKBASALT - elif match := re.match(r'^(#\s*)?export\s+DISABLE_VKBASALT=(\d+)', line): - config["disable_vkbasalt"] = not bool(match.group(1)) and match.group(2) == '1' - - # Parse DXVK_FRAME_RATE - elif match := re.match(r'^(#\s*)?export\s+DXVK_FRAME_RATE=(\d+)', line): - if not bool(match.group(1)): # Not commented out - try: - config["frame_cap"] = int(match.group(2)) - except ValueError: - pass - else: - # If it's commented out, frame cap is disabled (0) - config["frame_cap"] = 0 - - return config - - def update_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float, - hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> ConfigurationResponse: - """Update lsfg script configuration + def update_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> ConfigurationResponse: + """Update TOML configuration Args: - enable_lsfg: Whether to enable LSFG + enable: Whether to enable LSFG + dll: Path to Lossless.dll 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) - disable_vkbasalt: Whether to disable vkbasalt layer - frame_cap: Frame rate cap value (0-60, 0 = disabled) + performance_mode: Whether to enable performance mode + hdr_mode: Whether to enable HDR mode Returns: ConfigurationResponse with success status @@ -132,23 +79,25 @@ class ConfigurationService(BaseService): try: # Create configuration from individual arguments config = ConfigurationManager.create_config_from_args( - enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode, disable_vkbasalt, frame_cap + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode ) - # Generate script content using centralized manager - script_content = ConfigurationManager.generate_script_content(config) + # Generate TOML content using centralized manager + toml_content = ConfigurationManager.generate_toml_content(config) - # Write the updated script atomically - self._atomic_write(self.lsfg_script_path, script_content, 0o755) + # Ensure config directory exists + self.config_dir.mkdir(parents=True, exist_ok=True) - self.log.info(f"Updated lsfg script configuration: enable_lsfg={enable_lsfg}, " - f"multiplier={multiplier}, flow_scale={flow_scale}, hdr={hdr}, " - f"perf_mode={perf_mode}, immediate_mode={immediate_mode}, " - f"disable_vkbasalt={disable_vkbasalt}, frame_cap={frame_cap}") + # Write the updated config atomically + self._atomic_write(self.config_file_path, toml_content, 0o644) + + self.log.info(f"Updated lsfg TOML configuration: enable={enable}, " + f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, " + f"performance_mode={performance_mode}, hdr_mode={hdr_mode}") return { "success": True, - "config": None, + "config": config, "message": "lsfg configuration updated successfully", "error": None } @@ -171,3 +120,52 @@ class ConfigurationService(BaseService): "message": None, "error": str(e) } + + def update_dll_path(self, dll_path: str) -> ConfigurationResponse: + """Update just the DLL path in the configuration + + Args: + dll_path: Path to the Lossless.dll file + + Returns: + ConfigurationResponse with success status + """ + try: + # Get current config + current_response = self.get_config() + if not current_response["success"] or current_response["config"] is None: + # If we can't read current config, use defaults + config = ConfigurationManager.get_defaults() + else: + config = current_response["config"] + + # Update just the DLL path + config["dll"] = dll_path + + # Generate TOML content and write it + toml_content = ConfigurationManager.generate_toml_content(config) + + # Ensure config directory exists + self.config_dir.mkdir(parents=True, exist_ok=True) + + # Write the updated config atomically + self._atomic_write(self.config_file_path, toml_content, 0o644) + + self.log.info(f"Updated DLL path in lsfg configuration: '{dll_path}'") + + return { + "success": True, + "config": config, + "message": f"DLL path updated to: {dll_path}", + "error": None + } + + except Exception as e: + error_msg = f"Error updating DLL path: {str(e)}" + self.log.error(error_msg) + return { + "success": False, + "config": None, + "message": None, + "error": str(e) + } diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index 5f1e5a2..252c7a5 100644 --- a/py_modules/lsfg_vk/constants.py +++ b/py_modules/lsfg_vk/constants.py @@ -8,9 +8,11 @@ from pathlib import Path 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_archlinux.zip" diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 767a97a..a1c2d64 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -51,8 +51,8 @@ class InstallationService(BaseService): # Extract and install files self._extract_and_install_files(zip_path) - # Create the lsfg script - self._create_lsfg_script() + # Create the config file + self._create_config_file() self.log.info("lsfg-vk installed successfully") return {"success": True, "message": "lsfg-vk installed successfully", "error": None} @@ -102,17 +102,17 @@ class InstallationService(BaseService): 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""" + def _create_config_file(self) -> None: + """Create the TOML config file in ~/.config/lsfg-vk with default configuration""" # Get default configuration defaults = ConfigurationManager.get_defaults() - # Generate script content using centralized manager - script_content = ConfigurationManager.generate_script_content(defaults) + # Generate TOML content using centralized manager + toml_content = ConfigurationManager.generate_toml_content(defaults) # 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}") + self._atomic_write(self.config_file_path, toml_content, 0o644) + self.log.info(f"Created config file at {self.config_file_path}") def check_installation(self) -> InstallationCheckResponse: """Check if lsfg-vk is already installed @@ -123,18 +123,18 @@ class InstallationService(BaseService): try: lib_exists = self.lib_file.exists() json_exists = self.json_file.exists() - script_exists = self.lsfg_script_path.exists() + config_exists = self.config_file_path.exists() - self.log.info(f"Installation check: lib={lib_exists}, json={json_exists}, script={script_exists}") + self.log.info(f"Installation check: lib={lib_exists}, json={json_exists}, config={config_exists}") return { "installed": lib_exists and json_exists, "lib_exists": lib_exists, "json_exists": json_exists, - "script_exists": script_exists, + "script_exists": config_exists, # Keep script_exists for backward compatibility "lib_path": str(self.lib_file), "json_path": str(self.json_file), - "script_path": str(self.lsfg_script_path), + "script_path": str(self.config_file_path), # Keep script_path for backward compatibility "error": None } @@ -148,7 +148,7 @@ class InstallationService(BaseService): "script_exists": False, "lib_path": str(self.lib_file), "json_path": str(self.json_file), - "script_path": str(self.lsfg_script_path), + "script_path": str(self.config_file_path), "error": str(e) } @@ -160,12 +160,24 @@ class InstallationService(BaseService): """ try: removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path] for file_path in files_to_remove: if self._remove_if_exists(file_path): removed_files.append(str(file_path)) + # Also try to remove the old script file if it exists + if self._remove_if_exists(self.lsfg_script_path): + removed_files.append(str(self.lsfg_script_path)) + + # Remove config directory if it's empty + try: + if self.config_dir.exists() and not any(self.config_dir.iterdir()): + self.config_dir.rmdir() + removed_files.append(str(self.config_dir)) + except OSError: + pass # Directory not empty or other error, ignore + if not removed_files: return { "success": True, @@ -198,10 +210,11 @@ class InstallationService(BaseService): 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}") + self.log.info(f" Config file: {self.config_file_path}") + self.log.info(f" Old script file: {self.lsfg_script_path}") removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_script_path] for file_path in files_to_remove: try: @@ -210,6 +223,14 @@ class InstallationService(BaseService): except OSError as e: self.log.error(f"Failed to remove {file_path}: {e}") + # Try to remove config directory if empty + try: + if self.config_dir.exists() and not any(self.config_dir.iterdir()): + self.config_dir.rmdir() + removed_files.append(str(self.config_dir)) + except OSError: + pass # Directory not empty or other error, ignore + if removed_files: self.log.info(f"Cleaned up {len(removed_files)} lsfg-vk files during plugin uninstall: {removed_files}") else: diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 3e37047..a8fed53 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -67,7 +67,28 @@ class Plugin: Returns: DllDetectionResponse dict with detection status and path info """ - return self.dll_detection_service.check_lossless_scaling_dll() + 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 # Configuration methods async def get_lsfg_config(self) -> Dict[str, Any]: @@ -90,27 +111,36 @@ class Plugin: "defaults": ConfigurationManager.get_defaults() } - async def update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float, - hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> Dict[str, Any]: - """Update lsfg script configuration + async def update_lsfg_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> Dict[str, Any]: + """Update lsfg TOML configuration Args: - enable_lsfg: Whether to enable LSFG + enable: Whether to enable LSFG + dll: Path to Lossless.dll 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) - disable_vkbasalt: Whether to disable vkbasalt layer - frame_cap: Frame rate cap value (0-60, 0 = disabled) + performance_mode: Whether to enable performance mode + hdr_mode: Whether to enable HDR mode Returns: ConfigurationResponse dict with success status """ return self.configuration_service.update_config( - enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode, disable_vkbasalt, frame_cap + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode ) + 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) + # Self-updater methods async def check_for_plugin_update(self) -> Dict[str, Any]: """Check for plugin updates by comparing current version with latest GitHub release |
