summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-08-03 14:32:35 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-08-03 14:32:35 -0400
commite0681b06efdee3dcacb6ddfca515d947b802faff (patch)
tree52c1cad716b92c784d2946d063cc36b34255ea5e
parent8df4d48cd89d636940982e60bb51b688226e630c (diff)
parentb936baab13b026d4355844d94616a622753a7370 (diff)
downloaddecky-lsfg-vk-e0681b06efdee3dcacb6ddfca515d947b802faff.tar.gz
decky-lsfg-vk-e0681b06efdee3dcacb6ddfca515d947b802faff.zip
Merge main and normalize translation locale handling
-rw-r--r--package.json16
-rw-r--r--pnpm-lock.yaml7
-rw-r--r--py_modules/lsfg_vk/config_schema.py25
-rw-r--r--py_modules/lsfg_vk/config_schema_generated.py45
-rw-r--r--py_modules/lsfg_vk/configuration.py55
-rw-r--r--py_modules/lsfg_vk/configuration_helpers_generated.py22
-rw-r--r--py_modules/lsfg_vk/constants.py4
-rw-r--r--py_modules/lsfg_vk/installation.py50
-rw-r--r--scripts/generate_python_boilerplate.py111
-rw-r--r--src/components/ConfigurationSection.tsx11
-rw-r--r--src/components/Content.tsx2
-rw-r--r--src/components/UsageInstructions.tsx7
-rw-r--r--src/config/configSchema.ts118
-rw-r--r--src/hooks/useLsfgHooks.ts8
-rw-r--r--src/i18n/i18n.ts12
-rw-r--r--src/i18n/languages.json2
16 files changed, 134 insertions, 361 deletions
diff --git a/package.json b/package.json
index 0791681..0548f7e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "decky-lsfg-vk",
- "version": "0.12.3",
+ "version": "0.12.7",
"description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer",
"type": "module",
"scripts": {
@@ -42,6 +42,10 @@
"react-icons": "^5.3.0",
"tslib": "^2.7.0"
},
+ "optionalDependencies": {
+ "@rollup/rollup-linux-x64-gnu": "4.22.5",
+ "@rollup/rollup-linux-x64-musl": "4.22.5"
+ },
"remote_binary_bundling" : true,
"remote_binary": [
{
@@ -63,9 +67,19 @@
"name": "org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak",
"url": "https://github.com/PancakeTAS/lsfg-vk/releases/download/v1.0.0/org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak",
"sha256hash": "0651bda96751ef0f1314a5179585926a0cd354476790ca2616662c39fe6fae54"
+ },
+ {
+ "name": "liblsfg-vk-arm64.so",
+ "url": "https://github.com/Janleyx/decky-lsfg-vk-for-Ayn-Odin-2/releases/download/arm64-layer-3e89e54/liblsfg-vk-arm64.so",
+ "sha256hash": "2e84f8d1a1dd0344474846f6252d6f948f72caaaeab3cd316c04254be05a9949"
}
],
"pnpm": {
+ "supportedArchitectures": {
+ "cpu": ["arm64", "x64"],
+ "libc": ["glibc", "musl"],
+ "os": ["darwin", "linux"]
+ },
"peerDependencyRules": {
"ignoreMissing": [
"react",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8f954a4..cad45b2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,13 @@ importers:
tslib:
specifier: ^2.7.0
version: 2.7.0
+ optionalDependencies:
+ '@rollup/rollup-linux-x64-gnu':
+ specifier: 4.22.5
+ version: 4.22.5
+ '@rollup/rollup-linux-x64-musl':
+ specifier: 4.22.5
+ version: 4.22.5
devDependencies:
'@decky/rollup':
specifier: ^1.0.1
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index 4ab2dcb..f76b5f2 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -71,10 +71,6 @@ SCRIPT_ONLY_FIELDS = {
# Complete configuration schema (TOML + script-only fields)
COMPLETE_CONFIG_SCHEMA = {**CONFIG_SCHEMA, **SCRIPT_ONLY_FIELDS}
-
-# Import auto-generated configuration components
-from .config_schema_generated import ConfigurationData, get_script_parsing_logic, get_script_generation_logic
-
# Constants for profile management
DEFAULT_PROFILE_NAME = "decky-lsfg-vk"
GLOBAL_SECTION_FIELDS = {"dll", "no_fp16"}
@@ -182,7 +178,7 @@ class ConfigurationManager:
"profiles": {DEFAULT_PROFILE_NAME: config},
"global_config": {
"dll": config.get("dll", ""),
- "no_fp16": False # Always enabled even if previously set
+ "no_fp16": config.get("no_fp16", False)
}
}
return ConfigurationManager.generate_toml_content_multi_profile(profile_data)
@@ -192,7 +188,7 @@ class ConfigurationManager:
"""Generate TOML configuration file content with multiple profiles"""
lines = ["version = 1"]
lines.append("")
-
+
# Add global section with global fields
lines.append("[global]")
@@ -206,10 +202,11 @@ class ConfigurationManager:
if dll_path:
lines.append(f"# specify where Lossless.dll is stored")
lines.append(f'dll = "{dll_path}"')
- lines.append("")
-
+ lines.append("")
+
lines.append(f"# FP16 acceleration")
- lines.append(f"no_fp16 = false")
+ no_fp16 = bool(profile_data["global_config"].get("no_fp16", False))
+ lines.append(f"no_fp16 = {str(no_fp16).lower()}")
lines.append("")
# Add game sections for each profile
@@ -343,8 +340,7 @@ class ConfigurationManager:
elif key == "dll":
global_config["dll"] = value
elif key == "no_fp16":
- # Always enforce FP16 to be enabled (no_fp16 = false)
- global_config["no_fp16"] = False
+ global_config["no_fp16"] = value.lower() in ('true', '1', 'yes', 'on')
# Handle game section
elif in_game_section:
@@ -447,13 +443,6 @@ class ConfigurationManager:
return cast(ConfigurationData, merged_config)
@staticmethod
- @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)
-
- @staticmethod
def normalize_profile_name(profile_name: str) -> str:
"""Normalize profile name by converting spaces to dashes and trimming
diff --git a/py_modules/lsfg_vk/config_schema_generated.py b/py_modules/lsfg_vk/config_schema_generated.py
index 53e9693..b320a97 100644
--- a/py_modules/lsfg_vk/config_schema_generated.py
+++ b/py_modules/lsfg_vk/config_schema_generated.py
@@ -3,7 +3,7 @@ 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 typing import TypedDict, Dict, Any, Union
from enum import Enum
import sys
from pathlib import Path
@@ -122,47 +122,4 @@ def get_script_generation_logic():
return generate_script_lines
-def get_function_parameters() -> str:
- """Return function signature parameters"""
- return """dll: str = "/games/Lossless Scaling/Lossless.dll",
- no_fp16: bool = False,
- multiplier: int = 1,
- flow_scale: float = 0.8,
- performance_mode: bool = False,
- 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,
- force_enable_vkbasalt: bool = False,
- enable_wsi: bool = False,
- enable_zink: bool = False"""
-
-
-def create_config_dict(**kwargs) -> ConfigurationData:
- """Create configuration dictionary from keyword arguments"""
- return cast(ConfigurationData, {
- "dll": kwargs.get("dll"),
- "no_fp16": kwargs.get("no_fp16"),
- "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"),
- "force_enable_vkbasalt": kwargs.get("force_enable_vkbasalt"),
- "enable_wsi": kwargs.get("enable_wsi"),
- "enable_zink": kwargs.get("enable_zink"),
- })
-
-
-# Field lists for dynamic operations
-TOML_FIELDS = ['dll', 'no_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode']
-SCRIPT_FIELDS = ['dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
ALL_FIELDS = ['dll', 'no_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'hdr_mode', 'experimental_present_mode', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink']
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index 74a3694..fce3738 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -8,7 +8,7 @@ from typing import Dict, Any
from .base_service import BaseService
from .config_schema import ConfigurationManager, CONFIG_SCHEMA, ProfileData, DEFAULT_PROFILE_NAME
from .config_schema_generated import ConfigurationData, get_script_generation_logic
-from .configuration_helpers_generated import log_configuration_update
+from .constants import ARMADA_DEVICE_ENV, ARMADA_GAME_LAUNCH
from .types import ConfigurationResponse, ProfilesResponse, ProfileResponse
@@ -81,29 +81,6 @@ class ConfigurationService(BaseService):
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:
- config = ConfigurationManager.create_config_from_args(**kwargs)
-
- return self.update_config_from_dict(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_lsfg_script(self, config: ConfigurationData) -> ConfigurationResponse:
"""Update the ~/lsfg launch script with current configuration
@@ -147,10 +124,8 @@ class ConfigurationService(BaseService):
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(config))
- lines.extend([
- "export LSFG_PROCESS=decky-lsfg-vk",
- 'exec "$@"'
- ])
+ lines.append("export LSFG_PROCESS=decky-lsfg-vk")
+ lines.extend(self._generate_game_launch_lines())
return "\n".join(lines) + "\n"
@@ -178,12 +153,28 @@ class ConfigurationService(BaseService):
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(merged_config))
- lines.extend([
- f"export LSFG_PROCESS={current_profile}",
- 'exec "$@"'
- ])
+ lines.append(f"export LSFG_PROCESS={current_profile}")
+ lines.extend(self._generate_game_launch_lines())
return "\n".join(lines) + "\n"
+
+ @staticmethod
+ def _generate_game_launch_lines() -> list[str]:
+ """Generate a portable exec block with Armada's host wrapper."""
+ device_env = ARMADA_DEVICE_ENV.as_posix()
+ game_launch = ARMADA_GAME_LAUNCH.as_posix()
+ return [
+ f'armada_game_launch="{game_launch}"',
+ 'for argument in "$@"; do',
+ ' if [ "$argument" = "$armada_game_launch" ]; then',
+ ' exec "$@"',
+ " fi",
+ "done",
+ f'if [ -f "{device_env}" ] && [ -x "$armada_game_launch" ]; then',
+ ' exec "$armada_game_launch" "$@"',
+ "fi",
+ 'exec "$@"',
+ ]
def _get_profile_data(self) -> ProfileData:
"""Get current profile data from config file"""
diff --git a/py_modules/lsfg_vk/configuration_helpers_generated.py b/py_modules/lsfg_vk/configuration_helpers_generated.py
deleted file mode 100644
index 1383174..0000000
--- a/py_modules/lsfg_vk/configuration_helpers_generated.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-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']}, no_fp16={config['no_fp16']}, 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']}, force_enable_vkbasalt={config['force_enable_vkbasalt']}, enable_wsi={config['enable_wsi']}, enable_zink={config['enable_zink']}")
-
-
-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/constants.py b/py_modules/lsfg_vk/constants.py
index 3d8e44a..023894f 100644
--- a/py_modules/lsfg_vk/constants.py
+++ b/py_modules/lsfg_vk/constants.py
@@ -14,6 +14,7 @@ CONFIG_FILENAME = "conf.toml"
LIB_FILENAME = "liblsfg-vk.so"
JSON_FILENAME = "VkLayer_LS_frame_generation.json"
ZIP_FILENAME = "lsfg-vk_noui.zip"
+ARM_LIB_FILENAME = "liblsfg-vk-arm64.so"
FLATPAK_23_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_23.08.flatpak"
FLATPAK_24_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_24.08.flatpak"
@@ -24,6 +25,9 @@ JSON_EXT = ".json"
BIN_DIR = "bin"
+ARMADA_DEVICE_ENV = Path("/usr/libexec/armada/device-env")
+ARMADA_GAME_LAUNCH = Path("/usr/libexec/armada/armada-game-launch")
+
STEAM_COMMON_PATH = Path("steamapps/common/Lossless Scaling")
LOSSLESS_DLL_NAME = "Lossless.dll"
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index 4329d49..ce47268 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -3,6 +3,7 @@ Installation service for lsfg-vk.
"""
import os
+import platform
import shutil
import traceback
import zipfile
@@ -14,7 +15,7 @@ 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
+ SO_EXT, JSON_EXT, ARM_LIB_FILENAME, ARMADA_DEVICE_ENV
)
from .config_schema import ConfigurationManager
from .types import InstallationResponse, UninstallationResponse, InstallationCheckResponse
@@ -48,6 +49,13 @@ class InstallationService(BaseService):
self._extract_and_install_files(zip_path)
+ # If on ARM, overwrite the .so with the ARM version
+ if self._is_arm_architecture():
+ self.log.info("Detected ARM architecture, using ARM binary")
+ arm_so_path = plugin_dir / BIN_DIR / ARM_LIB_FILENAME
+ self._copy_plugin_file(arm_so_path, self.lib_file)
+ self.log.info(f"Overwrote with ARM binary: {self.lib_file}")
+
self._create_config_file()
self._create_lsfg_launch_script()
@@ -64,6 +72,42 @@ class InstallationService(BaseService):
self.log.error(error_msg)
return self._error_response(InstallationResponse, str(e), message="")
+ def _is_arm_architecture(self) -> bool:
+ """Check if running on ARM architecture
+
+ Returns:
+ True if running on ARM (aarch64), False otherwise
+ """
+ if platform.machine().lower() in ('aarch64', 'arm64'):
+ return True
+
+ # Decky runs through FEX on Armada, so Python reports x86_64 even
+ # though the host is AArch64. Armada exposes this native helper only
+ # on its ARM image, including inside Decky's FEX rootfs.
+ if ARMADA_DEVICE_ENV.is_file():
+ self.log.info("Detected native AArch64 Armada host through device-env")
+ return True
+
+ # Fall back to the native PID 1 ELF header. e_machine 183 is AArch64.
+ try:
+ with Path('/proc/1/exe').open('rb') as host_executable:
+ elf_header = host_executable.read(20)
+ if elf_header[:4] == b'\x7fELF' and elf_header[5] in (1, 2):
+ byte_order = 'little' if elf_header[5] == 1 else 'big'
+ if int.from_bytes(elf_header[18:20], byte_order) == 183:
+ self.log.info("Detected native AArch64 host through PID 1")
+ return True
+ except OSError as e:
+ self.log.debug(f"Could not inspect native host architecture: {e}")
+
+ return False
+
+ @staticmethod
+ def _copy_plugin_file(src_file: Path, dst_file: Path) -> None:
+ """Copy plugin content without preserving FEX-incompatible metadata."""
+ shutil.copyfile(src_file, dst_file)
+ dst_file.chmod(0o644)
+
def _extract_and_install_files(self, zip_path: Path) -> None:
"""Extract zip file and install files to appropriate locations
@@ -101,7 +145,7 @@ class InstallationService(BaseService):
if file_path.suffix == JSON_EXT and file == JSON_FILENAME:
self._copy_and_fix_json_file(src_file, dst_file)
else:
- shutil.copy2(src_file, dst_file)
+ self._copy_plugin_file(src_file, dst_file)
self.log.info(f"Copied {file} to {dst_file}")
@@ -131,7 +175,7 @@ class InstallationService(BaseService):
except (json.JSONDecodeError, KeyError, OSError) as e:
self.log.error(f"Error fixing JSON file {src_file}: {e}")
# Fallback to simple copy if JSON modification fails
- shutil.copy2(src_file, dst_file)
+ self._copy_plugin_file(src_file, dst_file)
def _create_config_file(self) -> None:
"""Create or update the TOML config file in ~/.config/lsfg-vk with default configuration and detected DLL path
diff --git a/scripts/generate_python_boilerplate.py b/scripts/generate_python_boilerplate.py
index dc51dae..a03aa2b 100644
--- a/scripts/generate_python_boilerplate.py
+++ b/scripts/generate_python_boilerplate.py
@@ -56,38 +56,6 @@ def generate_typed_dict() -> str:
return "\n".join(lines)
-def generate_function_signature() -> str:
- """Generate function signature for update_config and create_config_from_args"""
- params = []
-
- for field_name, field_def in CONFIG_SCHEMA_DEF.items():
- python_type = get_python_type(ConfigFieldType(field_def["fieldType"]))
- default = field_def["default"]
-
- # Format default value
- if isinstance(default, str):
- default_str = f'"{default}"'
- elif isinstance(default, bool):
- default_str = str(default)
- else:
- default_str = str(default)
-
- params.append(f"{field_name}: {python_type} = {default_str}")
-
- return ",\n ".join(params)
-
-
-def generate_config_dict_creation() -> str:
- """Generate dictionary creation for create_config_from_args"""
- lines = [" return cast(ConfigurationData, {"]
-
- for field_name in CONFIG_SCHEMA_DEF.keys():
- lines.append(f' "{field_name}": kwargs.get("{field_name}"),')
-
- lines.append(" })")
- return "\n".join(lines)
-
-
def generate_script_parsing() -> str:
"""Generate script content parsing logic"""
lines = []
@@ -195,17 +163,6 @@ def generate_script_generation() -> str:
return "\n".join(lines)
-def generate_log_statement() -> str:
- """Generate logging statement with all field values"""
- field_parts = []
-
- for field_name in CONFIG_SCHEMA_DEF.keys():
- field_parts.append(f"{field_name}={{{field_name}}}")
-
- log_format = ", ".join(field_parts)
- return f' self.log.info(f"Updated lsfg TOML configuration: {log_format}")'
-
-
def generate_complete_schema_file() -> str:
"""Generate complete config_schema_generated.py file"""
@@ -221,7 +178,7 @@ def generate_complete_schema_file() -> str:
'DO NOT EDIT THIS FILE MANUALLY - it will be overwritten on build',
'"""',
'',
- 'from typing import TypedDict, Dict, Any, Union, cast',
+ 'from typing import TypedDict, Dict, Any, Union',
'from enum import Enum',
'import sys',
'from pathlib import Path',
@@ -267,19 +224,6 @@ def generate_complete_schema_file() -> str:
' return generate_script_lines',
'',
'',
- 'def get_function_parameters() -> str:',
- ' """Return function signature parameters"""',
- f' return """{generate_function_signature()}"""',
- '',
- '',
- 'def create_config_dict(**kwargs) -> ConfigurationData:',
- ' """Create configuration dictionary from keyword arguments"""',
- f'{generate_config_dict_creation().replace(" return cast(ConfigurationData, {", " return cast(ConfigurationData, {").replace(" })", " })")}',
- '',
- '',
- '# Field lists for dynamic operations',
- f'TOML_FIELDS = {[name for name, field in CONFIG_SCHEMA_DEF.items() if field.get("location") == "toml"]}',
- f'SCRIPT_FIELDS = {[name for name, field in CONFIG_SCHEMA_DEF.items() if field.get("location") == "script"]}',
f'ALL_FIELDS = {list(CONFIG_SCHEMA_DEF.keys())}',
''
]
@@ -287,66 +231,17 @@ def generate_complete_schema_file() -> str:
return '\n'.join(lines)
-def generate_complete_configuration_helpers() -> str:
- """Generate configuration_helpers_generated.py file"""
-
- # Generate the log format string using config parameter
- log_parts = []
- for field_name in CONFIG_SCHEMA_DEF.keys():
- log_parts.append(f"{field_name}={{config['{field_name}']}}")
- log_format = ", ".join(log_parts)
-
- lines = [
- '"""',
- '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"""',
- f' logger.info(f"Updated lsfg TOML configuration: {log_format}")',
- '',
- '',
- '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}',
- ''
- ]
-
- return '\n'.join(lines)
-
-
def main():
"""Generate complete Python configuration files"""
try:
# Create generated files in py_modules/lsfg_vk/
target_dir = project_root / "py_modules" / "lsfg_vk"
-
+
# Generate the complete schema file
schema_content = generate_complete_schema_file()
schema_file = target_dir / "config_schema_generated.py"
schema_file.write_text(schema_content)
- print(f"āœ… Generated {schema_file.relative_to(project_root)}")
-
- # Generate configuration helpers
- helpers_content = generate_complete_configuration_helpers()
- helpers_file = target_dir / "configuration_helpers_generated.py"
- helpers_file.write_text(helpers_content)
- print(f"āœ… Generated {helpers_file.relative_to(project_root)}")
-
- print(f"\nšŸŽÆ Ready-to-use files generated!")
- print(" Import these in your main files:")
- print(" - from .config_schema_generated import ConfigurationData, get_script_parsing_logic, etc.")
- print(" - from .configuration_helpers_generated import log_configuration_update, etc.")
+ print(f"Generated {schema_file.relative_to(project_root)}")
except Exception as e:
print(f"āŒ Error generating Python files: {e}")
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx
index c988f9f..a10aab2 100644
--- a/src/components/ConfigurationSection.tsx
+++ b/src/components/ConfigurationSection.tsx
@@ -3,7 +3,7 @@ import { useState, useEffect } from "react";
import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri";
import { ConfigurationData } from "../config/configSchema";
import {
- FLOW_SCALE, PERFORMANCE_MODE, HDR_MODE,
+ FLOW_SCALE, NO_FP16, PERFORMANCE_MODE, HDR_MODE,
EXPERIMENTAL_PRESENT_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE,
MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK
} from "../config/generatedConfigSchema";
@@ -127,6 +127,15 @@ export function ConfigurationSection({
</PanelSectionRow>
<PanelSectionRow>
+ <ToggleField
+ label="FP16 Acceleration"
+ description="Use FP16 shaders when supported"
+ checked={!config.no_fp16}
+ onChange={(value) => onConfigChange(NO_FP16, !value)}
+ />
+ </PanelSectionRow>
+
+ <PanelSectionRow>
<SliderField
label={`${t('CONFIG_BASE_FPS_CAP', 'Base FPS Cap')}${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : ` (${t('CONFIG_BASE_FPS_CAP_OFF', 'Off')})`}`}
description={t('CONFIG_BASE_FPS_CAP_DESC', 'Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)')}
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index c785a50..5988e79 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -144,7 +144,7 @@ export function Content() {
</>
)}
- <UsageInstructions config={config} />
+ <UsageInstructions />
<PanelSectionRow>
<ButtonItem
diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx
index 6d32506..36e31cf 100644
--- a/src/components/UsageInstructions.tsx
+++ b/src/components/UsageInstructions.tsx
@@ -1,12 +1,7 @@
import { PanelSectionRow } from "@decky/ui";
-import { ConfigurationData } from "../config/configSchema";
import t from '../i18n/i18n';
-interface UsageInstructionsProps {
- config: ConfigurationData;
-}
-
-export function UsageInstructions({ config: _config }: UsageInstructionsProps) {
+export function UsageInstructions() {
return (
<>
<PanelSectionRow>
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts
index 6c6cf19..8b4fc1e 100644
--- a/src/config/configSchema.ts
+++ b/src/config/configSchema.ts
@@ -1,15 +1,3 @@
-/**
- * Configuration schema and management for LSFG VK plugin
- *
- * This file re-exports auto-generated configuration constants from generatedConfigSchema.ts
- * and provides the ConfigurationManager class for handling configuration operations.
- */
-
-import { callable } from "@decky/api";
-import type { ConfigurationData } from './generatedConfigSchema';
-import { getDefaults } from './generatedConfigSchema';
-import { updateLsfgConfig } from "../api/lsfgApi";
-
export {
ConfigFieldType,
ConfigField,
@@ -23,109 +11,3 @@ export {
DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT,
FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK
} from './generatedConfigSchema';
-
-/**
- * Configuration management class
- * Handles CRUD operations for plugin configuration
- */
-export class ConfigurationManager {
- private static instance: ConfigurationManager;
- private _config: ConfigurationData | null = null;
-
- private getConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("get_configuration");
- private resetConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("reset_configuration");
-
- private constructor() {}
-
- static getInstance(): ConfigurationManager {
- if (!ConfigurationManager.instance) {
- ConfigurationManager.instance = new ConfigurationManager();
- }
- return ConfigurationManager.instance;
- }
-
- /**
- * Get default configuration values
- */
- static getDefaults(): ConfigurationData {
- return getDefaults();
- }
-
- /**
- * Load configuration from backend
- */
- async loadConfig(): Promise<ConfigurationData> {
- try {
- const result = await this.getConfiguration();
- if (result.success && result.data) {
- this._config = result.data;
- return this._config;
- } else {
- throw new Error(result.error || 'Failed to load configuration');
- }
- } catch (error) {
- console.error('Error loading configuration:', error);
- throw error;
- }
- }
-
- /**
- * Save configuration to backend
- */
- async saveConfig(config: ConfigurationData): Promise<void> {
- try {
- const result = await updateLsfgConfig(config);
- if (result.success) {
- this._config = config;
- } else {
- throw new Error(result.error || 'Failed to save configuration');
- }
- } catch (error) {
- console.error('Error saving configuration:', error);
- throw error;
- }
- }
-
- /**
- * Update a single configuration field
- */
- async updateField(fieldName: keyof ConfigurationData, value: any): Promise<void> {
- if (!this._config) {
- await this.loadConfig();
- }
-
- const updatedConfig = {
- ...this._config!,
- [fieldName]: value
- };
-
- await this.saveConfig(updatedConfig);
- }
-
- /**
- * Get current configuration (cached)
- */
- getConfig(): ConfigurationData | null {
- return this._config;
- }
-
- /**
- * Reset configuration to defaults
- */
- async resetToDefaults(): Promise<ConfigurationData> {
- try {
- const result = await this.resetConfiguration();
- if (result.success && result.data) {
- this._config = result.data;
- return this._config;
- } else {
- throw new Error(result.error || 'Failed to reset configuration');
- }
- } catch (error) {
- console.error('Error resetting configuration:', error);
- throw error;
- }
- }
-}
-
-export const configManager = ConfigurationManager.getInstance();
diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts
index adc18ba..d9bbe3e 100644
--- a/src/hooks/useLsfgHooks.ts
+++ b/src/hooks/useLsfgHooks.ts
@@ -6,7 +6,7 @@ import {
updateLsfgConfigFromObject,
type ConfigUpdateResult
} from "../api/lsfgApi";
-import { ConfigurationData, ConfigurationManager } from "../config/configSchema";
+import { ConfigurationData, getDefaults } from "../config/configSchema";
import { showErrorToast, ToastMessages } from "../utils/toastUtils";
export function useInstallationStatus() {
@@ -71,7 +71,7 @@ export function useDllDetection() {
}
export function useLsfgConfig() {
- const [config, setConfig] = useState<ConfigurationData>(() => ConfigurationManager.getDefaults());
+ const [config, setConfig] = useState<ConfigurationData>(() => getDefaults());
const loadLsfgConfig = useCallback(async () => {
try {
@@ -80,11 +80,11 @@ export function useLsfgConfig() {
setConfig(result.config);
} else {
console.log("lsfg config not available, using defaults:", result.error);
- setConfig(ConfigurationManager.getDefaults());
+ setConfig(getDefaults());
}
} catch (error) {
console.error("Error loading lsfg config:", error);
- setConfig(ConfigurationManager.getDefaults());
+ setConfig(getDefaults());
}
}, []);
diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts
index 83ed3da..dc8de8b 100644
--- a/src/i18n/i18n.ts
+++ b/src/i18n/i18n.ts
@@ -2,6 +2,14 @@
// to generate for localhost/dev, run `build_i18n_json.sh` script
import * as languages from "./languages.json";
+const steamLanguageMap: Record<string, string> =
+ languages.steam_language_map as Record<string, string>;
+
+const normalizeLanguage = (language: string): string => {
+ const normalized = language.trim().toLowerCase();
+ return steamLanguageMap[normalized] ?? normalized;
+};
+
function getLangs() {
const langs = languages.language_metadata;
@@ -32,13 +40,13 @@ let cachedLang: string | undefined;
export const getCurrentLanguage = (): string => {
if (cachedLang) return cachedLang;
- const lang = window.LocalizationManager.m_rgLocalesToUse[0];
+ const lang = normalizeLanguage(window.LocalizationManager.m_rgLocalesToUse[0]);
cachedLang = lang;
return lang;
};
export const getLanguageName = (lang?: string): string => {
- const targetLang = lang || getCurrentLanguage();
+ const targetLang = normalizeLanguage(lang || getCurrentLanguage());
return LANGS[targetLang]?.name || targetLang;
};
diff --git a/src/i18n/languages.json b/src/i18n/languages.json
index 004d71c..c1a49b8 100644
--- a/src/i18n/languages.json
+++ b/src/i18n/languages.json
@@ -348,4 +348,4 @@
"CLIPBOARD_COPY_LAUNCH": "Copy Launch Option",
"CLIPBOARD_LSFG_FGMOD": "LSFG + DeckyFG"
}
-} \ No newline at end of file
+}