From 4d6360812d56923b42ee9bd5a13d2f95a1d1c958 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 27 Nov 2025 22:24:13 -0500 Subject: add experimental arm .so and overwrite logic on setup --- package.json | 7 ++++++- py_modules/lsfg_vk/constants.py | 1 + py_modules/lsfg_vk/installation.py | 22 +++++++++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 0174f9a..7d1ac8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-lsfg-vk", - "version": "0.12.2", + "version": "0.12.3", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { @@ -63,6 +63,11 @@ "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/xXJSONDeruloXx/lsfg-vk/releases/download/arm-test/liblsfg-vk.so", + "sha256hash": "c745893b3798eb1acb422775a43107fc3f2efb2ae2711090b90b04941d8de309" } ], "pnpm": { diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index 3d8e44a..795c1bf 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" diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 4329d49..f041ea3 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 ) from .config_schema import ConfigurationManager from .types import InstallationResponse, UninstallationResponse, InstallationCheckResponse @@ -48,6 +49,16 @@ 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 + if arm_so_path.exists(): + shutil.copy2(arm_so_path, self.lib_file) + self.log.info(f"Overwrote with ARM binary: {self.lib_file}") + else: + self.log.warning(f"ARM binary not found at {arm_so_path}, using x86_64 version") + self._create_config_file() self._create_lsfg_launch_script() @@ -64,6 +75,15 @@ 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/arm64), False otherwise + """ + machine = platform.machine().lower() + return machine in ('aarch64', 'arm64', 'armv8l', 'armv8b') + def _extract_and_install_files(self, zip_path: Path) -> None: """Extract zip file and install files to appropriate locations -- cgit v1.2.3 From 126c0a98558fa31ee00c72663e700321e822823a Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 27 Nov 2025 22:43:00 -0500 Subject: simplify check for arch --- py_modules/lsfg_vk/installation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index f041ea3..5e9ef73 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -53,11 +53,8 @@ class InstallationService(BaseService): if self._is_arm_architecture(): self.log.info("Detected ARM architecture, using ARM binary") arm_so_path = plugin_dir / BIN_DIR / ARM_LIB_FILENAME - if arm_so_path.exists(): - shutil.copy2(arm_so_path, self.lib_file) - self.log.info(f"Overwrote with ARM binary: {self.lib_file}") - else: - self.log.warning(f"ARM binary not found at {arm_so_path}, using x86_64 version") + shutil.copy2(arm_so_path, self.lib_file) + self.log.info(f"Overwrote with ARM binary: {self.lib_file}") self._create_config_file() -- cgit v1.2.3 From fc8724b850b7c59151522da8391963cc071d49f8 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 27 Nov 2025 22:45:37 -0500 Subject: simplify arch detect --- py_modules/lsfg_vk/installation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 5e9ef73..4f01be1 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -76,10 +76,9 @@ class InstallationService(BaseService): """Check if running on ARM architecture Returns: - True if running on ARM (aarch64/arm64), False otherwise + True if running on ARM (aarch64), False otherwise """ - machine = platform.machine().lower() - return machine in ('aarch64', 'arm64', 'armv8l', 'armv8b') + return platform.machine().lower() == 'aarch64' def _extract_and_install_files(self, zip_path: Path) -> None: """Extract zip file and install files to appropriate locations -- cgit v1.2.3 From 0a9ed38d9e5e3f67efc0ecdc0d549d67ccb86fb8 Mon Sep 17 00:00:00 2001 From: Christopher Lott Date: Thu, 7 May 2026 22:44:59 +0200 Subject: remove dead code across frontend and backend - Remove TS ConfigurationManager class in configSchema.ts (called non-existent backend methods; only getDefaults() was used, replaced with direct import) - Remove Python update_config method and create_config_from_args (unreachable since plugin.py routes through update_config_from_dict; also carried a duplicate @staticmethod that would crash at runtime) - Delete configuration_helpers_generated.py entirely (all three functions were unused) - Remove their generators from generate_python_boilerplate.py - Remove dead generated code from config_schema_generated.py (create_config_dict, get_function_parameters, TOML_FIELDS, SCRIPT_FIELDS) - Remove duplicate import in config_schema.py - Remove unused config prop from UsageInstructions component --- py_modules/lsfg_vk/config_schema.py | 11 -- py_modules/lsfg_vk/config_schema_generated.py | 45 +------- py_modules/lsfg_vk/configuration.py | 24 ----- .../lsfg_vk/configuration_helpers_generated.py | 22 ---- scripts/generate_python_boilerplate.py | 111 +------------------ src/components/Content.tsx | 2 +- src/components/UsageInstructions.tsx | 7 +- src/config/configSchema.ts | 118 --------------------- src/hooks/useLsfgHooks.ts | 8 +- 9 files changed, 10 insertions(+), 338 deletions(-) delete mode 100644 py_modules/lsfg_vk/configuration_helpers_generated.py diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 4ab2dcb..3a82bbd 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"} @@ -446,13 +442,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..e479f43 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -8,7 +8,6 @@ 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 .types import ConfigurationResponse, ProfilesResponse, ProfileResponse @@ -81,29 +80,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 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/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/Content.tsx b/src/components/Content.tsx index 3dc2696..3f94745 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -143,7 +143,7 @@ export function Content() { )} - + 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 { - 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 { - 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 { - 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 { - 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(() => ConfigurationManager.getDefaults()); + const [config, setConfig] = useState(() => 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()); } }, []); -- cgit v1.2.3 From 6aa69db7a6895c89ffa99498beb017ad12d1b9ce Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Tue, 7 Jul 2026 16:03:23 -0400 Subject: chore: bump ver to 0.12.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d1ac8b..a71b9cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-lsfg-vk", - "version": "0.12.3", + "version": "0.12.5", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { -- cgit v1.2.3 From 722ac0cf338d659e2d0553431285fbfb0cd978b5 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:08:42 -0400 Subject: add reproducible Armada ARM64 support --- .github/workflows/build-arm64-layer.yml | 72 +++++++++++++++++++++++++++++++++ package.json | 4 +- py_modules/lsfg_vk/installation.py | 36 +++++++++++++++-- 3 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build-arm64-layer.yml diff --git a/.github/workflows/build-arm64-layer.yml b/.github/workflows/build-arm64-layer.yml new file mode 100644 index 0000000..973a548 --- /dev/null +++ b/.github/workflows/build-arm64-layer.yml @@ -0,0 +1,72 @@ +name: Build ARM64 lsfg-vk layer + +on: + workflow_dispatch: + pull_request: + paths: + - .github/workflows/build-arm64-layer.yml + +permissions: + contents: read + +env: + SOURCE_REPOSITORY: https://github.com/FrankBarretta/lsfg-vk-android.git + SOURCE_COMMIT: 3e89e5439a98f55d5acb003d20039426ab24e69c + +jobs: + build: + name: Native AArch64 release build + runs-on: ubuntu-24.04-arm + + steps: + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install --yes --no-install-recommends \ + clang cmake g++ git libvulkan-dev ninja-build + + - name: Check out the pinned layer source + run: | + git init lsfg-vk-android + cd lsfg-vk-android + git remote add origin "${SOURCE_REPOSITORY}" + git fetch --depth=1 origin "${SOURCE_COMMIT}" + git checkout --detach FETCH_HEAD + git submodule update --init --recursive --depth=1 + test "$(git rev-parse HEAD)" = "${SOURCE_COMMIT}" + + - name: Build with static C++ runtimes + run: | + cmake -S lsfg-vk-android -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ + -DCMAKE_SHARED_LINKER_FLAGS="-static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL" + cmake --build build --parallel 4 + strip --strip-unneeded build/liblsfg-vk.so + + - name: Verify and document the artifact + run: | + mkdir -p artifact + install -m 0755 build/liblsfg-vk.so artifact/liblsfg-vk-arm64.so + readelf -h artifact/liblsfg-vk-arm64.so | grep -q 'Machine:.*AArch64' + readelf -d artifact/liblsfg-vk-arm64.so | tee artifact/ELF-DYNAMIC.txt + ! grep -Eq 'libstdc\+\+\.so|libgcc_s\.so' artifact/ELF-DYNAMIC.txt + sha256sum artifact/liblsfg-vk-arm64.so | tee artifact/SHA256SUMS + { + echo "Source repository: ${SOURCE_REPOSITORY}" + echo "Source commit: ${SOURCE_COMMIT}" + echo "Runner: ubuntu-24.04-arm" + echo "Compiler: $(clang++ --version | head -n1)" + echo "CMake: $(cmake --version | head -n1)" + echo "Static runtimes: libstdc++ and libgcc" + } > artifact/SOURCE.txt + + - name: Upload ARM64 layer + uses: actions/upload-artifact@v4 + with: + name: lsfg-vk-arm64-${{ env.SOURCE_COMMIT }} + path: artifact/ + if-no-files-found: error + retention-days: 90 diff --git a/package.json b/package.json index a71b9cc..347820b 100644 --- a/package.json +++ b/package.json @@ -66,8 +66,8 @@ }, { "name": "liblsfg-vk-arm64.so", - "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/arm-test/liblsfg-vk.so", - "sha256hash": "c745893b3798eb1acb422775a43107fc3f2efb2ae2711090b90b04941d8de309" + "url": "https://github.com/Janleyx/decky-lsfg-vk-for-Ayn-Odin-2/releases/download/arm64-layer-3e89e54/liblsfg-vk-arm64.so", + "sha256hash": "2e84f8d1a1dd0344474846f6252d6f948f72caaaeab3cd316c04254be05a9949" } ], "pnpm": { diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 4f01be1..29259f7 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -53,7 +53,7 @@ class InstallationService(BaseService): if self._is_arm_architecture(): self.log.info("Detected ARM architecture, using ARM binary") arm_so_path = plugin_dir / BIN_DIR / ARM_LIB_FILENAME - shutil.copy2(arm_so_path, self.lib_file) + 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() @@ -78,7 +78,35 @@ class InstallationService(BaseService): Returns: True if running on ARM (aarch64), False otherwise """ - return platform.machine().lower() == 'aarch64' + 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 Path('/usr/libexec/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 @@ -117,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}") @@ -147,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 -- cgit v1.2.3 From e205effa83dbbcb9c13f3f11a493a525e989d7b6 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:20:20 -0400 Subject: preserve Armada game launch wrapper --- README.md | 14 +++++++++++ py_modules/lsfg_vk/constants.py | 2 ++ py_modules/lsfg_vk/plugin.py | 8 +++++++ src/components/SmartClipboardButton.tsx | 9 ++++--- src/components/UsageInstructions.tsx | 42 +++++++++++++++++++++++++++++++-- 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e8bf1fa..7532108 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,20 @@ A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scali - Or use the "Launch Option Clipboard" button in the plugin to copy the command 6. **Launch your game** - frame generation will activate automatically using your plugin configuration +### Armada launch options + +Armada games already use `/usr/libexec/armada/armada-game-launch %command%` +to apply their FEX profile and controller/runtime fixes. Preserve that wrapper by +prepending LSFG to the existing option: + +```bash +~/lsfg /usr/libexec/armada/armada-game-launch %command% +``` + +The plugin detects Armada and copies this combined command automatically. LSFG +sets its environment first, then Armada applies the game's settings and launches +Proton normally. + ## Configuration Options The plugin provides several configuration options to optimize frame generation for your games: diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index 795c1bf..a2140b3 100644 --- a/py_modules/lsfg_vk/constants.py +++ b/py_modules/lsfg_vk/constants.py @@ -25,6 +25,8 @@ JSON_EXT = ".json" BIN_DIR = "bin" +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/plugin.py b/py_modules/lsfg_vk/plugin.py index cb59b4f..2cb08f4 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -18,6 +18,7 @@ from .dll_detection import DllDetectionService from .configuration import ConfigurationService from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService +from .constants import ARMADA_GAME_LAUNCH class Plugin: @@ -255,6 +256,13 @@ class Plugin: Returns: Dict containing the launch option string and instructions """ + if ARMADA_GAME_LAUNCH.is_file(): + return { + "launch_option": f"~/lsfg {ARMADA_GAME_LAUNCH} %command%", + "instructions": "Use this combined command in Steam Properties, preserving Armada's game-launch wrapper", + "explanation": "LSFG sets its environment first, then Armada applies the game's FEX profile and launches Proton" + } + return { "launch_option": "~/lsfg %command%", "instructions": "Add this to your game's launch options in Steam Properties", diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index c90515a..7217e83 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -20,12 +20,11 @@ export function SmartClipboardButton() { }, [showSuccess]); const getLaunchOptionText = async (): Promise => { - try { - const result = await getLaunchOption(); - return result.launch_option || "~/lsfg %command%"; - } catch (error) { - return "~/lsfg %command%"; + const result = await getLaunchOption(); + if (!result.launch_option) { + throw new Error("Launch option is unavailable"); } + return result.launch_option; }; const copyToClipboard = async () => { diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index 5f032b8..02d0eb3 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -1,6 +1,31 @@ +import { useEffect, useState } from "react"; import { PanelSectionRow } from "@decky/ui"; +import { getLaunchOption } from "../api/lsfgApi"; export function UsageInstructions() { + const [launchOption, setLaunchOption] = useState("~/lsfg %command%"); + const [launchExplanation, setLaunchExplanation] = useState( + "The LSFG wrapper configures frame generation before launching the game." + ); + + useEffect(() => { + let active = true; + + getLaunchOption() + .then((result) => { + if (!active) return; + if (result.launch_option) setLaunchOption(result.launch_option); + if (result.explanation) setLaunchExplanation(result.explanation); + }) + .catch(() => { + // Keep the standard launch option visible if the backend is unavailable. + }); + + return () => { + active = false; + }; + }, []); + return ( <> @@ -47,7 +72,20 @@ export function UsageInstructions() { textAlign: "center" }} > - ~/lsfg %command% + {launchOption} + + + + +
+ {launchExplanation}
@@ -60,7 +98,7 @@ export function UsageInstructions() { marginTop: "8px" }} > -The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running. + The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running.
-- cgit v1.2.3 From bbd959cee4d4b3ac784cc5543464775c10a0b981 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:21:48 -0400 Subject: document Armada-only launch behavior --- README.md | 5 +++++ py_modules/lsfg_vk/plugin.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 7532108..9197b2e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,11 @@ The plugin detects Armada and copies this combined command automatically. LSFG sets its environment first, then Armada applies the game's settings and launches Proton normally. +This is an Armada-specific compatibility path, not a change to the standard +launch flow. It activates only when Armada's +`/usr/libexec/armada/armada-game-launch` wrapper is present. SteamOS, Bazzite, +and other systems continue to receive the existing `~/lsfg %command%` option. + ## Configuration Options The plugin provides several configuration options to optimize frame generation for your games: diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 2cb08f4..dee2650 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -256,6 +256,8 @@ class Plugin: Returns: Dict containing the launch option string and instructions """ + # Armada-specific: other distributions do not ship this wrapper and + # continue through the unchanged generic launch option below. if ARMADA_GAME_LAUNCH.is_file(): return { "launch_option": f"~/lsfg {ARMADA_GAME_LAUNCH} %command%", -- cgit v1.2.3 From 84c79a3158d596af97dd89c1abffa2dad8ab899c Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Sat, 18 Jul 2026 10:27:34 -0400 Subject: gate launch wrapper on Armada host --- README.md | 8 +++++--- py_modules/lsfg_vk/constants.py | 1 + py_modules/lsfg_vk/installation.py | 4 ++-- py_modules/lsfg_vk/plugin.py | 8 ++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9197b2e..d522691 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,11 @@ sets its environment first, then Armada applies the game's settings and launches Proton normally. This is an Armada-specific compatibility path, not a change to the standard -launch flow. It activates only when Armada's -`/usr/libexec/armada/armada-game-launch` wrapper is present. SteamOS, Bazzite, -and other systems continue to receive the existing `~/lsfg %command%` option. +launch flow. Because Decky runs through FEX on Armada, the guest's OS identity +is not a reliable way to identify the native host. The compatibility path +therefore activates only when both Armada's native `device-env` marker and its +`armada-game-launch` wrapper are present. SteamOS, Bazzite, and other systems +continue to receive the existing `~/lsfg %command%` option. ## Configuration Options diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index a2140b3..023894f 100644 --- a/py_modules/lsfg_vk/constants.py +++ b/py_modules/lsfg_vk/constants.py @@ -25,6 +25,7 @@ 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") diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 29259f7..49ed711 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -15,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, ARM_LIB_FILENAME + SO_EXT, JSON_EXT, ARM_LIB_FILENAME, ARMADA_DEVICE_ENV ) from .config_schema import ConfigurationManager from .types import InstallationResponse, UninstallationResponse, InstallationCheckResponse @@ -84,7 +84,7 @@ class InstallationService(BaseService): # 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 Path('/usr/libexec/armada/device-env').is_file(): + if ARMADA_DEVICE_ENV.is_file(): self.log.info("Detected native AArch64 Armada host through device-env") return True diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index dee2650..74c1677 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -18,7 +18,7 @@ from .dll_detection import DllDetectionService from .configuration import ConfigurationService from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService -from .constants import ARMADA_GAME_LAUNCH +from .constants import ARMADA_DEVICE_ENV, ARMADA_GAME_LAUNCH class Plugin: @@ -256,9 +256,9 @@ class Plugin: Returns: Dict containing the launch option string and instructions """ - # Armada-specific: other distributions do not ship this wrapper and - # continue through the unchanged generic launch option below. - if ARMADA_GAME_LAUNCH.is_file(): + # Decky runs through FEX on Armada, so the guest OS identity is not a + # reliable host check. Require both Armada's native marker and wrapper. + if ARMADA_DEVICE_ENV.is_file() and ARMADA_GAME_LAUNCH.is_file(): return { "launch_option": f"~/lsfg {ARMADA_GAME_LAUNCH} %command%", "instructions": "Use this combined command in Steam Properties, preserving Armada's game-launch wrapper", -- cgit v1.2.3 From ff3213291a56ea8de73335adcceed3962a40114d Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:28:36 -0400 Subject: remove Armada README section --- README.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/README.md b/README.md index d522691..e8bf1fa 100644 --- a/README.md +++ b/README.md @@ -39,27 +39,6 @@ A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scali - Or use the "Launch Option Clipboard" button in the plugin to copy the command 6. **Launch your game** - frame generation will activate automatically using your plugin configuration -### Armada launch options - -Armada games already use `/usr/libexec/armada/armada-game-launch %command%` -to apply their FEX profile and controller/runtime fixes. Preserve that wrapper by -prepending LSFG to the existing option: - -```bash -~/lsfg /usr/libexec/armada/armada-game-launch %command% -``` - -The plugin detects Armada and copies this combined command automatically. LSFG -sets its environment first, then Armada applies the game's settings and launches -Proton normally. - -This is an Armada-specific compatibility path, not a change to the standard -launch flow. Because Decky runs through FEX on Armada, the guest's OS identity -is not a reliable way to identify the native host. The compatibility path -therefore activates only when both Armada's native `device-env` marker and its -`armada-game-launch` wrapper are present. SteamOS, Bazzite, and other systems -continue to receive the existing `~/lsfg %command%` option. - ## Configuration Options The plugin provides several configuration options to optimize frame generation for your games: -- cgit v1.2.3 From 1260aa400e6d19e8d1cbc3d887c8ddcff3058be9 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:35:23 -0400 Subject: wrap Armada launch inside lsfg script --- py_modules/lsfg_vk/configuration.py | 25 +++++++++++++------- py_modules/lsfg_vk/plugin.py | 10 -------- src/components/SmartClipboardButton.tsx | 9 +++---- src/components/UsageInstructions.tsx | 42 ++------------------------------- 4 files changed, 24 insertions(+), 62 deletions(-) diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index e479f43..f7a171a 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -8,6 +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 .constants import ARMADA_DEVICE_ENV, ARMADA_GAME_LAUNCH from .types import ConfigurationResponse, ProfilesResponse, ProfileResponse @@ -123,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" @@ -154,12 +153,22 @@ 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'if [ -f "{device_env}" ] && [ -x "{game_launch}" ]; then', + f' exec "{game_launch}" "$@"', + "fi", + 'exec "$@"', + ] def _get_profile_data(self) -> ProfileData: """Get current profile data from config file""" diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 74c1677..cb59b4f 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -18,7 +18,6 @@ from .dll_detection import DllDetectionService from .configuration import ConfigurationService from .config_schema import ConfigurationManager from .flatpak_service import FlatpakService -from .constants import ARMADA_DEVICE_ENV, ARMADA_GAME_LAUNCH class Plugin: @@ -256,15 +255,6 @@ class Plugin: Returns: Dict containing the launch option string and instructions """ - # Decky runs through FEX on Armada, so the guest OS identity is not a - # reliable host check. Require both Armada's native marker and wrapper. - if ARMADA_DEVICE_ENV.is_file() and ARMADA_GAME_LAUNCH.is_file(): - return { - "launch_option": f"~/lsfg {ARMADA_GAME_LAUNCH} %command%", - "instructions": "Use this combined command in Steam Properties, preserving Armada's game-launch wrapper", - "explanation": "LSFG sets its environment first, then Armada applies the game's FEX profile and launches Proton" - } - return { "launch_option": "~/lsfg %command%", "instructions": "Add this to your game's launch options in Steam Properties", diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx index 7217e83..c90515a 100644 --- a/src/components/SmartClipboardButton.tsx +++ b/src/components/SmartClipboardButton.tsx @@ -20,11 +20,12 @@ export function SmartClipboardButton() { }, [showSuccess]); const getLaunchOptionText = async (): Promise => { - const result = await getLaunchOption(); - if (!result.launch_option) { - throw new Error("Launch option is unavailable"); + try { + const result = await getLaunchOption(); + return result.launch_option || "~/lsfg %command%"; + } catch (error) { + return "~/lsfg %command%"; } - return result.launch_option; }; const copyToClipboard = async () => { diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index 02d0eb3..5f032b8 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -1,31 +1,6 @@ -import { useEffect, useState } from "react"; import { PanelSectionRow } from "@decky/ui"; -import { getLaunchOption } from "../api/lsfgApi"; export function UsageInstructions() { - const [launchOption, setLaunchOption] = useState("~/lsfg %command%"); - const [launchExplanation, setLaunchExplanation] = useState( - "The LSFG wrapper configures frame generation before launching the game." - ); - - useEffect(() => { - let active = true; - - getLaunchOption() - .then((result) => { - if (!active) return; - if (result.launch_option) setLaunchOption(result.launch_option); - if (result.explanation) setLaunchExplanation(result.explanation); - }) - .catch(() => { - // Keep the standard launch option visible if the backend is unavailable. - }); - - return () => { - active = false; - }; - }, []); - return ( <> @@ -72,20 +47,7 @@ export function UsageInstructions() { textAlign: "center" }} > - {launchOption} - - - - -
- {launchExplanation} + ~/lsfg %command%
@@ -98,7 +60,7 @@ export function UsageInstructions() { marginTop: "8px" }} > - The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running. +The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running.
-- cgit v1.2.3 From 2e55d0cb3205cf0770ec93610b5d3df18deacd34 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:54:22 -0400 Subject: avoid double Armada launch wrapping --- py_modules/lsfg_vk/configuration.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index f7a171a..fce3738 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -164,8 +164,14 @@ class ConfigurationService(BaseService): device_env = ARMADA_DEVICE_ENV.as_posix() game_launch = ARMADA_GAME_LAUNCH.as_posix() return [ - f'if [ -f "{device_env}" ] && [ -x "{game_launch}" ]; then', - f' exec "{game_launch}" "$@"', + 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 "$@"', ] -- cgit v1.2.3 From 4fbd659fc3ee8a6f25355ff2a65961f4c7898cd8 Mon Sep 17 00:00:00 2001 From: Janleyx <242423638+Janleyx@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:13:37 -0400 Subject: remove layer workflow from plugin PR --- .github/workflows/build-arm64-layer.yml | 72 --------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 .github/workflows/build-arm64-layer.yml diff --git a/.github/workflows/build-arm64-layer.yml b/.github/workflows/build-arm64-layer.yml deleted file mode 100644 index 973a548..0000000 --- a/.github/workflows/build-arm64-layer.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Build ARM64 lsfg-vk layer - -on: - workflow_dispatch: - pull_request: - paths: - - .github/workflows/build-arm64-layer.yml - -permissions: - contents: read - -env: - SOURCE_REPOSITORY: https://github.com/FrankBarretta/lsfg-vk-android.git - SOURCE_COMMIT: 3e89e5439a98f55d5acb003d20039426ab24e69c - -jobs: - build: - name: Native AArch64 release build - runs-on: ubuntu-24.04-arm - - steps: - - name: Install build dependencies - run: | - sudo apt-get update - sudo apt-get install --yes --no-install-recommends \ - clang cmake g++ git libvulkan-dev ninja-build - - - name: Check out the pinned layer source - run: | - git init lsfg-vk-android - cd lsfg-vk-android - git remote add origin "${SOURCE_REPOSITORY}" - git fetch --depth=1 origin "${SOURCE_COMMIT}" - git checkout --detach FETCH_HEAD - git submodule update --init --recursive --depth=1 - test "$(git rev-parse HEAD)" = "${SOURCE_COMMIT}" - - - name: Build with static C++ runtimes - run: | - cmake -S lsfg-vk-android -B build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF \ - -DCMAKE_SHARED_LINKER_FLAGS="-static-libstdc++ -static-libgcc -Wl,--exclude-libs,ALL" - cmake --build build --parallel 4 - strip --strip-unneeded build/liblsfg-vk.so - - - name: Verify and document the artifact - run: | - mkdir -p artifact - install -m 0755 build/liblsfg-vk.so artifact/liblsfg-vk-arm64.so - readelf -h artifact/liblsfg-vk-arm64.so | grep -q 'Machine:.*AArch64' - readelf -d artifact/liblsfg-vk-arm64.so | tee artifact/ELF-DYNAMIC.txt - ! grep -Eq 'libstdc\+\+\.so|libgcc_s\.so' artifact/ELF-DYNAMIC.txt - sha256sum artifact/liblsfg-vk-arm64.so | tee artifact/SHA256SUMS - { - echo "Source repository: ${SOURCE_REPOSITORY}" - echo "Source commit: ${SOURCE_COMMIT}" - echo "Runner: ubuntu-24.04-arm" - echo "Compiler: $(clang++ --version | head -n1)" - echo "CMake: $(cmake --version | head -n1)" - echo "Static runtimes: libstdc++ and libgcc" - } > artifact/SOURCE.txt - - - name: Upload ARM64 layer - uses: actions/upload-artifact@v4 - with: - name: lsfg-vk-arm64-${{ env.SOURCE_COMMIT }} - path: artifact/ - if-no-files-found: error - retention-days: 90 -- cgit v1.2.3 From b1f2a36ed17928fb98a22d70ce6f39fd967a143c Mon Sep 17 00:00:00 2001 From: xXJsonDeruloXx Date: Sun, 2 Aug 2026 13:36:22 -0400 Subject: chore: release v0.12.6 --- package.json | 11 ++++++++++- pnpm-lock.yaml | 7 +++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 347820b..2671260 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-lsfg-vk", - "version": "0.12.5", + "version": "0.12.6", "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": [ { @@ -71,6 +75,11 @@ } ], "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 -- cgit v1.2.3 From 27c821cc860606fe721f2b83af5ff88317d2c37f Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 3 Aug 2026 14:13:35 -0400 Subject: feat: expose FP16 acceleration setting --- package.json | 2 +- py_modules/lsfg_vk/config_schema.py | 14 +++++++------- src/components/ConfigurationSection.tsx | 11 ++++++++++- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 2671260..0548f7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-lsfg-vk", - "version": "0.12.6", + "version": "0.12.7", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 3a82bbd..f76b5f2 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -178,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) @@ -188,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]") @@ -202,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 @@ -339,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: diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 0734297..1683ca2 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"; @@ -125,6 +125,15 @@ export function ConfigurationSection({ /> + + onConfigChange(NO_FP16, !value)} + /> + + 0 ? ` (${config.dxvk_frame_rate} FPS)` : " (Off)"}`} -- cgit v1.2.3 From 24840e334aff52ef77a92dbedaf3c574496d6ea9 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 3 Aug 2026 14:18:50 -0400 Subject: fix: simplify FP16 toggle description --- src/components/ConfigurationSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 1683ca2..344481e 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -128,7 +128,7 @@ export function ConfigurationSection({ onConfigChange(NO_FP16, !value)} /> -- cgit v1.2.3