summaryrefslogtreecommitdiff
path: root/py_modules
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-17 14:22:56 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2025-07-17 14:22:56 -0400
commit6b701637ad308513b678c80baceec6c79e339ce9 (patch)
tree1d7e306ad3c93b36515f5cb0622023be7ada0560 /py_modules
parent8a71f528ef34447c43e67db67d071e38ff084ff1 (diff)
downloaddecky-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.py7
-rw-r--r--py_modules/lsfg_vk/config_schema.py230
-rw-r--r--py_modules/lsfg_vk/configuration.py184
-rw-r--r--py_modules/lsfg_vk/constants.py2
-rw-r--r--py_modules/lsfg_vk/installation.py53
-rw-r--r--py_modules/lsfg_vk/plugin.py52
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