summaryrefslogtreecommitdiff
path: root/py_modules/lsfg_vk
diff options
context:
space:
mode:
authorKurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>2025-07-22 13:33:13 -0400
committerGitHub <noreply@github.com>2025-07-22 13:33:13 -0400
commit42668d412105b972e0cc7440b64e8e24aeef4587 (patch)
treec8f20ccbe508ac6684a38c115d63602476575493 /py_modules/lsfg_vk
parent97bb41947bd44a712ad26905771a9d2cc4692878 (diff)
parent43def41747d3b75bb547b649a00f12653c3ae537 (diff)
downloaddecky-lsfg-vk-42668d412105b972e0cc7440b64e8e24aeef4587.tar.gz
decky-lsfg-vk-42668d412105b972e0cc7440b64e8e24aeef4587.zip
Merge pull request #67 from xXJSONDeruloXx/workaround-envs
add workaround env vars, rm old tests
Diffstat (limited to 'py_modules/lsfg_vk')
-rw-r--r--py_modules/lsfg_vk/config_schema.py118
-rw-r--r--py_modules/lsfg_vk/config_schema_generated.py131
-rw-r--r--py_modules/lsfg_vk/configuration.py108
-rw-r--r--py_modules/lsfg_vk/configuration_helpers_generated.py22
-rw-r--r--py_modules/lsfg_vk/installation.py7
-rw-r--r--py_modules/lsfg_vk/plugin.py28
6 files changed, 257 insertions, 157 deletions
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index c82d1d3..bbace42 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -19,6 +19,9 @@ from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from shared_config import CONFIG_SCHEMA_DEF, ConfigFieldType, get_field_names, get_defaults, get_field_types
+# Import auto-generated configuration components
+from .config_schema_generated import ConfigurationData, get_script_parsing_logic, get_script_generation_logic
+
@dataclass
class ConfigField:
@@ -52,45 +55,27 @@ CONFIG_SCHEMA["dll"] = ConfigField(
description="specify where Lossless.dll is stored"
)
-# Fields that should ONLY be in the lsfg script, not in TOML config
+# Get script-only fields dynamically from shared config
SCRIPT_ONLY_FIELDS = {
- "dxvk_frame_rate": ConfigField(
- name="dxvk_frame_rate",
- field_type=ConfigFieldType.INTEGER,
- default=0,
- description="base framerate cap for DirectX games, before frame multiplier (0 = disabled, requires game re-launch)"
- ),
-
- "enable_wow64": ConfigField(
- name="enable_wow64",
- field_type=ConfigFieldType.BOOLEAN,
- default=False,
- description="enable PROTON_USE_WOW64=1 for 32-bit games (use with ProtonGE to fix crashing)"
- ),
-
- "disable_steamdeck_mode": ConfigField(
- name="disable_steamdeck_mode",
- field_type=ConfigFieldType.BOOLEAN,
- default=False,
- description="disable Steam Deck mode (unlocks hidden settings in some games)"
+ field_name: ConfigField(
+ name=field_def["name"],
+ field_type=ConfigFieldType(field_def["fieldType"]),
+ default=field_def["default"],
+ description=field_def["description"]
)
+ for field_name, field_def in CONFIG_SCHEMA_DEF.items()
+ if field_def.get("location") == "script"
}
# Complete configuration schema (TOML + script-only fields)
COMPLETE_CONFIG_SCHEMA = {**CONFIG_SCHEMA, **SCRIPT_ONLY_FIELDS}
-class ConfigurationData(TypedDict):
- """Type-safe configuration data structure"""
- dll: str
- multiplier: int
- flow_scale: float
- performance_mode: bool
- hdr_mode: bool
- experimental_present_mode: str
- dxvk_frame_rate: int
- enable_wow64: bool
- disable_steamdeck_mode: bool
+# Import auto-generated configuration components
+from .config_schema_generated import ConfigurationData, get_script_parsing_logic, get_script_generation_logic
+
+# Note: ConfigurationData is now imported from generated file
+# No need to manually maintain the TypedDict anymore!
class ConfigurationManager:
@@ -186,7 +171,9 @@ class ConfigurationManager:
if config.get("dll"):
lines.append("[global]")
lines.append(f"# specify where Lossless.dll is stored")
- lines.append(f'dll = "{config["dll"]}"')
+ # Generate TOML lines for TOML fields only - USE GENERATED CONSTANTS
+ from .config_schema_generated import DLL
+ lines.append(f'dll = "{config[DLL]}"')
lines.append("")
# Add game section with process name for LSFG_PROCESS approach
@@ -263,9 +250,10 @@ class ConfigurationManager:
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
- # Handle global section (dll only)
+ # Handle global section (dll only) - USE GENERATED CONSTANTS
if in_global_section and key == "dll":
- config["dll"] = value
+ from .config_schema_generated import DLL
+ config[DLL] = value
# Handle game section
elif in_game_section:
@@ -305,42 +293,9 @@ class ConfigurationManager:
Returns:
Dict containing parsed script-only field values
"""
- script_values = {}
-
- try:
- lines = script_content.split('\n')
-
- for line in lines:
- line = line.strip()
-
- # Skip comments, empty lines, and non-export lines
- if not line or line.startswith('#') or not line.startswith('export '):
- continue
-
- # Parse export statements: export VAR=value
- if '=' in line:
- # Remove 'export ' prefix
- export_line = line[len('export '):]
- key, value = export_line.split('=', 1)
- key = key.strip()
- value = value.strip()
-
- # Map environment variables to config field names
- if key == "DXVK_FRAME_RATE":
- try:
- script_values["dxvk_frame_rate"] = int(value)
- except ValueError:
- pass
- elif key == "PROTON_USE_WOW64":
- script_values["enable_wow64"] = value == "1"
- elif key == "SteamDeck":
- script_values["disable_steamdeck_mode"] = value == "0"
-
- except (ValueError, KeyError, IndexError) as e:
- # If parsing fails, log the error and return empty dict (will use defaults)
- print(f"Error parsing script content: {e}")
-
- return script_values
+ # Use auto-generated parsing logic
+ parse_script_values = get_script_parsing_logic()
+ return parse_script_values(script_content.split('\n'))
@staticmethod
def merge_config_with_script(toml_config: ConfigurationData, script_values: Dict[str, Union[bool, int, str]]) -> ConfigurationData:
@@ -363,21 +318,8 @@ class ConfigurationManager:
return cast(ConfigurationData, merged_config)
@staticmethod
- def create_config_from_args(dll: str, multiplier: int, flow_scale: float,
- performance_mode: bool, hdr_mode: bool,
- experimental_present_mode: str = "fifo",
- dxvk_frame_rate: int = 0,
- enable_wow64: bool = False,
- disable_steamdeck_mode: bool = False) -> ConfigurationData:
- """Create configuration from individual arguments"""
- return cast(ConfigurationData, {
- "dll": dll,
- "multiplier": multiplier,
- "flow_scale": flow_scale,
- "performance_mode": performance_mode,
- "hdr_mode": hdr_mode,
- "experimental_present_mode": experimental_present_mode,
- "dxvk_frame_rate": dxvk_frame_rate,
- "enable_wow64": enable_wow64,
- "disable_steamdeck_mode": disable_steamdeck_mode
- })
+ @staticmethod
+ def create_config_from_args(**kwargs) -> ConfigurationData:
+ """Create configuration from keyword arguments - USES GENERATED CODE"""
+ from .config_schema_generated import create_config_dict
+ return create_config_dict(**kwargs)
diff --git a/py_modules/lsfg_vk/config_schema_generated.py b/py_modules/lsfg_vk/config_schema_generated.py
new file mode 100644
index 0000000..cc90207
--- /dev/null
+++ b/py_modules/lsfg_vk/config_schema_generated.py
@@ -0,0 +1,131 @@
+"""
+Auto-generated configuration schema components from shared_config.py
+DO NOT EDIT THIS FILE MANUALLY - it will be overwritten on build
+"""
+
+from typing import TypedDict, Dict, Any, Union, cast
+from enum import Enum
+import sys
+from pathlib import Path
+
+# Import shared configuration constants
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+from shared_config import CONFIG_SCHEMA_DEF, ConfigFieldType
+
+# Field name constants for type-safe access
+DLL = "dll"
+MULTIPLIER = "multiplier"
+FLOW_SCALE = "flow_scale"
+PERFORMANCE_MODE = "performance_mode"
+HDR_MODE = "hdr_mode"
+EXPERIMENTAL_PRESENT_MODE = "experimental_present_mode"
+DXVK_FRAME_RATE = "dxvk_frame_rate"
+ENABLE_WOW64 = "enable_wow64"
+DISABLE_STEAMDECK_MODE = "disable_steamdeck_mode"
+MANGOHUD_WORKAROUND = "mangohud_workaround"
+DISABLE_VKBASALT = "disable_vkbasalt"
+
+
+class ConfigurationData(TypedDict):
+ """Type-safe configuration data structure - AUTO-GENERATED"""
+ dll: str
+ multiplier: int
+ flow_scale: float
+ performance_mode: bool
+ hdr_mode: bool
+ experimental_present_mode: str
+ dxvk_frame_rate: int
+ enable_wow64: bool
+ disable_steamdeck_mode: bool
+ mangohud_workaround: bool
+ disable_vkbasalt: bool
+
+
+def get_script_parsing_logic():
+ """Return the script parsing logic as a callable"""
+ def parse_script_values(lines):
+ script_values = {}
+ for line in lines:
+ line = line.strip()
+ if not line or line.startswith("#") or not line.startswith("export "):
+ continue
+ if "=" in line:
+ export_line = line[len("export "):]
+ key, value = export_line.split("=", 1)
+ key = key.strip()
+ value = value.strip()
+
+ # Auto-generated parsing logic:
+ if key == "DXVK_FRAME_RATE":
+ try:
+ script_values["dxvk_frame_rate"] = int(value)
+ except ValueError:
+ pass
+ if key == "PROTON_USE_WOW64":
+ script_values["enable_wow64"] = value == "1"
+ if key == "SteamDeck":
+ script_values["disable_steamdeck_mode"] = value == "0"
+ if key == "MANGOHUD":
+ script_values["mangohud_workaround"] = value == "1"
+ if key == "DISABLE_VKBASALT":
+ script_values["disable_vkbasalt"] = value == "1"
+
+ return script_values
+ return parse_script_values
+
+
+def get_script_generation_logic():
+ """Return the script generation logic as a callable"""
+ def generate_script_lines(config):
+ lines = []
+ dxvk_frame_rate = config.get("dxvk_frame_rate", 0)
+ if dxvk_frame_rate > 0:
+ lines.append(f"export DXVK_FRAME_RATE={dxvk_frame_rate}")
+ if config.get("enable_wow64", False):
+ lines.append("export PROTON_USE_WOW64=1")
+ if config.get("disable_steamdeck_mode", False):
+ lines.append("export SteamDeck=0")
+ if config.get("mangohud_workaround", False):
+ lines.append("export MANGOHUD=1")
+ if config.get("disable_vkbasalt", False):
+ lines.append("export DISABLE_VKBASALT=1")
+ return lines
+ return generate_script_lines
+
+
+def get_function_parameters() -> str:
+ """Return function signature parameters"""
+ return """dll: str = "/games/Lossless Scaling/Lossless.dll",
+ multiplier: int = 1,
+ flow_scale: float = 0.8,
+ performance_mode: bool = True,
+ hdr_mode: bool = False,
+ experimental_present_mode: str = "fifo",
+ dxvk_frame_rate: int = 0,
+ enable_wow64: bool = False,
+ disable_steamdeck_mode: bool = False,
+ mangohud_workaround: bool = False,
+ disable_vkbasalt: bool = False"""
+
+
+def create_config_dict(**kwargs) -> ConfigurationData:
+ """Create configuration dictionary from keyword arguments"""
+ return cast(ConfigurationData, {
+ "dll": kwargs.get("dll"),
+ "multiplier": kwargs.get("multiplier"),
+ "flow_scale": kwargs.get("flow_scale"),
+ "performance_mode": kwargs.get("performance_mode"),
+ "hdr_mode": kwargs.get("hdr_mode"),
+ "experimental_present_mode": kwargs.get("experimental_present_mode"),
+ "dxvk_frame_rate": kwargs.get("dxvk_frame_rate"),
+ "enable_wow64": kwargs.get("enable_wow64"),
+ "disable_steamdeck_mode": kwargs.get("disable_steamdeck_mode"),
+ "mangohud_workaround": kwargs.get("mangohud_workaround"),
+ "disable_vkbasalt": kwargs.get("disable_vkbasalt"),
+ })
+
+
+# Field lists for dynamic operations
+TOML_FIELDS = ['dll', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode']
+SCRIPT_FIELDS = ['dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt']
+ALL_FIELDS = ['dll', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt']
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index 47d0ebc..b9ee174 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -6,7 +6,9 @@ from pathlib import Path
from typing import Dict, Any
from .base_service import BaseService
-from .config_schema import ConfigurationManager, ConfigurationData, CONFIG_SCHEMA
+from .config_schema import ConfigurationManager, CONFIG_SCHEMA
+from .config_schema_generated import ConfigurationData, get_script_generation_logic
+from .configuration_helpers_generated import log_configuration_update
from .types import ConfigurationResponse
@@ -60,34 +62,59 @@ class ConfigurationService(BaseService):
f"Using default configuration due to parse error: {str(e)}",
config=config)
- def update_config(self, dll: str, multiplier: int, flow_scale: float,
- performance_mode: bool, hdr_mode: bool,
- experimental_present_mode: str = "fifo",
- dxvk_frame_rate: int = 0,
- enable_wow64: bool = False,
- disable_steamdeck_mode: bool = False) -> ConfigurationResponse:
- """Update TOML configuration
+ def update_config_from_dict(self, config: ConfigurationData) -> ConfigurationResponse:
+ """Update TOML configuration from configuration dictionary (eliminates parameter duplication)
Args:
- dll: Path to Lossless.dll
- multiplier: LSFG multiplier value
- flow_scale: LSFG flow scale value
- performance_mode: Whether to enable performance mode
- hdr_mode: Whether to enable HDR mode
- experimental_present_mode: Experimental Vulkan present mode override
- dxvk_frame_rate: Frame rate cap for DirectX games, before frame multiplier (0 = disabled)
- enable_wow64: Whether to enable PROTON_USE_WOW64=1 for 32-bit games
- disable_steamdeck_mode: Whether to disable Steam Deck mode
+ config: Complete configuration data dictionary
Returns:
ConfigurationResponse with success status
"""
try:
- # Create configuration from individual arguments
- config = ConfigurationManager.create_config_from_args(
- dll, multiplier, flow_scale, performance_mode, hdr_mode,
- experimental_present_mode, dxvk_frame_rate, enable_wow64, disable_steamdeck_mode
- )
+ # Generate TOML content using centralized manager
+ toml_content = ConfigurationManager.generate_toml_content(config)
+
+ # Ensure config directory exists
+ self.config_dir.mkdir(parents=True, exist_ok=True)
+
+ # Write the updated config directly to preserve inode for file watchers
+ self._write_file(self.config_file_path, toml_content, 0o644)
+
+ # Update the launch script with the new configuration
+ script_result = self.update_lsfg_script(config)
+ if not script_result["success"]:
+ self.log.warning(f"Failed to update launch script: {script_result['error']}")
+
+ # Log with dynamic field listing
+ field_values = ", ".join(f"{k}={repr(v)}" for k, v in config.items())
+ self.log.info(f"Updated lsfg configuration: {field_values}")
+
+ return self._success_response(ConfigurationResponse,
+ "lsfg configuration updated successfully",
+ config=config)
+
+ except (OSError, IOError) as e:
+ error_msg = f"Error updating lsfg config: {str(e)}"
+ self.log.error(error_msg)
+ return self._error_response(ConfigurationResponse, str(e), config=None)
+ except ValueError as e:
+ error_msg = f"Invalid configuration arguments: {str(e)}"
+ self.log.error(error_msg)
+ return self._error_response(ConfigurationResponse, str(e), config=None)
+
+ def update_config(self, **kwargs) -> ConfigurationResponse:
+ """Update TOML configuration using generated schema - SIMPLIFIED WITH GENERATED CODE
+
+ Args:
+ **kwargs: Configuration field values (see shared_config.py for available fields)
+
+ Returns:
+ ConfigurationResponse with success status
+ """
+ try:
+ # Create configuration from keyword arguments using generated function
+ config = ConfigurationManager.create_config_from_args(**kwargs)
# Generate TOML content using centralized manager
toml_content = ConfigurationManager.generate_toml_content(config)
@@ -103,12 +130,8 @@ class ConfigurationService(BaseService):
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
- self.log.info(f"Updated lsfg TOML configuration: "
- f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, "
- f"performance_mode={performance_mode}, hdr_mode={hdr_mode}, "
- f"experimental_present_mode='{experimental_present_mode}', "
- f"dxvk_frame_rate={dxvk_frame_rate}, "
- f"enable_wow64={enable_wow64}, disable_steamdeck_mode={disable_steamdeck_mode}")
+ # Use auto-generated logging
+ log_configuration_update(self.log, config)
return self._success_response(ConfigurationResponse,
"lsfg configuration updated successfully",
@@ -143,8 +166,9 @@ class ConfigurationService(BaseService):
else:
config = current_response["config"]
- # Update just the DLL path
- config["dll"] = dll_path
+ # Update just the DLL path - USE GENERATED CONSTANTS
+ from .config_schema_generated import DLL
+ config[DLL] = dll_path
# Generate TOML content and write it
toml_content = ConfigurationManager.generate_toml_content(config)
@@ -207,22 +231,14 @@ class ConfigurationService(BaseService):
"# This script sets up the environment for lsfg-vk to work with the plugin configuration"
]
- # Add optional export statements based on configuration
- if config.get("enable_wow64", False):
- lines.append("export PROTON_USE_WOW64=1")
-
- if config.get("disable_steamdeck_mode", False):
- lines.append("export SteamDeck=0")
-
- # Add DXVK_FRAME_RATE if dxvk_frame_rate is set
- dxvk_frame_rate = config.get("dxvk_frame_rate", 0)
- if dxvk_frame_rate > 0:
- lines.append(f"export DXVK_FRAME_RATE={dxvk_frame_rate}")
-
- # Always add the LSFG_PROCESS export
- lines.append("export LSFG_PROCESS=decky-lsfg-vk")
+ # Use auto-generated script generation logic
+ generate_script_lines = get_script_generation_logic()
+ lines.extend(generate_script_lines(config))
- # Add the execution line
- lines.append('exec "$@"')
+ # Always add the LSFG_PROCESS export and execution line
+ lines.extend([
+ "export LSFG_PROCESS=decky-lsfg-vk",
+ 'exec "$@"'
+ ])
return "\n".join(lines) + "\n"
diff --git a/py_modules/lsfg_vk/configuration_helpers_generated.py b/py_modules/lsfg_vk/configuration_helpers_generated.py
new file mode 100644
index 0000000..f9f4a65
--- /dev/null
+++ b/py_modules/lsfg_vk/configuration_helpers_generated.py
@@ -0,0 +1,22 @@
+"""
+Auto-generated configuration helper functions from shared_config.py
+DO NOT EDIT THIS FILE MANUALLY - it will be overwritten on build
+"""
+
+from typing import Dict, Any
+from .config_schema_generated import ConfigurationData, ALL_FIELDS
+
+
+def log_configuration_update(logger, config: ConfigurationData) -> None:
+ """Log configuration update with all field values"""
+ logger.info(f"Updated lsfg TOML configuration: dll={config['dll']}, multiplier={config['multiplier']}, flow_scale={config['flow_scale']}, performance_mode={config['performance_mode']}, hdr_mode={config['hdr_mode']}, experimental_present_mode={config['experimental_present_mode']}, dxvk_frame_rate={config['dxvk_frame_rate']}, enable_wow64={config['enable_wow64']}, disable_steamdeck_mode={config['disable_steamdeck_mode']}, mangohud_workaround={config['mangohud_workaround']}, disable_vkbasalt={config['disable_vkbasalt']}")
+
+
+def get_config_field_names() -> list[str]:
+ """Get all configuration field names"""
+ return ALL_FIELDS.copy()
+
+
+def extract_config_values(config: ConfigurationData) -> Dict[str, Any]:
+ """Extract configuration values as a dictionary"""
+ return {field: config[field] for field in ALL_FIELDS}
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index b340093..996a03f 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -121,9 +121,10 @@ class InstallationService(BaseService):
self._write_file(self.config_file_path, toml_content, 0o644)
self.log.info(f"Created config file at {self.config_file_path}")
- # Log detected DLL path if found
- if config["dll"]:
- self.log.info(f"Configured DLL path: {config['dll']}")
+ # Log detected DLL path if found - USE GENERATED CONSTANTS
+ from .config_schema_generated import DLL
+ if config[DLL]:
+ self.log.info(f"Configured DLL path: {config[DLL]}")
def _create_lsfg_launch_script(self) -> None:
"""Create the ~/lsfg launch script for easier game setup"""
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 425d7e7..8fa2435 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -184,32 +184,20 @@ class Plugin:
"defaults": ConfigurationManager.get_defaults()
}
- async def update_lsfg_config(self, dll: str, multiplier: int, flow_scale: float,
- performance_mode: bool, hdr_mode: bool,
- experimental_present_mode: str = "fifo",
- dxvk_frame_rate: int = 0,
- enable_wow64: bool = False,
- disable_steamdeck_mode: bool = False) -> Dict[str, Any]:
- """Update lsfg TOML configuration
+ async def update_lsfg_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
+ """Update lsfg TOML configuration using object-based API (single source of truth)
Args:
- dll: Path to Lossless.dll
- multiplier: LSFG multiplier value
- flow_scale: LSFG flow scale value
- performance_mode: Whether to enable performance mode
- hdr_mode: Whether to enable HDR mode
- experimental_present_mode: Experimental Vulkan present mode override
- dxvk_frame_rate: Frame rate cap for DirectX games, before frame multiplier (0 = disabled)
- enable_wow64: Whether to enable PROTON_USE_WOW64=1 for 32-bit games
- disable_steamdeck_mode: Whether to disable Steam Deck mode
+ config: Configuration data dictionary containing all settings
Returns:
ConfigurationResponse dict with success status
"""
- return self.configuration_service.update_config(
- dll, multiplier, flow_scale, performance_mode, hdr_mode,
- experimental_present_mode, dxvk_frame_rate, enable_wow64, disable_steamdeck_mode
- )
+ # 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