From 2563298ae23199e2150a837442121cd89225f2d4 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 16 Jul 2025 21:36:40 -0400 Subject: config version of lsfg-vk remote bin --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5919b3f..18ce29d 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "remote_binary": [ { "name": "lsfg-vk_archlinux.zip", - "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/upstream-16274840875/lsfg-vk_archlinux.zip", - "sha256hash": "7409f91a717d17d77c90eec161652dcc26a6fab333b253a9e095e451ad81bbab" + "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/pre-conf-jul16/lsfg-vk_archlinux.zip", + "sha256hash": "sha256:1f40a4e2505c83bdbce45ba77c60d221f9203119dc2e97b263bce6b775df9b98" } ], "pnpm": { -- cgit v1.2.3 From 1263b33e98fe8cb6058fc41b122cec74aed13d25 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Wed, 16 Jul 2025 23:34:43 -0400 Subject: fix sha256 hash for bin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 18ce29d..29903f2 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ { "name": "lsfg-vk_archlinux.zip", "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/pre-conf-jul16/lsfg-vk_archlinux.zip", - "sha256hash": "sha256:1f40a4e2505c83bdbce45ba77c60d221f9203119dc2e97b263bce6b775df9b98" + "sha256hash": "1f40a4e2505c83bdbce45ba77c60d221f9203119dc2e97b263bce6b775df9b98" } ], "pnpm": { -- cgit v1.2.3 From 8a71f528ef34447c43e67db67d071e38ff084ff1 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 14:01:30 -0400 Subject: udpate remote bin for conf test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 29903f2..df7ba84 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ { "name": "lsfg-vk_archlinux.zip", "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/pre-conf-jul16/lsfg-vk_archlinux.zip", - "sha256hash": "1f40a4e2505c83bdbce45ba77c60d221f9203119dc2e97b263bce6b775df9b98" + "sha256hash": "c9eb86e0ef458395fca18161b1620855b21eee54aa1df536a5fbbd2d524b1b88" } ], "pnpm": { -- cgit v1.2.3 From 6b701637ad308513b678c80baceec6c79e339ce9 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 14:22:56 -0400 Subject: initial conf FE and BE hooks --- justfile | 2 +- py_modules/lsfg_vk/base_service.py | 7 +- py_modules/lsfg_vk/config_schema.py | 230 ++++++++++++++++---------------- py_modules/lsfg_vk/configuration.py | 184 +++++++++++++------------ py_modules/lsfg_vk/constants.py | 2 + py_modules/lsfg_vk/installation.py | 53 +++++--- py_modules/lsfg_vk/plugin.py | 52 ++++++-- src/api/lsfgApi.ts | 4 +- src/components/ConfigurationSection.tsx | 67 ++++------ src/components/Content.tsx | 2 +- src/components/UsageInstructions.tsx | 130 ++++++++---------- src/config/configSchema.ts | 83 +++++------- src/hooks/useLsfgHooks.ts | 2 +- 13 files changed, 408 insertions(+), 410 deletions(-) diff --git a/justfile b/justfile index 7641cea..d92039c 100644 --- a/justfile +++ b/justfile @@ -2,7 +2,7 @@ default: echo "Available recipes: build, test, clean" build: - rm -rf node_modules && .vscode/build.sh + sudo rm -rf node_modules && .vscode/build.sh test: scp "/Users/kurt/Developer/decky-lossless-scaling-vk/out/Lossless Scaling.zip" deck@192.168.0.6:~/Desktop diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py index b547759..92ac152 100644 --- a/py_modules/lsfg_vk/base_service.py +++ b/py_modules/lsfg_vk/base_service.py @@ -8,7 +8,7 @@ import tempfile from pathlib import Path from typing import Any, Optional -from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME +from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME, CONFIG_DIR, CONFIG_FILENAME class BaseService: @@ -31,12 +31,15 @@ class BaseService: self.local_lib_dir = self.user_home / LOCAL_LIB self.local_share_dir = self.user_home / VULKAN_LAYER_DIR self.lsfg_script_path = self.user_home / SCRIPT_NAME + self.config_dir = self.user_home / CONFIG_DIR + self.config_file_path = self.config_dir / CONFIG_FILENAME def _ensure_directories(self) -> None: """Create necessary directories if they don't exist""" self.local_lib_dir.mkdir(parents=True, exist_ok=True) self.local_share_dir.mkdir(parents=True, exist_ok=True) - self.log.info(f"Ensured directories exist: {self.local_lib_dir}, {self.local_share_dir}") + self.config_dir.mkdir(parents=True, exist_ok=True) + self.log.info(f"Ensured directories exist: {self.local_lib_dir}, {self.local_share_dir}, {self.config_dir}") def _remove_if_exists(self, path: Path) -> bool: """Remove a file if it exists diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 0f1bdae..f054abf 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -1,16 +1,18 @@ """ Centralized configuration schema for lsfg-vk. -This module defines the complete configuration structure, including: +This module defines the complete configuration structure for TOML-based config files, including: - Field definitions with types, defaults, and metadata -- Script generation logic +- TOML generation logic - Validation rules - Type definitions """ -from typing import TypedDict, Dict, Any, Union, Callable, cast -from dataclasses import dataclass, field +import re +from typing import TypedDict, Dict, Any, Union, cast +from dataclasses import dataclass from enum import Enum +from pathlib import Path class ConfigFieldType(Enum): @@ -18,6 +20,7 @@ class ConfigFieldType(Enum): BOOLEAN = "boolean" INTEGER = "integer" FLOAT = "float" + STRING = "string" @dataclass @@ -25,110 +28,72 @@ class ConfigField: """Configuration field definition""" name: str field_type: ConfigFieldType - default: Union[bool, int, float] + default: Union[bool, int, float, str] description: str - script_template: str # Template for script generation - script_comment: str = "" # Comment to add when disabled - def get_script_line(self, value: Union[bool, int, float]) -> str: - """Generate script line for this field""" - if self.field_type == ConfigFieldType.BOOLEAN: - if value: - return self.script_template.format(value=1) - else: - return f"# {self.script_template.format(value=1)}" - else: - return self.script_template.format(value=value) + def get_toml_value(self, value: Union[bool, int, float, str]) -> Union[bool, int, float, str]: + """Get the value for TOML output""" + return value # Configuration schema definition CONFIG_SCHEMA: Dict[str, ConfigField] = { - "enable_lsfg": ConfigField( - name="enable_lsfg", + "enable": ConfigField( + name="enable", field_type=ConfigFieldType.BOOLEAN, default=True, - description="Enables the frame generation layer", - script_template="export ENABLE_LSFG={value}", - script_comment="# export ENABLE_LSFG=1" + description="enable/disable lsfg on every game" + ), + + "dll": ConfigField( + name="dll", + field_type=ConfigFieldType.STRING, + default="/games/Lossless Scaling/Lossless.dll", + description="specify where Lossless.dll is stored" ), "multiplier": ConfigField( name="multiplier", field_type=ConfigFieldType.INTEGER, default=2, - description="Traditional FPS multiplier value", - script_template="export LSFG_MULTIPLIER={value}" + description="change the fps multiplier" ), "flow_scale": ConfigField( name="flow_scale", field_type=ConfigFieldType.FLOAT, default=0.8, - description="Lowers the internal motion estimation resolution", - script_template="export LSFG_FLOW_SCALE={value}" - ), - - "hdr": ConfigField( - name="hdr", - field_type=ConfigFieldType.BOOLEAN, - default=False, - description="Enable HDR mode (only if Game supports HDR)", - script_template="export LSFG_HDR={value}", - script_comment="# export LSFG_HDR=1" + description="change the flow scale (lower = faster)" ), - "perf_mode": ConfigField( - name="perf_mode", + "performance_mode": ConfigField( + name="performance_mode", field_type=ConfigFieldType.BOOLEAN, default=True, - description="Use lighter model for FG", - script_template="export LSFG_PERF_MODE={value}", - script_comment="# export LSFG_PERF_MODE=1" + description="toggle performance mode (2x-8x performance increase)" ), - "immediate_mode": ConfigField( - name="immediate_mode", + "hdr_mode": ConfigField( + name="hdr_mode", field_type=ConfigFieldType.BOOLEAN, default=False, - description="Reduce input lag (Experimental, will cause issues in many games)", - script_template="export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync", - script_comment="# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" - ), - - "disable_vkbasalt": ConfigField( - name="disable_vkbasalt", - field_type=ConfigFieldType.BOOLEAN, - default=True, - description="Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this", - script_template="export DISABLE_VKBASALT={value}", - script_comment="# export DISABLE_VKBASALT=1" - ), - - "frame_cap": ConfigField( - name="frame_cap", - field_type=ConfigFieldType.INTEGER, - default=0, - description="Limit base game FPS (0 = disabled)", - script_template="export DXVK_FRAME_RATE={value}", - script_comment="# export DXVK_FRAME_RATE=60" + description="enable hdr mode (doesn't support scrgb)" ) } class ConfigurationData(TypedDict): """Type-safe configuration data structure""" - enable_lsfg: bool + enable: bool + dll: str multiplier: int flow_scale: float - hdr: bool - perf_mode: bool - immediate_mode: bool - disable_vkbasalt: bool - frame_cap: int + performance_mode: bool + hdr_mode: bool class ConfigurationManager: - """Centralized configuration management""" + """Centralized configuration management for TOML-based config""" @staticmethod def get_defaults() -> ConfigurationData: @@ -166,63 +131,104 @@ class ConfigurationManager: validated[field_name] = int(value) elif field_def.field_type == ConfigFieldType.FLOAT: validated[field_name] = float(value) + elif field_def.field_type == ConfigFieldType.STRING: + validated[field_name] = str(value) else: validated[field_name] = value return cast(ConfigurationData, validated) @staticmethod - def generate_script_content(config: ConfigurationData) -> str: - """Generate lsfg script content from configuration""" - script_lines = ["#!/bin/bash", ""] + def generate_toml_content(config: ConfigurationData) -> str: + """Generate TOML configuration file content""" + lines = ["[global]"] - # Generate script lines for each field - for field_name in CONFIG_SCHEMA.keys(): - field_def = CONFIG_SCHEMA[field_name] + for field_name, field_def in CONFIG_SCHEMA.items(): value = config[field_name] + lines.append(f"# {field_def.description}") - if field_def.field_type == ConfigFieldType.BOOLEAN: - if value: - script_lines.append(field_def.script_template.format(value=1)) - else: - script_lines.append(field_def.script_comment) + # Format value based on type + if isinstance(value, bool): + lines.append(f"{field_name} = {str(value).lower()}") + elif isinstance(value, str): + lines.append(f'{field_name} = "{value}"') else: - # For frame_cap, special handling for 0 value - if field_name == "frame_cap" and value == 0: - script_lines.append(field_def.script_comment) - else: - script_lines.append(field_def.script_template.format(value=value)) - - # Add script footer - script_lines.extend([ - "", - "# Execute the passed command with the environment variables set", - 'exec "$@"' - ]) + lines.append(f"{field_name} = {value}") + + lines.append("") # Empty line for readability - return "\n".join(script_lines) + return "\n".join(lines) @staticmethod - def get_update_signature() -> list[tuple[str, type]]: - """Get the function signature for update_config method""" - signature = [] - for field_name, field_def in CONFIG_SCHEMA.items(): - if field_def.field_type == ConfigFieldType.BOOLEAN: - signature.append((field_name, bool)) - elif field_def.field_type == ConfigFieldType.INTEGER: - signature.append((field_name, int)) - elif field_def.field_type == ConfigFieldType.FLOAT: - signature.append((field_name, float)) - return signature + def parse_toml_content(content: str) -> ConfigurationData: + """Parse TOML content into configuration data using simple regex parsing""" + config = ConfigurationManager.get_defaults() + + try: + # Look for [global] section + lines = content.split('\n') + in_global_section = False + + for line in lines: + line = line.strip() + + # Skip comments and empty lines + if not line or line.startswith('#'): + continue + + # Check for section headers + if line.startswith('[') and line.endswith(']'): + section = line[1:-1].strip() + in_global_section = (section == 'global') + continue + + # Only parse lines in the global section + if not in_global_section: + continue + + # Parse key = value lines + if '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + + # Remove quotes from string values + if value.startswith('"') and value.endswith('"'): + value = value[1:-1] + elif value.startswith("'") and value.endswith("'"): + value = value[1:-1] + + # Convert to appropriate type based on field definition + if key in CONFIG_SCHEMA: + field_def = CONFIG_SCHEMA[key] + try: + if field_def.field_type == ConfigFieldType.BOOLEAN: + config[key] = value.lower() in ('true', '1', 'yes', 'on') + elif field_def.field_type == ConfigFieldType.INTEGER: + config[key] = int(value) + elif field_def.field_type == ConfigFieldType.FLOAT: + config[key] = float(value) + elif field_def.field_type == ConfigFieldType.STRING: + config[key] = value + except (ValueError, TypeError): + # If conversion fails, keep default value + pass + + return config + + except Exception: + # If parsing fails completely, return defaults + return ConfigurationManager.get_defaults() @staticmethod - def create_config_from_args(*args) -> ConfigurationData: - """Create configuration from ordered arguments""" - field_names = ConfigurationManager.get_field_names() - if len(args) != len(field_names): - raise ValueError(f"Expected {len(field_names)} arguments, got {len(args)}") - + def create_config_from_args(enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> ConfigurationData: + """Create configuration from individual arguments""" return cast(ConfigurationData, { - field_name: args[i] - for i, field_name in enumerate(field_names) + "enable": enable, + "dll": dll, + "multiplier": multiplier, + "flow_scale": flow_scale, + "performance_mode": performance_mode, + "hdr_mode": hdr_mode }) diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index 8be7b47..4d76a30 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -1,8 +1,7 @@ """ -Configuration service for lsfg script management. +Configuration service for TOML-based lsfg configuration management. """ -import re from pathlib import Path from typing import Dict, Any @@ -12,25 +11,27 @@ from .types import ConfigurationResponse class ConfigurationService(BaseService): - """Service for managing lsfg script configuration""" + """Service for managing TOML-based lsfg configuration""" def get_config(self) -> ConfigurationResponse: - """Read current lsfg script configuration + """Read current TOML configuration Returns: ConfigurationResponse with current configuration or error """ try: - if not self.lsfg_script_path.exists(): + if not self.config_file_path.exists(): + # Return default configuration if file doesn't exist + config = ConfigurationManager.get_defaults() return { - "success": False, - "config": None, - "message": None, - "error": "lsfg script not found" + "success": True, + "config": config, + "message": "Using default configuration (config file not found)", + "error": None } - content = self.lsfg_script_path.read_text() - config = self._parse_script_content(content) + content = self.config_file_path.read_text(encoding='utf-8') + config = ConfigurationManager.parse_toml_content(content) return { "success": True, @@ -48,83 +49,29 @@ class ConfigurationService(BaseService): "message": None, "error": str(e) } + except Exception as e: + error_msg = f"Error parsing config file: {str(e)}" + self.log.error(error_msg) + # Return defaults if parsing fails + config = ConfigurationManager.get_defaults() + return { + "success": True, + "config": config, + "message": f"Using default configuration due to parse error: {str(e)}", + "error": None + } - def _parse_script_content(self, content: str) -> ConfigurationData: - """Parse script content to extract configuration values - - Args: - content: Script file content - - Returns: - ConfigurationData with parsed values - """ - # Start with defaults - config = ConfigurationManager.get_defaults() - - lines = content.split('\n') - for line in lines: - line = line.strip() - - # Parse ENABLE_LSFG - if match := re.match(r'^(#\s*)?export\s+ENABLE_LSFG=(\d+)', line): - config["enable_lsfg"] = not bool(match.group(1)) and match.group(2) == '1' - - # Parse LSFG_MULTIPLIER - elif match := re.match(r'^export\s+LSFG_MULTIPLIER=(\d+)', line): - try: - config["multiplier"] = int(match.group(1)) - except ValueError: - pass - - # Parse LSFG_FLOW_SCALE - elif match := re.match(r'^export\s+LSFG_FLOW_SCALE=([0-9]*\.?[0-9]+)', line): - try: - config["flow_scale"] = float(match.group(1)) - except ValueError: - pass - - # Parse LSFG_HDR - elif match := re.match(r'^(#\s*)?export\s+LSFG_HDR=(\d+)', line): - config["hdr"] = not bool(match.group(1)) and match.group(2) == '1' - - # Parse LSFG_PERF_MODE - elif match := re.match(r'^(#\s*)?export\s+LSFG_PERF_MODE=(\d+)', line): - config["perf_mode"] = not bool(match.group(1)) and match.group(2) == '1' - - # Parse MESA_VK_WSI_PRESENT_MODE - elif match := re.match(r'^(#\s*)?export\s+MESA_VK_WSI_PRESENT_MODE=([^\s#]+)', line): - config["immediate_mode"] = not bool(match.group(1)) and match.group(2) == 'immediate' - - # Parse DISABLE_VKBASALT - elif match := re.match(r'^(#\s*)?export\s+DISABLE_VKBASALT=(\d+)', line): - config["disable_vkbasalt"] = not bool(match.group(1)) and match.group(2) == '1' - - # Parse DXVK_FRAME_RATE - elif match := re.match(r'^(#\s*)?export\s+DXVK_FRAME_RATE=(\d+)', line): - if not bool(match.group(1)): # Not commented out - try: - config["frame_cap"] = int(match.group(2)) - except ValueError: - pass - else: - # If it's commented out, frame cap is disabled (0) - config["frame_cap"] = 0 - - return config - - def update_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float, - hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> ConfigurationResponse: - """Update lsfg script configuration + def update_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> ConfigurationResponse: + """Update TOML configuration Args: - enable_lsfg: Whether to enable LSFG + enable: Whether to enable LSFG + dll: Path to Lossless.dll multiplier: LSFG multiplier value flow_scale: LSFG flow scale value - hdr: Whether to enable HDR - perf_mode: Whether to enable performance mode - immediate_mode: Whether to enable immediate present mode (disable vsync) - disable_vkbasalt: Whether to disable vkbasalt layer - frame_cap: Frame rate cap value (0-60, 0 = disabled) + performance_mode: Whether to enable performance mode + hdr_mode: Whether to enable HDR mode Returns: ConfigurationResponse with success status @@ -132,23 +79,25 @@ class ConfigurationService(BaseService): try: # Create configuration from individual arguments config = ConfigurationManager.create_config_from_args( - enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode, disable_vkbasalt, frame_cap + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode ) - # Generate script content using centralized manager - script_content = ConfigurationManager.generate_script_content(config) + # Generate TOML content using centralized manager + toml_content = ConfigurationManager.generate_toml_content(config) - # Write the updated script atomically - self._atomic_write(self.lsfg_script_path, script_content, 0o755) + # Ensure config directory exists + self.config_dir.mkdir(parents=True, exist_ok=True) - self.log.info(f"Updated lsfg script configuration: enable_lsfg={enable_lsfg}, " - f"multiplier={multiplier}, flow_scale={flow_scale}, hdr={hdr}, " - f"perf_mode={perf_mode}, immediate_mode={immediate_mode}, " - f"disable_vkbasalt={disable_vkbasalt}, frame_cap={frame_cap}") + # Write the updated config atomically + self._atomic_write(self.config_file_path, toml_content, 0o644) + + self.log.info(f"Updated lsfg TOML configuration: enable={enable}, " + f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, " + f"performance_mode={performance_mode}, hdr_mode={hdr_mode}") return { "success": True, - "config": None, + "config": config, "message": "lsfg configuration updated successfully", "error": None } @@ -171,3 +120,52 @@ class ConfigurationService(BaseService): "message": None, "error": str(e) } + + def update_dll_path(self, dll_path: str) -> ConfigurationResponse: + """Update just the DLL path in the configuration + + Args: + dll_path: Path to the Lossless.dll file + + Returns: + ConfigurationResponse with success status + """ + try: + # Get current config + current_response = self.get_config() + if not current_response["success"] or current_response["config"] is None: + # If we can't read current config, use defaults + config = ConfigurationManager.get_defaults() + else: + config = current_response["config"] + + # Update just the DLL path + config["dll"] = dll_path + + # Generate TOML content and write it + toml_content = ConfigurationManager.generate_toml_content(config) + + # Ensure config directory exists + self.config_dir.mkdir(parents=True, exist_ok=True) + + # Write the updated config atomically + self._atomic_write(self.config_file_path, toml_content, 0o644) + + self.log.info(f"Updated DLL path in lsfg configuration: '{dll_path}'") + + return { + "success": True, + "config": config, + "message": f"DLL path updated to: {dll_path}", + "error": None + } + + except Exception as e: + error_msg = f"Error updating DLL path: {str(e)}" + self.log.error(error_msg) + return { + "success": False, + "config": None, + "message": None, + "error": str(e) + } diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index 5f1e5a2..252c7a5 100644 --- a/py_modules/lsfg_vk/constants.py +++ b/py_modules/lsfg_vk/constants.py @@ -8,9 +8,11 @@ from pathlib import Path LOCAL_LIB = ".local/lib" LOCAL_SHARE_BASE = ".local/share" VULKAN_LAYER_DIR = ".local/share/vulkan/implicit_layer.d" +CONFIG_DIR = ".config/lsfg-vk" # File names SCRIPT_NAME = "lsfg" +CONFIG_FILENAME = "conf.toml" LIB_FILENAME = "liblsfg-vk.so" JSON_FILENAME = "VkLayer_LS_frame_generation.json" ZIP_FILENAME = "lsfg-vk_archlinux.zip" diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 767a97a..a1c2d64 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -51,8 +51,8 @@ class InstallationService(BaseService): # Extract and install files self._extract_and_install_files(zip_path) - # Create the lsfg script - self._create_lsfg_script() + # Create the config file + self._create_config_file() self.log.info("lsfg-vk installed successfully") return {"success": True, "message": "lsfg-vk installed successfully", "error": None} @@ -102,17 +102,17 @@ class InstallationService(BaseService): shutil.copy2(src_file, dst_file) self.log.info(f"Copied {file} to {dst_file}") - def _create_lsfg_script(self) -> None: - """Create the lsfg script in home directory with default configuration""" + def _create_config_file(self) -> None: + """Create the TOML config file in ~/.config/lsfg-vk with default configuration""" # Get default configuration defaults = ConfigurationManager.get_defaults() - # Generate script content using centralized manager - script_content = ConfigurationManager.generate_script_content(defaults) + # Generate TOML content using centralized manager + toml_content = ConfigurationManager.generate_toml_content(defaults) # Use atomic write to prevent corruption - self._atomic_write(self.lsfg_script_path, script_content, 0o755) - self.log.info(f"Created executable lsfg script at {self.lsfg_script_path}") + self._atomic_write(self.config_file_path, toml_content, 0o644) + self.log.info(f"Created config file at {self.config_file_path}") def check_installation(self) -> InstallationCheckResponse: """Check if lsfg-vk is already installed @@ -123,18 +123,18 @@ class InstallationService(BaseService): try: lib_exists = self.lib_file.exists() json_exists = self.json_file.exists() - script_exists = self.lsfg_script_path.exists() + config_exists = self.config_file_path.exists() - self.log.info(f"Installation check: lib={lib_exists}, json={json_exists}, script={script_exists}") + self.log.info(f"Installation check: lib={lib_exists}, json={json_exists}, config={config_exists}") return { "installed": lib_exists and json_exists, "lib_exists": lib_exists, "json_exists": json_exists, - "script_exists": script_exists, + "script_exists": config_exists, # Keep script_exists for backward compatibility "lib_path": str(self.lib_file), "json_path": str(self.json_file), - "script_path": str(self.lsfg_script_path), + "script_path": str(self.config_file_path), # Keep script_path for backward compatibility "error": None } @@ -148,7 +148,7 @@ class InstallationService(BaseService): "script_exists": False, "lib_path": str(self.lib_file), "json_path": str(self.json_file), - "script_path": str(self.lsfg_script_path), + "script_path": str(self.config_file_path), "error": str(e) } @@ -160,12 +160,24 @@ class InstallationService(BaseService): """ try: removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path] for file_path in files_to_remove: if self._remove_if_exists(file_path): removed_files.append(str(file_path)) + # Also try to remove the old script file if it exists + if self._remove_if_exists(self.lsfg_script_path): + removed_files.append(str(self.lsfg_script_path)) + + # Remove config directory if it's empty + try: + if self.config_dir.exists() and not any(self.config_dir.iterdir()): + self.config_dir.rmdir() + removed_files.append(str(self.config_dir)) + except OSError: + pass # Directory not empty or other error, ignore + if not removed_files: return { "success": True, @@ -198,10 +210,11 @@ class InstallationService(BaseService): self.log.info("Checking for lsfg-vk files to clean up:") self.log.info(f" Library file: {self.lib_file}") self.log.info(f" JSON file: {self.json_file}") - self.log.info(f" lsfg script: {self.lsfg_script_path}") + self.log.info(f" Config file: {self.config_file_path}") + self.log.info(f" Old script file: {self.lsfg_script_path}") removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.lsfg_script_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_script_path] for file_path in files_to_remove: try: @@ -210,6 +223,14 @@ class InstallationService(BaseService): except OSError as e: self.log.error(f"Failed to remove {file_path}: {e}") + # Try to remove config directory if empty + try: + if self.config_dir.exists() and not any(self.config_dir.iterdir()): + self.config_dir.rmdir() + removed_files.append(str(self.config_dir)) + except OSError: + pass # Directory not empty or other error, ignore + if removed_files: self.log.info(f"Cleaned up {len(removed_files)} lsfg-vk files during plugin uninstall: {removed_files}") else: diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 3e37047..a8fed53 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -67,7 +67,28 @@ class Plugin: Returns: DllDetectionResponse dict with detection status and path info """ - return self.dll_detection_service.check_lossless_scaling_dll() + result = self.dll_detection_service.check_lossless_scaling_dll() + + # Convert to dict to allow modification + result_dict = dict(result) + + # If DLL was detected, automatically update the configuration + if result.get("detected") and result.get("path"): + try: + dll_path = result["path"] + if dll_path: # Type guard + update_result = self.configuration_service.update_dll_path(dll_path) + if update_result.get("success"): + result_dict["config_updated"] = True + result_dict["message"] = f"DLL detected and configuration updated: {dll_path}" + else: + result_dict["config_updated"] = False + result_dict["message"] = f"DLL detected but config update failed: {update_result.get('error', 'Unknown error')}" + except Exception as e: + result_dict["config_updated"] = False + result_dict["message"] = f"DLL detected but config update failed: {str(e)}" + + return result_dict # Configuration methods async def get_lsfg_config(self) -> Dict[str, Any]: @@ -90,27 +111,36 @@ class Plugin: "defaults": ConfigurationManager.get_defaults() } - async def update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float, - hdr: bool, perf_mode: bool, immediate_mode: bool, disable_vkbasalt: bool, frame_cap: int) -> Dict[str, Any]: - """Update lsfg script configuration + async def update_lsfg_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, + performance_mode: bool, hdr_mode: bool) -> Dict[str, Any]: + """Update lsfg TOML configuration Args: - enable_lsfg: Whether to enable LSFG + enable: Whether to enable LSFG + dll: Path to Lossless.dll multiplier: LSFG multiplier value flow_scale: LSFG flow scale value - hdr: Whether to enable HDR - perf_mode: Whether to enable performance mode - immediate_mode: Whether to enable immediate present mode (disable vsync) - disable_vkbasalt: Whether to disable vkbasalt layer - frame_cap: Frame rate cap value (0-60, 0 = disabled) + performance_mode: Whether to enable performance mode + hdr_mode: Whether to enable HDR mode Returns: ConfigurationResponse dict with success status """ return self.configuration_service.update_config( - enable_lsfg, multiplier, flow_scale, hdr, perf_mode, immediate_mode, disable_vkbasalt, frame_cap + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode ) + async def update_dll_path(self, dll_path: str) -> Dict[str, Any]: + """Update the DLL path in the configuration when detected + + Args: + dll_path: Path to the detected Lossless.dll file + + Returns: + ConfigurationResponse dict with success status + """ + return self.configuration_service.update_dll_path(dll_path) + # Self-updater methods async def check_for_plugin_update(self) -> Dict[str, Any]: """Check for plugin updates by comparing current version with latest GitHub release diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index f7363c1..8cdf6f0 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -76,14 +76,14 @@ export const getConfigSchema = callable<[], ConfigSchemaResult>("get_config_sche // Updated config function using centralized configuration export const updateLsfgConfig = callable< - [boolean, number, number, boolean, boolean, boolean, boolean, number], + [boolean, string, number, number, boolean, boolean], ConfigUpdateResult >("update_lsfg_config"); // Helper function to create config update from configuration object export const updateLsfgConfigFromObject = async (config: ConfigurationData): Promise => { const args = ConfigurationManager.createArgsFromConfig(config); - return updateLsfgConfig(...args as [boolean, number, number, boolean, boolean, boolean, boolean, number]); + return updateLsfgConfig(...args as [boolean, string, number, number, boolean, boolean]); }; // Self-updater API functions diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 2545217..118b418 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -1,9 +1,9 @@ -import { PanelSectionRow, ToggleField, SliderField } from "@decky/ui"; +import { PanelSectionRow, ToggleField, SliderField, TextField } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; interface ConfigurationSectionProps { config: ConfigurationData; - onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number) => Promise; + onConfigChange: (fieldName: keyof ConfigurationData, value: boolean | number | string) => Promise; } export function ConfigurationSection({ @@ -30,16 +30,25 @@ export function ConfigurationSection({ onConfigChange('enable_lsfg', value)} + description="enable/disable lsfg on every game" + checked={config.enable} + onChange={(value) => onConfigChange('enable', value)} + /> + + + + onConfigChange('dll', e.target.value)} /> - - onConfigChange('hdr', value)} - /> - - onConfigChange('perf_mode', value)} + description="toggle performance mode (2x-8x performance increase)" + checked={config.performance_mode} + onChange={(value) => onConfigChange('performance_mode', value)} /> onConfigChange('immediate_mode', value)} - /> - - - - onConfigChange('frame_cap', value)} + label="HDR Mode" + description="enable hdr mode (doesn't support scrgb)" + checked={config.hdr_mode} + onChange={(value) => onConfigChange('hdr_mode', value)} /> - - {/* - onConfigChange('disable_vkbasalt', value)} - /> - */} ); } diff --git a/src/components/Content.tsx b/src/components/Content.tsx index ea3f3c1..613e722 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -37,7 +37,7 @@ export function Content() { }, [isInstalled, loadLsfgConfig]); // Generic configuration change handler - const handleConfigChange = async (fieldName: keyof ConfigurationData, value: boolean | number) => { + const handleConfigChange = async (fieldName: keyof ConfigurationData, value: boolean | number | string) => { await updateField(fieldName, value); }; diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index 727a0ab..ac721c7 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -6,88 +6,68 @@ interface UsageInstructionsProps { } export function UsageInstructions({ config }: UsageInstructionsProps) { - // Build manual environment variables string based on current config - const buildManualEnvVars = (): string => { - const envVars: string[] = []; - - if (config.enable_lsfg) { - envVars.push("ENABLE_LSFG=1"); - } - - // Always include multiplier and flow_scale if LSFG is enabled, as they have defaults - if (config.enable_lsfg) { - envVars.push(`LSFG_MULTIPLIER=${config.multiplier}`); - envVars.push(`LSFG_FLOW_SCALE=${config.flow_scale}`); - } - - if (config.hdr) { - envVars.push("LSFG_HDR=1"); - } - - if (config.perf_mode) { - envVars.push("LSFG_PERF_MODE=1"); - } - - if (config.immediate_mode) { - envVars.push("MESA_VK_WSI_PRESENT_MODE=immediate"); - } - - if (config.disable_vkbasalt) { - envVars.push("DISABLE_VKBASALT=1"); - } - - if (config.frame_cap > 0) { - envVars.push(`DXVK_FRAME_RATE=${config.frame_cap}`); - } - - return envVars.length > 0 ? `${envVars.join(" ")} %command%` : "%command%"; - }; - return ( <>
+ Usage Instructions +
+
+ + +
+ {config.enable + ? "LSFG is enabled globally. The layer will be active for all games automatically. No launch arguments needed." + : "LSFG is disabled. Enable it above to activate frame generation for all games." + } +
+
+ + +
+ {`Current Configuration: +• Enable: ${config.enable ? "Yes" : "No"} +• DLL Path: ${config.dll} +• Multiplier: ${config.multiplier}x +• Flow Scale: ${Math.round(config.flow_scale * 100)}% +• Performance Mode: ${config.performance_mode ? "Yes" : "No"} +• HDR Mode: ${config.hdr_mode ? "Yes" : "No"}`} +
+
+ + +
-
- Usage Instructions: -
-
- Option 1: Use the lsfg script (recommended): -
-
- ~/lsfg %command% -
-
- Option 2: Manual environment variables: -
-
- {buildManualEnvVars()} -
+ The configuration is stored in ~/.config/lsfg-vk/conf.toml and applies to all games globally.
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts index 6956030..83dc4aa 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -9,95 +9,72 @@ export enum ConfigFieldType { BOOLEAN = "boolean", INTEGER = "integer", - FLOAT = "float" + FLOAT = "float", + STRING = "string" } // Configuration field definition export interface ConfigField { name: string; fieldType: ConfigFieldType; - default: boolean | number; + default: boolean | number | string; description: string; - scriptTemplate: string; - scriptComment?: string; } // Configuration schema - must match Python CONFIG_SCHEMA export const CONFIG_SCHEMA: Record = { - enable_lsfg: { - name: "enable_lsfg", + enable: { + name: "enable", fieldType: ConfigFieldType.BOOLEAN, default: true, - description: "Enables the frame generation layer", - scriptTemplate: "export ENABLE_LSFG={value}", - scriptComment: "# export ENABLE_LSFG=1" + description: "enable/disable lsfg on every game" + }, + + dll: { + name: "dll", + fieldType: ConfigFieldType.STRING, + default: "/games/Lossless Scaling/Lossless.dll", + description: "specify where Lossless.dll is stored" }, multiplier: { name: "multiplier", fieldType: ConfigFieldType.INTEGER, default: 2, - description: "Traditional FPS multiplier value", - scriptTemplate: "export LSFG_MULTIPLIER={value}" + description: "change the fps multiplier" }, flow_scale: { name: "flow_scale", fieldType: ConfigFieldType.FLOAT, default: 0.8, - description: "Lowers the internal motion estimation resolution", - scriptTemplate: "export LSFG_FLOW_SCALE={value}" - }, - - hdr: { - name: "hdr", - fieldType: ConfigFieldType.BOOLEAN, - default: false, - description: "Enable HDR mode (only if Game supports HDR)", - scriptTemplate: "export LSFG_HDR={value}", - scriptComment: "# export LSFG_HDR=1" + description: "change the flow scale (lower = faster)" }, - perf_mode: { - name: "perf_mode", + performance_mode: { + name: "performance_mode", fieldType: ConfigFieldType.BOOLEAN, default: true, - description: "Use lighter model for FG", - scriptTemplate: "export LSFG_PERF_MODE={value}", - scriptComment: "# export LSFG_PERF_MODE=1" + description: "toggle performance mode (2x-8x performance increase)" }, - immediate_mode: { - name: "immediate_mode", + hdr_mode: { + name: "hdr_mode", fieldType: ConfigFieldType.BOOLEAN, default: false, - description: "Reduce input lag (Experimental, will cause issues in many games)", - scriptTemplate: "export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync", - scriptComment: "# export MESA_VK_WSI_PRESENT_MODE=immediate # - disable vsync" - }, - - disable_vkbasalt: { - name: "disable_vkbasalt", - fieldType: ConfigFieldType.BOOLEAN, - default: true, - description: "Some plugins add vkbasalt layer, which can break lsfg. Toggling on fixes this", - scriptTemplate: "export DISABLE_VKBASALT={value}", - scriptComment: "# export DISABLE_VKBASALT=1" - }, - - frame_cap: { - name: "frame_cap", - fieldType: ConfigFieldType.INTEGER, - default: 0, - description: "Limit base game FPS (0 = disabled)", - scriptTemplate: "export DXVK_FRAME_RATE={value}", - scriptComment: "# export DXVK_FRAME_RATE=60" + description: "enable hdr mode (doesn't support scrgb)" } }; // Type-safe configuration data structure export interface ConfigurationData { - enable_lsfg: boolean; + enable: boolean; + dll: string; + multiplier: number; + flow_scale: number; + performance_mode: boolean; + hdr_mode: boolean; +} multiplier: number; flow_scale: number; hdr: boolean; @@ -140,7 +117,7 @@ export class ConfigurationManager { /** * Create ordered arguments array from configuration object */ - static createArgsFromConfig(config: ConfigurationData): (boolean | number)[] { + static createArgsFromConfig(config: ConfigurationData): (boolean | number | string)[] { return this.getFieldNames().map(fieldName => config[fieldName as keyof ConfigurationData] ); @@ -163,6 +140,8 @@ export class ConfigurationManager { (validated as any)[fieldName] = parseInt(String(value), 10); } else if (fieldDef.fieldType === ConfigFieldType.FLOAT) { (validated as any)[fieldName] = parseFloat(String(value)); + } else if (fieldDef.fieldType === ConfigFieldType.STRING) { + (validated as any)[fieldName] = String(value); } } }); diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts index 8ff9061..e514d72 100644 --- a/src/hooks/useLsfgHooks.ts +++ b/src/hooks/useLsfgHooks.ts @@ -110,7 +110,7 @@ export function useLsfgConfig() { } }, []); - const updateField = useCallback(async (fieldName: keyof ConfigurationData, value: boolean | number): Promise => { + const updateField = useCallback(async (fieldName: keyof ConfigurationData, value: boolean | number | string): Promise => { const newConfig = { ...config, [fieldName]: value }; return updateConfig(newConfig); }, [config, updateConfig]); -- cgit v1.2.3 From 57bd1cd9fcbbc8d4cd1d2f45106fc29d00bb6918 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 14:35:06 -0400 Subject: write dll path when discovered --- py_modules/lsfg_vk/config_schema.py | 30 +++++++++++++++++++++++++++++- py_modules/lsfg_vk/configuration.py | 18 ++++++++++++------ py_modules/lsfg_vk/installation.py | 16 ++++++++++++---- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index f054abf..8c5f58c 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -48,7 +48,7 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = { "dll": ConfigField( name="dll", field_type=ConfigFieldType.STRING, - default="/games/Lossless Scaling/Lossless.dll", + default="", # Will be populated dynamically based on detection description="specify where Lossless.dll is stored" ), @@ -103,6 +103,34 @@ class ConfigurationManager: for field in CONFIG_SCHEMA.values() }) + @staticmethod + def get_defaults_with_dll_detection(dll_detection_service=None) -> ConfigurationData: + """Get default configuration values with DLL path detection + + Args: + dll_detection_service: Optional DLL detection service instance + + Returns: + ConfigurationData with detected DLL path if available + """ + defaults = ConfigurationManager.get_defaults() + + # Try to detect DLL path if service provided + if dll_detection_service: + try: + dll_result = dll_detection_service.check_lossless_scaling_dll() + if dll_result.get("detected") and dll_result.get("path"): + defaults["dll"] = dll_result["path"] + except Exception: + # If detection fails, keep empty default + pass + + # If DLL path is still empty, use a reasonable fallback + if not defaults["dll"]: + defaults["dll"] = "/home/deck/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll" + + return defaults + @staticmethod def get_field_names() -> list[str]: """Get ordered list of configuration field names""" diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index 4d76a30..c15fc37 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -21,8 +21,10 @@ class ConfigurationService(BaseService): """ try: if not self.config_file_path.exists(): - # Return default configuration if file doesn't exist - config = ConfigurationManager.get_defaults() + # Return default configuration with DLL detection if file doesn't exist + from .dll_detection import DllDetectionService + dll_service = DllDetectionService(self.log) + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) return { "success": True, "config": config, @@ -52,8 +54,10 @@ class ConfigurationService(BaseService): except Exception as e: error_msg = f"Error parsing config file: {str(e)}" self.log.error(error_msg) - # Return defaults if parsing fails - config = ConfigurationManager.get_defaults() + # Return defaults with DLL detection if parsing fails + from .dll_detection import DllDetectionService + dll_service = DllDetectionService(self.log) + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) return { "success": True, "config": config, @@ -134,8 +138,10 @@ class ConfigurationService(BaseService): # Get current config current_response = self.get_config() if not current_response["success"] or current_response["config"] is None: - # If we can't read current config, use defaults - config = ConfigurationManager.get_defaults() + # If we can't read current config, use defaults with DLL detection + from .dll_detection import DllDetectionService + dll_service = DllDetectionService(self.log) + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) else: config = current_response["config"] diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index a1c2d64..27be850 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -103,16 +103,24 @@ class InstallationService(BaseService): self.log.info(f"Copied {file} to {dst_file}") def _create_config_file(self) -> None: - """Create the TOML config file in ~/.config/lsfg-vk with default configuration""" - # Get default configuration - defaults = ConfigurationManager.get_defaults() + """Create the TOML config file in ~/.config/lsfg-vk with default configuration and detected DLL path""" + # Import here to avoid circular imports + from .dll_detection import DllDetectionService + + # Try to detect DLL path + dll_service = DllDetectionService(self.log) + config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) # Generate TOML content using centralized manager - toml_content = ConfigurationManager.generate_toml_content(defaults) + toml_content = ConfigurationManager.generate_toml_content(config) # Use atomic write to prevent corruption self._atomic_write(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']}") def check_installation(self) -> InstallationCheckResponse: """Check if lsfg-vk is already installed -- cgit v1.2.3 From ad0ba0fc61f83e2aaf22192e7d0ad05dde9ffd62 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 14:53:18 -0400 Subject: fix descriptions and cleanup UI --- py_modules/lsfg_vk/config_schema.py | 8 ++++---- src/components/ConfigurationSection.tsx | 12 ++++++------ src/config/configSchema.ts | 14 +++----------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 8c5f58c..1604f5d 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -63,21 +63,21 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = { name="flow_scale", field_type=ConfigFieldType.FLOAT, default=0.8, - description="change the flow scale (lower = faster)" + description="change the flow scale" ), "performance_mode": ConfigField( name="performance_mode", field_type=ConfigFieldType.BOOLEAN, default=True, - description="toggle performance mode (2x-8x performance increase)" + description="toggle performance mode" ), "hdr_mode": ConfigField( name="hdr_mode", field_type=ConfigFieldType.BOOLEAN, default=False, - description="enable hdr mode (doesn't support scrgb)" + description="enable hdr mode" ) } @@ -93,7 +93,7 @@ class ConfigurationData(TypedDict): class ConfigurationManager: - """Centralized configuration management for TOML-based config""" + """Centralized configuration management""" @staticmethod def get_defaults() -> ConfigurationData: diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 118b418..a1c175a 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -30,25 +30,25 @@ export function ConfigurationSection({ onConfigChange('enable', value)} /> - + {/* onConfigChange('dll', e.target.value)} /> - + */} onConfigChange('performance_mode', value)} /> @@ -87,7 +87,7 @@ export function ConfigurationSection({ onConfigChange('hdr_mode', value)} /> diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts index 83dc4aa..6dc8687 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -48,21 +48,21 @@ export const CONFIG_SCHEMA: Record = { name: "flow_scale", fieldType: ConfigFieldType.FLOAT, default: 0.8, - description: "change the flow scale (lower = faster)" + description: "change the flow scale" }, performance_mode: { name: "performance_mode", fieldType: ConfigFieldType.BOOLEAN, default: true, - description: "toggle performance mode (2x-8x performance increase)" + description: "toggle performance mode" }, hdr_mode: { name: "hdr_mode", fieldType: ConfigFieldType.BOOLEAN, default: false, - description: "enable hdr mode (doesn't support scrgb)" + description: "enable hdr in games that support it" } }; @@ -75,14 +75,6 @@ export interface ConfigurationData { performance_mode: boolean; hdr_mode: boolean; } - multiplier: number; - flow_scale: number; - hdr: boolean; - perf_mode: boolean; - immediate_mode: boolean; - disable_vkbasalt: boolean; - frame_cap: number; -} // Centralized configuration manager export class ConfigurationManager { -- cgit v1.2.3 From 620c04f75ad0f6025cc26f73dd07c466d6e1c62e Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 21:00:44 -0400 Subject: fix config writes --- justfile | 2 +- package.json | 2 +- py_modules/lsfg_vk/base_service.py | 37 +++++++++++-------------------------- py_modules/lsfg_vk/configuration.py | 10 +++++----- py_modules/lsfg_vk/installation.py | 4 ++-- 5 files changed, 20 insertions(+), 35 deletions(-) diff --git a/justfile b/justfile index d92039c..4d6a2be 100644 --- a/justfile +++ b/justfile @@ -5,7 +5,7 @@ build: sudo rm -rf node_modules && .vscode/build.sh test: - scp "/Users/kurt/Developer/decky-lossless-scaling-vk/out/Lossless Scaling.zip" deck@192.168.0.6:~/Desktop + scp "/var/home/bazzite/decky-lossless-scaling-vk/out/Lossless Scaling.zip" deck@192.168.0.6:~/Desktop clean: rm -rf node_modules dist \ No newline at end of file diff --git a/package.json b/package.json index df7ba84..adcbe1c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ { "name": "lsfg-vk_archlinux.zip", "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/pre-conf-jul16/lsfg-vk_archlinux.zip", - "sha256hash": "c9eb86e0ef458395fca18161b1620855b21eee54aa1df536a5fbbd2d524b1b88" + "sha256hash": "d44add7cec95a54f36e0b48fd3f509fda6852ff25127d58574a9ee31a9885864" } ], "pnpm": { diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py index 92ac152..a796480 100644 --- a/py_modules/lsfg_vk/base_service.py +++ b/py_modules/lsfg_vk/base_service.py @@ -4,7 +4,6 @@ Base service class with common functionality. import os import shutil -import tempfile from pathlib import Path from typing import Any, Optional @@ -65,8 +64,8 @@ class BaseService: self.log.info(f"File not found: {path}") return False - def _atomic_write(self, path: Path, content: str, mode: int = 0o644) -> None: - """Write content to a file atomically + def _write_file(self, path: Path, content: str, mode: int = 0o644) -> None: + """Write content to a file Args: path: Target file path @@ -76,31 +75,17 @@ class BaseService: Raises: OSError: If write fails """ - # Create temporary file in the same directory to ensure atomic move - temp_path = None try: - with tempfile.NamedTemporaryFile( - mode='w', - dir=path.parent, - delete=False, - prefix=f'.{path.name}.', - suffix='.tmp' - ) as temp_file: - temp_file.write(content) - temp_path = Path(temp_file.name) + # Write directly to the file + with open(path, 'w', encoding='utf-8') as f: + f.write(content) + f.flush() # Ensure data is written to disk + os.fsync(f.fileno()) # Force filesystem sync - # Set permissions before moving - temp_path.chmod(mode) - - # Atomic move - temp_path.replace(path) - self.log.info(f"Atomically wrote to {path}") + # Set permissions + path.chmod(mode) + self.log.info(f"Wrote to {path}") except Exception: - # Clean up temp file if something went wrong - if temp_path and temp_path.exists(): - try: - temp_path.unlink() - except OSError: - pass # Best effort cleanup + self.log.error(f"Failed to write to {path}") raise diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index c15fc37..a4fcae5 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -89,11 +89,11 @@ class ConfigurationService(BaseService): # Generate TOML content using centralized manager toml_content = ConfigurationManager.generate_toml_content(config) - # Ensure config directory exists + # Ensure config directory exists self.config_dir.mkdir(parents=True, exist_ok=True) - # Write the updated config atomically - self._atomic_write(self.config_file_path, toml_content, 0o644) + # Write the updated config directly to preserve inode for file watchers + self._write_file(self.config_file_path, toml_content, 0o644) self.log.info(f"Updated lsfg TOML configuration: enable={enable}, " f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, " @@ -154,8 +154,8 @@ class ConfigurationService(BaseService): # Ensure config directory exists self.config_dir.mkdir(parents=True, exist_ok=True) - # Write the updated config atomically - self._atomic_write(self.config_file_path, toml_content, 0o644) + # Write the updated config directly to preserve inode for file watchers + self._write_file(self.config_file_path, toml_content, 0o644) self.log.info(f"Updated DLL path in lsfg configuration: '{dll_path}'") diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 27be850..fc9ac97 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -114,8 +114,8 @@ class InstallationService(BaseService): # Generate TOML content using centralized manager toml_content = ConfigurationManager.generate_toml_content(config) - # Use atomic write to prevent corruption - self._atomic_write(self.config_file_path, toml_content, 0o644) + # Write initial config file + 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 -- cgit v1.2.3 From f2870ff308131a0a4c970edf36bb88aac10a6175 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 21:35:54 -0400 Subject: fix dll config write crash, add multiplier = 1 --- py_modules/lsfg_vk/plugin.py | 11 +++++++++++ src/components/ConfigurationSection.tsx | 13 +++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index a8fed53..c56765b 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -64,6 +64,17 @@ class Plugin: async def check_lossless_scaling_dll(self) -> Dict[str, Any]: """Check if Lossless Scaling DLL is available at the expected paths + Returns: + DllDetectionResponse dict with detection status and path info + """ + return self.dll_detection_service.check_lossless_scaling_dll() + + async def check_lossless_scaling_dll_and_update_config(self) -> Dict[str, Any]: + """Check for DLL and automatically update configuration if found + + This method should only be used during installation or when explicitly + requested by the user, not for routine DLL detection checks. + Returns: DllDetectionResponse dict with detection status and path info """ diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index a1c175a..deb8fba 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -48,16 +48,17 @@ export function ConfigurationSection({ onConfigChange('multiplier', value)} /> -- cgit v1.2.3 From 0670041467ca5625d93e3e4dbc2f738da24d88b4 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 23:23:03 -0400 Subject: add experimental toggles --- py_modules/lsfg_vk/config_schema.py | 24 +++++++++++++++-- py_modules/lsfg_vk/configuration.py | 13 +++++++--- py_modules/lsfg_vk/plugin.py | 9 +++++-- src/api/lsfgApi.ts | 4 +-- src/components/ConfigurationSection.tsx | 46 ++++++++++++++++++++++++++++++++- src/components/Content.tsx | 4 +-- src/components/UsageInstructions.tsx | 4 ++- src/components/index.ts | 2 +- src/config/configSchema.ts | 16 ++++++++++++ 9 files changed, 108 insertions(+), 14 deletions(-) diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 1604f5d..42ac640 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -78,6 +78,20 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = { field_type=ConfigFieldType.BOOLEAN, default=False, description="enable hdr mode" + ), + + "experimental_present_mode": ConfigField( + name="experimental_present_mode", + field_type=ConfigFieldType.STRING, + default="", + description="experimental: override vulkan present mode (empty/fifo/vsync/mailbox/immediate)" + ), + + "experimental_fps_limit": ConfigField( + name="experimental_fps_limit", + field_type=ConfigFieldType.INTEGER, + default=0, + description="experimental: base framerate cap for dxvk games, before frame multiplier (0 = disabled)" ) } @@ -90,6 +104,8 @@ class ConfigurationData(TypedDict): flow_scale: float performance_mode: bool hdr_mode: bool + experimental_present_mode: str + experimental_fps_limit: int class ConfigurationManager: @@ -250,7 +266,9 @@ class ConfigurationManager: @staticmethod def create_config_from_args(enable: bool, dll: str, multiplier: int, flow_scale: float, - performance_mode: bool, hdr_mode: bool) -> ConfigurationData: + performance_mode: bool, hdr_mode: bool, + experimental_present_mode: str = "", + experimental_fps_limit: int = 0) -> ConfigurationData: """Create configuration from individual arguments""" return cast(ConfigurationData, { "enable": enable, @@ -258,5 +276,7 @@ class ConfigurationManager: "multiplier": multiplier, "flow_scale": flow_scale, "performance_mode": performance_mode, - "hdr_mode": hdr_mode + "hdr_mode": hdr_mode, + "experimental_present_mode": experimental_present_mode, + "experimental_fps_limit": experimental_fps_limit }) diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index a4fcae5..255092a 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -66,7 +66,9 @@ class ConfigurationService(BaseService): } def update_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, - performance_mode: bool, hdr_mode: bool) -> ConfigurationResponse: + performance_mode: bool, hdr_mode: bool, + experimental_present_mode: str = "", + experimental_fps_limit: int = 0) -> ConfigurationResponse: """Update TOML configuration Args: @@ -76,6 +78,8 @@ class ConfigurationService(BaseService): 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 + experimental_fps_limit: Experimental FPS limit for DXVK games Returns: ConfigurationResponse with success status @@ -83,7 +87,8 @@ class ConfigurationService(BaseService): try: # Create configuration from individual arguments config = ConfigurationManager.create_config_from_args( - enable, dll, multiplier, flow_scale, performance_mode, hdr_mode + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode, + experimental_present_mode, experimental_fps_limit ) # Generate TOML content using centralized manager @@ -97,7 +102,9 @@ class ConfigurationService(BaseService): self.log.info(f"Updated lsfg TOML configuration: enable={enable}, " f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, " - f"performance_mode={performance_mode}, hdr_mode={hdr_mode}") + f"performance_mode={performance_mode}, hdr_mode={hdr_mode}, " + f"experimental_present_mode='{experimental_present_mode}', " + f"experimental_fps_limit={experimental_fps_limit}") return { "success": True, diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index c56765b..a7b6045 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -123,7 +123,9 @@ class Plugin: } async def update_lsfg_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float, - performance_mode: bool, hdr_mode: bool) -> Dict[str, Any]: + performance_mode: bool, hdr_mode: bool, + experimental_present_mode: str = "", + experimental_fps_limit: int = 0) -> Dict[str, Any]: """Update lsfg TOML configuration Args: @@ -133,12 +135,15 @@ class Plugin: 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 + experimental_fps_limit: Experimental FPS limit for DXVK games Returns: ConfigurationResponse dict with success status """ return self.configuration_service.update_config( - enable, dll, multiplier, flow_scale, performance_mode, hdr_mode + enable, dll, multiplier, flow_scale, performance_mode, hdr_mode, + experimental_present_mode, experimental_fps_limit ) async def update_dll_path(self, dll_path: str) -> Dict[str, Any]: diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index 8cdf6f0..7d37f4e 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -76,14 +76,14 @@ export const getConfigSchema = callable<[], ConfigSchemaResult>("get_config_sche // Updated config function using centralized configuration export const updateLsfgConfig = callable< - [boolean, string, number, number, boolean, boolean], + [boolean, string, number, number, boolean, boolean, string, number], ConfigUpdateResult >("update_lsfg_config"); // Helper function to create config update from configuration object export const updateLsfgConfigFromObject = async (config: ConfigurationData): Promise => { const args = ConfigurationManager.createArgsFromConfig(config); - return updateLsfgConfig(...args as [boolean, string, number, number, boolean, boolean]); + return updateLsfgConfig(...args as [boolean, string, number, number, boolean, boolean, string, number]); }; // Self-updater API functions diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index deb8fba..76b9bc2 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -1,4 +1,4 @@ -import { PanelSectionRow, ToggleField, SliderField, TextField } from "@decky/ui"; +import { PanelSectionRow, ToggleField, SliderField, Dropdown } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; interface ConfigurationSectionProps { @@ -93,6 +93,50 @@ export function ConfigurationSection({ onChange={(value) => onConfigChange('hdr_mode', value)} /> + + {/* Experimental Features Section */} + +
+ ⚠️ Experimental Features +
+
+ + + onConfigChange('experimental_present_mode', value.data)} + rgOptions={[ + { data: "", label: "Default (FIFO)" }, + { data: "fifo", label: "FIFO" }, + { data: "vsync", label: "VSync" }, + { data: "mailbox", label: "Mailbox" }, + { data: "immediate", label: "Immediate" } + ]} + /> + + + + 0 ? ` (${config.experimental_fps_limit} FPS)` : ' (Off)'}`} + description="Base framerate cap for DXVK games, before frame multiplier (0 = disabled)" + value={config.experimental_fps_limit} + min={0} + max={60} + step={1} + onChange={(value) => onConfigChange('experimental_fps_limit', value)} + /> + ); } diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 613e722..ae64931 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -5,7 +5,7 @@ import { useInstallationActions } from "../hooks/useInstallationActions"; import { StatusDisplay } from "./StatusDisplay"; import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; -import { UsageInstructions } from "./UsageInstructions"; +// import { UsageInstructions } from "./UsageInstructions"; import { WikiButton } from "./WikiButton"; import { ClipboardButton } from "./ClipboardButton"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; @@ -74,7 +74,7 @@ export function Content() { /> )} - + {/* */} diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index ac721c7..d156f9d 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -54,7 +54,9 @@ export function UsageInstructions({ config }: UsageInstructionsProps) { • Multiplier: ${config.multiplier}x • Flow Scale: ${Math.round(config.flow_scale * 100)}% • Performance Mode: ${config.performance_mode ? "Yes" : "No"} -• HDR Mode: ${config.hdr_mode ? "Yes" : "No"}`} +• HDR Mode: ${config.hdr_mode ? "Yes" : "No"} +• Present Mode: ${config.experimental_present_mode || "Default (FIFO)"} +• FPS Limit: ${config.experimental_fps_limit > 0 ? `${config.experimental_fps_limit} FPS` : "Off"}`}
diff --git a/src/components/index.ts b/src/components/index.ts index d26159d..ab7a117 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -2,7 +2,7 @@ export { Content } from "./Content"; export { StatusDisplay } from "./StatusDisplay"; export { InstallationButton } from "./InstallationButton"; export { ConfigurationSection } from "./ConfigurationSection"; -export { UsageInstructions } from "./UsageInstructions"; +// export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; export { ClipboardButton } from "./ClipboardButton"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts index 6dc8687..9b6fc41 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -63,6 +63,20 @@ export const CONFIG_SCHEMA: Record = { fieldType: ConfigFieldType.BOOLEAN, default: false, description: "enable hdr in games that support it" + }, + + experimental_present_mode: { + name: "experimental_present_mode", + fieldType: ConfigFieldType.STRING, + default: "", + description: "experimental: override vulkan present mode (empty/fifo/vsync/mailbox/immediate)" + }, + + experimental_fps_limit: { + name: "experimental_fps_limit", + fieldType: ConfigFieldType.INTEGER, + default: 0, + description: "experimental: base framerate cap for dxvk games, before frame multiplier (0 = disabled)" } }; @@ -74,6 +88,8 @@ export interface ConfigurationData { flow_scale: number; performance_mode: boolean; hdr_mode: boolean; + experimental_present_mode: string; + experimental_fps_limit: number; } // Centralized configuration manager -- cgit v1.2.3 From a921fc8168e13934bdfe6d159aee14ee2651949e Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 23:44:51 -0400 Subject: styling cleanup --- src/components/ConfigurationSection.tsx | 47 +++++++----- src/components/PluginUpdateChecker.tsx | 123 +++++++++++++++++++------------- 2 files changed, 105 insertions(+), 65 deletions(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 76b9bc2..0ee605c 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -94,17 +94,16 @@ export function ConfigurationSection({ />
- {/* Experimental Features Section */}
⚠️ Experimental Features @@ -112,18 +111,34 @@ export function ConfigurationSection({ - onConfigChange('experimental_present_mode', value.data)} - rgOptions={[ - { data: "", label: "Default (FIFO)" }, - { data: "fifo", label: "FIFO" }, - { data: "vsync", label: "VSync" }, - { data: "mailbox", label: "Mailbox" }, - { data: "immediate", label: "Immediate" } - ]} - /> +
+
+ Override Vulkan present mode +
+
+ Select a specific Vulkan presentation mode for better performance or compatibility +
+ onConfigChange('experimental_present_mode', value.data)} + rgOptions={[ + { data: "", label: "Default (FIFO)" }, + { data: "fifo", label: "FIFO" }, + { data: "vsync", label: "VSync" }, + { data: "mailbox", label: "Mailbox" }, + { data: "immediate", label: "Immediate" } + ]} + /> +
diff --git a/src/components/PluginUpdateChecker.tsx b/src/components/PluginUpdateChecker.tsx index 0028a79..a3982c2 100644 --- a/src/components/PluginUpdateChecker.tsx +++ b/src/components/PluginUpdateChecker.tsx @@ -1,7 +1,8 @@ import React, { useState, useEffect } from 'react'; import { ButtonItem, - PanelSection + PanelSection, + PanelSectionRow } from '@decky/ui'; import { checkForPluginUpdate, downloadPluginUpdate, UpdateCheckResult, UpdateDownloadResult } from '../api/lsfgApi'; @@ -121,66 +122,90 @@ export const PluginUpdateChecker: React.FC = () => { }; return ( - - - {checkingUpdate ? 'Checking for updates...' : 'Check for Updates'} - + + +
+ PLUGIN UPDATES +
+
- {updateInfo && updateInfo.updateAvailable && !downloadResult?.success && ( + - {downloadingUpdate ? 'Downloading...' : 'Download Update'} + {checkingUpdate ? 'Checking for updates...' : 'Check for Updates'} + + + {updateInfo && updateInfo.updateAvailable && !downloadResult?.success && ( + + + {downloadingUpdate ? 'Downloading...' : 'Download Update'} + + )} {downloadResult?.success && ( -
-
- ✓ Download Complete! + +
+
+ ✓ Download Complete! +
+
+ File saved to: {downloadResult.download_path} +
+
+ Installation Instructions: +
    +
  1. Go to Decky Loader settings
  2. +
  3. Click "Developer" tab
  4. +
  5. Click "Uninstall" next to "Lossless Scaling"
  6. +
  7. Click "Install from ZIP"
  8. +
  9. Select the downloaded file
  10. +
  11. Restart Steam or reload plugins
  12. +
+
-
- File saved to: {downloadResult.download_path} -
-
- Installation Instructions: -
    -
  1. Go to Decky Loader settings
  2. -
  3. Click "Developer" tab
  4. -
  5. Click "Uninstall" next to "Lossless Scaling"
  6. -
  7. Click "Install from ZIP"
  8. -
  9. Select the downloaded file
  10. -
  11. Restart Steam or reload plugins
  12. -
-
-
+ )} {updateError && ( -
- {updateError} -
+ +
+ {updateError} +
+
)} ); -- cgit v1.2.3 From f74ce82fcebdcb11c190a1652277f8e08d9590f4 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 23:51:05 -0400 Subject: persist plugin state --- src/components/Content.tsx | 4 ++-- src/components/index.ts | 2 +- src/index.tsx | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Content.tsx b/src/components/Content.tsx index ae64931..b248313 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -7,7 +7,7 @@ import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; // import { UsageInstructions } from "./UsageInstructions"; import { WikiButton } from "./WikiButton"; -import { ClipboardButton } from "./ClipboardButton"; +// import { ClipboardButton } from "./ClipboardButton"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { ConfigurationData } from "../config/configSchema"; @@ -77,7 +77,7 @@ export function Content() { {/* */} - + {/* */} {/* Plugin Update Checker */} diff --git a/src/components/index.ts b/src/components/index.ts index ab7a117..ec4c194 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,5 +4,5 @@ export { InstallationButton } from "./InstallationButton"; export { ConfigurationSection } from "./ConfigurationSection"; // export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; -export { ClipboardButton } from "./ClipboardButton"; +// export { ClipboardButton } from "./ClipboardButton"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; diff --git a/src/index.tsx b/src/index.tsx index e8ab56f..785ecf5 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,6 +11,8 @@ export default definePlugin(() => { name: "Lossless Scaling", // The element displayed at the top of your plugin's menu titleView:
Lossless Scaling
, + // Always render to retain state when panel is toggled + alwaysRender: true, // The content of your plugin's menu content: , // The icon displayed in the plugin list -- cgit v1.2.3 From b04af8a2d32ca940e1c626090e13804fed7057c6 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Thu, 17 Jul 2025 23:59:15 -0400 Subject: updater styling alignment --- src/components/PluginUpdateChecker.tsx | 104 +++++++++++---------------------- 1 file changed, 34 insertions(+), 70 deletions(-) diff --git a/src/components/PluginUpdateChecker.tsx b/src/components/PluginUpdateChecker.tsx index a3982c2..e7a9345 100644 --- a/src/components/PluginUpdateChecker.tsx +++ b/src/components/PluginUpdateChecker.tsx @@ -2,7 +2,9 @@ import React, { useState, useEffect } from 'react'; import { ButtonItem, PanelSection, - PanelSectionRow + PanelSectionRow, + Field, + Focusable } from '@decky/ui'; import { checkForPluginUpdate, downloadPluginUpdate, UpdateCheckResult, UpdateDownloadResult } from '../api/lsfgApi'; @@ -100,45 +102,17 @@ export const PluginUpdateChecker: React.FC = () => { if (updateInfo.updateAvailable) { if (downloadResult?.success) { - return ( -
- ✓ v{updateInfo.latestVersion} downloaded - ready to install -
- ); + return "✓ v" + updateInfo.latestVersion + " downloaded - ready to install"; } else { - return ( -
- Update available: v{updateInfo.latestVersion} -
- ); + return "Update available: v" + updateInfo.latestVersion; } } else { - return ( -
- Up to date (v{updateInfo.currentVersion}) -
- ); + return "Up to date (v" + updateInfo.currentVersion + ")"; } }; return ( - - -
- PLUGIN UPDATES -
-
- + = () => { )} {downloadResult?.success && ( - -
-
- ✓ Download Complete! -
-
- File saved to: {downloadResult.download_path} -
-
- Installation Instructions: -
    -
  1. Go to Decky Loader settings
  2. -
  3. Click "Developer" tab
  4. -
  5. Click "Uninstall" next to "Lossless Scaling"
  6. -
  7. Click "Install from ZIP"
  8. -
  9. Select the downloaded file
  10. -
  11. Restart Steam or reload plugins
  12. -
-
-
-
+ <> + + + + File saved to: {downloadResult.download_path} + + + + + + + + 1. Go to Decky Loader settings +
2. Click "Developer" tab +
3. Click "Uninstall" next to "Lossless Scaling" +
4. Click "Install from ZIP" +
5. Select the downloaded file +
6. Restart Steam or reload plugins +
+
+
+ )} {updateError && ( -
- {updateError} -
+ + + {updateError} + +
)}
-- cgit v1.2.3 From 14b08ac219dc134e130fc89b02c5a963d93bf243 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 00:06:11 -0400 Subject: fix experimental section formatting --- src/components/ConfigurationSection.tsx | 46 ++++++++++++--------------------- src/components/PluginUpdateChecker.tsx | 2 +- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 0ee605c..8433b04 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -1,4 +1,4 @@ -import { PanelSectionRow, ToggleField, SliderField, Dropdown } from "@decky/ui"; +import { PanelSectionRow, ToggleField, SliderField, DropdownItem } from "@decky/ui"; import { ConfigurationData } from "../config/configSchema"; interface ConfigurationSectionProps { @@ -106,39 +106,25 @@ export function ConfigurationSection({ color: "white" }} > - ⚠️ Experimental Features + Experimental Features
-
-
- Override Vulkan present mode -
-
- Select a specific Vulkan presentation mode for better performance or compatibility -
- onConfigChange('experimental_present_mode', value.data)} - rgOptions={[ - { data: "", label: "Default (FIFO)" }, - { data: "fifo", label: "FIFO" }, - { data: "vsync", label: "VSync" }, - { data: "mailbox", label: "Mailbox" }, - { data: "immediate", label: "Immediate" } - ]} - /> -
+ onConfigChange('experimental_present_mode', value.data)} + rgOptions={[ + { data: "", label: "Default (FIFO)" }, + { data: "fifo", label: "FIFO" }, + { data: "vsync", label: "VSync" }, + { data: "mailbox", label: "Mailbox" }, + { data: "immediate", label: "Immediate" } + ]} + />
diff --git a/src/components/PluginUpdateChecker.tsx b/src/components/PluginUpdateChecker.tsx index e7a9345..d427c18 100644 --- a/src/components/PluginUpdateChecker.tsx +++ b/src/components/PluginUpdateChecker.tsx @@ -140,7 +140,7 @@ export const PluginUpdateChecker: React.FC = () => { {downloadResult?.success && ( <> - + File saved to: {downloadResult.download_path} -- cgit v1.2.3 From 4ab2952d1b3bd5b87e9b9766c0b098f1b4885340 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 09:15:00 -0400 Subject: description tweak --- 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 8433b04..794a860 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -130,7 +130,7 @@ export function ConfigurationSection({ 0 ? ` (${config.experimental_fps_limit} FPS)` : ' (Off)'}`} - description="Base framerate cap for DXVK games, before frame multiplier (0 = disabled)" + description="Base framerate cap for DirectX games, before frame multiplier (0 = disabled)" value={config.experimental_fps_limit} min={0} max={60} -- cgit v1.2.3 From da113f878447e0830d414bb90b79b9a03d8cedec Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 09:17:16 -0400 Subject: fix label for multiplier --- 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 794a860..f44dd4f 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -55,7 +55,7 @@ export function ConfigurationSection({ step={1} notchCount={4} notchLabels={[ - { notchIndex: 0, label: "1X" }, + { notchIndex: 0, label: "OFF" }, { notchIndex: 1, label: "2X" }, { notchIndex: 2, label: "3X" }, { notchIndex: 3, label: "4X" } -- cgit v1.2.3 From 48ee73dae1bdecec47ccbaf5456be8c5937cb0fd Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 12:00:31 -0400 Subject: new profile method workaround for sudo global use --- justfile | 2 +- package.json | 4 +- py_modules/lsfg_vk/config_schema.py | 100 +++++++++++++++++++++++++---------- py_modules/lsfg_vk/plugin.py | 30 ++++++++--- src/api/lsfgApi.ts | 7 +++ src/components/ClipboardButton.tsx | 28 ++++++++-- src/components/Content.tsx | 8 +-- src/components/LaunchOptionInfo.tsx | 25 +++++++++ src/components/UsageInstructions.tsx | 26 +++++++-- src/components/index.ts | 3 +- 10 files changed, 182 insertions(+), 51 deletions(-) create mode 100644 src/components/LaunchOptionInfo.tsx diff --git a/justfile b/justfile index 4d6a2be..9923416 100644 --- a/justfile +++ b/justfile @@ -5,7 +5,7 @@ build: sudo rm -rf node_modules && .vscode/build.sh test: - scp "/var/home/bazzite/decky-lossless-scaling-vk/out/Lossless Scaling.zip" deck@192.168.0.6:~/Desktop + scp "out/Lossless Scaling.zip" deck@192.168.0.6:~/Desktop clean: rm -rf node_modules dist \ No newline at end of file diff --git a/package.json b/package.json index adcbe1c..c94df3f 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "remote_binary": [ { "name": "lsfg-vk_archlinux.zip", - "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/pre-conf-jul16/lsfg-vk_archlinux.zip", - "sha256hash": "d44add7cec95a54f36e0b48fd3f509fda6852ff25127d58574a9ee31a9885864" + "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/upstream-16374305472/lsfg-vk_archlinux.zip", + "sha256hash": "fc0bee74bd70fc7f955fd2f1dd0da1bba057114e7b1b69920c6018ab31f320b2" } ], "pnpm": { diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index 42ac640..ed82d97 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -184,19 +184,44 @@ class ConfigurationManager: @staticmethod def generate_toml_content(config: ConfigurationData) -> str: - """Generate TOML configuration file content""" - lines = ["[global]"] + """Generate TOML configuration file content using the new game-specific format""" + lines = ["version = 1"] + lines.append("") + # Add global section with DLL path only (if specified) + if config.get("dll"): + lines.append("[global]") + lines.append(f"# specify where Lossless.dll is stored") + lines.append(f'dll = "{config["dll"]}"') + lines.append("") + + # Add game section with process name for LSFG_PROCESS approach + lines.append("[[game]]") + lines.append("# Plugin-managed game entry (uses LSFG_PROCESS=decky-lsfg-vk)") + lines.append('exe = "decky-lsfg-vk"') + lines.append("") + + # Add all configuration fields to the game section for field_name, field_def in CONFIG_SCHEMA.items(): + # Skip dll and enable fields - dll goes in global, enable is handled via multiplier + if field_name in ["dll", "enable"]: + continue + value = config[field_name] - lines.append(f"# {field_def.description}") + + # Handle enable field by setting multiplier to 1 when disabled + if field_name == "multiplier" and not config.get("enable", True): + value = 1 + lines.append(f"# LSFG disabled via plugin - multiplier set to 1") + else: + lines.append(f"# {field_def.description}") # Format value based on type if isinstance(value, bool): lines.append(f"{field_name} = {str(value).lower()}") - elif isinstance(value, str): + elif isinstance(value, str) and value: # Only add non-empty strings lines.append(f'{field_name} = "{value}"') - else: + elif isinstance(value, (int, float)) and value != 0: # Only add non-zero numbers lines.append(f"{field_name} = {value}") lines.append("") # Empty line for readability @@ -209,9 +234,11 @@ class ConfigurationManager: config = ConfigurationManager.get_defaults() try: - # Look for [global] section + # Look for both [global] and [[game]] sections lines = content.split('\n') in_global_section = False + in_game_section = False + current_game_exe = None for line in lines: line = line.strip() @@ -222,12 +249,16 @@ class ConfigurationManager: # Check for section headers if line.startswith('[') and line.endswith(']'): - section = line[1:-1].strip() - in_global_section = (section == 'global') - continue - - # Only parse lines in the global section - if not in_global_section: + if line == '[global]': + in_global_section = True + in_game_section = False + elif line == '[[game]]': + in_global_section = False + in_game_section = True + current_game_exe = None + else: + in_global_section = False + in_game_section = False continue # Parse key = value lines @@ -242,21 +273,36 @@ class ConfigurationManager: elif value.startswith("'") and value.endswith("'"): value = value[1:-1] - # Convert to appropriate type based on field definition - if key in CONFIG_SCHEMA: - field_def = CONFIG_SCHEMA[key] - try: - if field_def.field_type == ConfigFieldType.BOOLEAN: - config[key] = value.lower() in ('true', '1', 'yes', 'on') - elif field_def.field_type == ConfigFieldType.INTEGER: - config[key] = int(value) - elif field_def.field_type == ConfigFieldType.FLOAT: - config[key] = float(value) - elif field_def.field_type == ConfigFieldType.STRING: - config[key] = value - except (ValueError, TypeError): - # If conversion fails, keep default value - pass + # Handle global section (dll only) + if in_global_section and key == "dll": + config["dll"] = value + + # Handle game section + elif in_game_section: + # Track the exe for this game section + if key == "exe": + current_game_exe = value + # Only parse config for our plugin-managed game entry + elif current_game_exe == "decky-lsfg-vk" and key in CONFIG_SCHEMA: + field_def = CONFIG_SCHEMA[key] + try: + if field_def.field_type == ConfigFieldType.BOOLEAN: + config[key] = value.lower() in ('true', '1', 'yes', 'on') + elif field_def.field_type == ConfigFieldType.INTEGER: + parsed_value = int(value) + # Handle enable field via multiplier + if key == "multiplier": + config[key] = parsed_value + config["enable"] = parsed_value != 1 + else: + config[key] = parsed_value + elif field_def.field_type == ConfigFieldType.FLOAT: + config[key] = float(value) + elif field_def.field_type == ConfigFieldType.STRING: + config[key] = value + except (ValueError, TypeError): + # If conversion fails, keep default value + pass return config diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index a7b6045..9caf2ea 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -352,29 +352,43 @@ class Plugin: return False # Plugin lifecycle methods + # Launch option methods + async def get_launch_option(self) -> Dict[str, Any]: + """Get the launch option that users need to set for their games + + Returns: + Dict containing the launch option string and instructions + """ + return { + "launch_option": "LSFG_PROCESS=decky-lsfg-vk %command%", + "instructions": "Add this to your game's launch options in Steam Properties", + "explanation": "This tells lsfg-vk to use the plugin-managed configuration for this game" + } + + # Lifecycle methods async def _main(self): """ - Asyncio-compatible long-running code, executed in a task when the plugin is loaded. + Main entry point for the plugin. - This method is called by Decky Loader when the plugin starts up. - Currently just logs that the plugin has loaded successfully. + This method is called by Decky Loader when the plugin is loaded. + Any initialization code should go here. """ import decky - decky.logger.info("Lossless Scaling VK plugin loaded!") + decky.logger.info("Lossless Scaling VK plugin loaded") async def _unload(self): """ - Function called first during the unload process. + Cleanup tasks when the plugin is unloaded. This method is called by Decky Loader when the plugin is being unloaded. - Use this for cleanup that should happen when the plugin stops. + Any cleanup code should go here. """ import decky - decky.logger.info("Lossless Scaling VK plugin unloading") + decky.logger.info("Lossless Scaling VK plugin unloaded") async def _uninstall(self): """ - Function called after `_unload` during uninstall. + Cleanup tasks when the plugin is uninstalled. This method is called by Decky Loader when the plugin is being uninstalled. It automatically cleans up any lsfg-vk files that were installed. diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index 7d37f4e..5d866ef 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -66,6 +66,12 @@ export interface UpdateDownloadResult { error?: string; } +export interface LaunchOptionResult { + launch_option: string; + instructions: string; + explanation: string; +} + // API functions export const installLsfgVk = callable<[], InstallationResult>("install_lsfg_vk"); export const uninstallLsfgVk = callable<[], InstallationResult>("uninstall_lsfg_vk"); @@ -73,6 +79,7 @@ export const checkLsfgVkInstalled = callable<[], InstallationStatus>("check_lsfg export const checkLosslessScalingDll = callable<[], DllDetectionResult>("check_lossless_scaling_dll"); export const getLsfgConfig = callable<[], ConfigResult>("get_lsfg_config"); export const getConfigSchema = callable<[], ConfigSchemaResult>("get_config_schema"); +export const getLaunchOption = callable<[], LaunchOptionResult>("get_launch_option"); // Updated config function using centralized configuration export const updateLsfgConfig = callable< diff --git a/src/components/ClipboardButton.tsx b/src/components/ClipboardButton.tsx index 3760e81..cf11e6e 100644 --- a/src/components/ClipboardButton.tsx +++ b/src/components/ClipboardButton.tsx @@ -1,9 +1,26 @@ +import { useState } from "react"; import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaExternalLinkAlt } from "react-icons/fa"; +import { FaClipboard, FaCheck } from "react-icons/fa"; +import { getLaunchOption } from "../api/lsfgApi"; export function ClipboardButton() { - const handleClipboardClick = () => { - window.open("https://github.com/xXJSONDeruloXx/decky-lossless-scaling-vk/wiki/Clipboard", "_blank"); + const [copied, setCopied] = useState(false); + + const handleClipboardClick = async () => { + try { + // Get the launch option from the backend + const response = await getLaunchOption(); + const launchOption = response.launch_option; + + // Copy to clipboard + await navigator.clipboard.writeText(launchOption); + setCopied(true); + + // Reset the copied state after 2 seconds + setTimeout(() => setCopied(false), 2000); + } catch (error) { + console.error("Failed to copy launch option:", error); + } }; return ( @@ -11,10 +28,11 @@ export function ClipboardButton() {
- -
Launch Option Clipboard
+ {copied ? : } +
{copied ? "Copied!" : "Copy Launch Option"}
diff --git a/src/components/Content.tsx b/src/components/Content.tsx index b248313..613e722 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -5,9 +5,9 @@ import { useInstallationActions } from "../hooks/useInstallationActions"; import { StatusDisplay } from "./StatusDisplay"; import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; -// import { UsageInstructions } from "./UsageInstructions"; +import { UsageInstructions } from "./UsageInstructions"; import { WikiButton } from "./WikiButton"; -// import { ClipboardButton } from "./ClipboardButton"; +import { ClipboardButton } from "./ClipboardButton"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { ConfigurationData } from "../config/configSchema"; @@ -74,10 +74,10 @@ export function Content() { /> )} - {/* */} + - {/* */} + {/* Plugin Update Checker */} diff --git a/src/components/LaunchOptionInfo.tsx b/src/components/LaunchOptionInfo.tsx new file mode 100644 index 0000000..298c45a --- /dev/null +++ b/src/components/LaunchOptionInfo.tsx @@ -0,0 +1,25 @@ +import { PanelSectionRow, Field } from "@decky/ui"; + +export function LaunchOptionInfo() { + return ( + + +
For each game where you want to use lsfg-vk:
+
+ 1. Right-click the game in Steam → Properties
+ 2. Add this to Launch Options: LSFG_PROCESS=decky-lsfg-vk %command%
+ 3. Or use the "Copy Launch Option" button above +
+
+ This temporary solution allows hot-reloading while keeping you on the latest lsfg-vk version. +
+ + } + /> +
+ ); +} diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index d156f9d..32aa0ff 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -33,12 +33,32 @@ export function UsageInstructions({ config }: UsageInstructionsProps) { }} > {config.enable - ? "LSFG is enabled globally. The layer will be active for all games automatically. No launch arguments needed." - : "LSFG is disabled. Enable it above to activate frame generation for all games." + ? "LSFG is enabled. Add the launch option below to Steam games to activate frame generation." + : "LSFG is disabled. Enable it above and add the launch option to activate frame generation." }
+ +
+ Required Launch Option: +
+ LSFG_PROCESS=decky-lsfg-vk %command% +
+
+
- The configuration is stored in ~/.config/lsfg-vk/conf.toml and applies to all games globally. + Add the launch option to each game's Properties → Launch Options in Steam. The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running.
diff --git a/src/components/index.ts b/src/components/index.ts index ec4c194..ed0b803 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,5 +4,6 @@ export { InstallationButton } from "./InstallationButton"; export { ConfigurationSection } from "./ConfigurationSection"; // export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; -// export { ClipboardButton } from "./ClipboardButton"; +export { ClipboardButton } from "./ClipboardButton"; +export { LaunchOptionInfo } from "./LaunchOptionInfo"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; -- cgit v1.2.3 From f3846f88402b6216675c9c48c04ab5a30cce3062 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 12:12:55 -0400 Subject: restore clipboard wiki button --- py_modules/lsfg_vk/base_service.py | 1 + py_modules/lsfg_vk/installation.py | 23 ++++++++++++++++++++--- py_modules/lsfg_vk/plugin.py | 4 ++-- src/components/ClipboardButton.tsx | 28 +++++----------------------- src/components/ConfigurationSection.tsx | 2 +- src/components/UsageInstructions.tsx | 4 ++-- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py index a796480..b595b07 100644 --- a/py_modules/lsfg_vk/base_service.py +++ b/py_modules/lsfg_vk/base_service.py @@ -30,6 +30,7 @@ class BaseService: self.local_lib_dir = self.user_home / LOCAL_LIB self.local_share_dir = self.user_home / VULKAN_LAYER_DIR self.lsfg_script_path = self.user_home / SCRIPT_NAME + self.lsfg_launch_script_path = self.user_home / SCRIPT_NAME # ~/lsfg launch script self.config_dir = self.user_home / CONFIG_DIR self.config_file_path = self.config_dir / CONFIG_FILENAME diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index fc9ac97..d193219 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -54,6 +54,9 @@ class InstallationService(BaseService): # Create the config file self._create_config_file() + # Create the lsfg launch script + self._create_lsfg_launch_script() + self.log.info("lsfg-vk installed successfully") return {"success": True, "message": "lsfg-vk installed successfully", "error": None} @@ -122,6 +125,19 @@ class InstallationService(BaseService): 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""" + script_content = """#!/bin/bash +# lsfg-vk launch script generated by decky-lossless-scaling-vk plugin +# This script sets up the environment for lsfg-vk to work with the plugin configuration +export LSFG_PROCESS=decky-lsfg-vk +exec "$@" +""" + + # Write the script file + self._write_file(self.lsfg_launch_script_path, script_content, 0o755) + self.log.info(f"Created lsfg launch script at {self.lsfg_launch_script_path}") + def check_installation(self) -> InstallationCheckResponse: """Check if lsfg-vk is already installed @@ -168,13 +184,13 @@ class InstallationService(BaseService): """ try: removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.config_file_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_launch_script_path] for file_path in files_to_remove: if self._remove_if_exists(file_path): removed_files.append(str(file_path)) - # Also try to remove the old script file if it exists + # Also try to remove the old script file if it exists (for backward compatibility) if self._remove_if_exists(self.lsfg_script_path): removed_files.append(str(self.lsfg_script_path)) @@ -219,10 +235,11 @@ class InstallationService(BaseService): self.log.info(f" Library file: {self.lib_file}") self.log.info(f" JSON file: {self.json_file}") self.log.info(f" Config file: {self.config_file_path}") + self.log.info(f" Launch script: {self.lsfg_launch_script_path}") self.log.info(f" Old script file: {self.lsfg_script_path}") removed_files = [] - files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_script_path] + files_to_remove = [self.lib_file, self.json_file, self.config_file_path, self.lsfg_launch_script_path, self.lsfg_script_path] for file_path in files_to_remove: try: diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index 9caf2ea..101542c 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -360,9 +360,9 @@ class Plugin: Dict containing the launch option string and instructions """ return { - "launch_option": "LSFG_PROCESS=decky-lsfg-vk %command%", + "launch_option": "~/lsfg %command%", "instructions": "Add this to your game's launch options in Steam Properties", - "explanation": "This tells lsfg-vk to use the plugin-managed configuration for this game" + "explanation": "The lsfg script is created during installation and sets up the environment for the plugin" } # Lifecycle methods diff --git a/src/components/ClipboardButton.tsx b/src/components/ClipboardButton.tsx index cf11e6e..3760e81 100644 --- a/src/components/ClipboardButton.tsx +++ b/src/components/ClipboardButton.tsx @@ -1,26 +1,9 @@ -import { useState } from "react"; import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaClipboard, FaCheck } from "react-icons/fa"; -import { getLaunchOption } from "../api/lsfgApi"; +import { FaExternalLinkAlt } from "react-icons/fa"; export function ClipboardButton() { - const [copied, setCopied] = useState(false); - - const handleClipboardClick = async () => { - try { - // Get the launch option from the backend - const response = await getLaunchOption(); - const launchOption = response.launch_option; - - // Copy to clipboard - await navigator.clipboard.writeText(launchOption); - setCopied(true); - - // Reset the copied state after 2 seconds - setTimeout(() => setCopied(false), 2000); - } catch (error) { - console.error("Failed to copy launch option:", error); - } + const handleClipboardClick = () => { + window.open("https://github.com/xXJSONDeruloXx/decky-lossless-scaling-vk/wiki/Clipboard", "_blank"); }; return ( @@ -28,11 +11,10 @@ export function ClipboardButton() {
- {copied ? : } -
{copied ? "Copied!" : "Copy Launch Option"}
+ +
Launch Option Clipboard
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index f44dd4f..bfbeb98 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -48,7 +48,7 @@ export function ConfigurationSection({ Required Launch Option:
- LSFG_PROCESS=decky-lsfg-vk %command% + ~/lsfg %command%
@@ -89,7 +89,7 @@ export function UsageInstructions({ config }: UsageInstructionsProps) { marginTop: "8px" }} > - Add the launch option to each game's Properties → Launch Options in Steam. The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running. + Add the launch option to each game's Properties → Launch Options in Steam. The lsfg script is automatically created during installation and connects your games to the plugin's configuration. The configuration is stored in ~/.config/lsfg-vk/conf.toml and hot-reloads while games are running. -- cgit v1.2.3 From 420571823c13345112ff608818b47b6b02d23fb4 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 12:24:07 -0400 Subject: feat: hide depreciated enable toggle --- src/components/ConfigurationSection.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index bfbeb98..19c10ea 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -27,14 +27,14 @@ export function ConfigurationSection({ - + {/* onConfigChange('enable', value)} /> - + */} {/* Date: Fri, 18 Jul 2025 12:46:16 -0400 Subject: update descriptions and layouts --- src/components/ConfigurationSection.tsx | 8 ++++---- src/components/UsageInstructions.tsx | 29 ++++++++++++++--------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 19c10ea..8aeded7 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -67,7 +67,7 @@ export function ConfigurationSection({ onConfigChange('performance_mode', value)} /> @@ -118,7 +118,7 @@ export function ConfigurationSection({ selectedOption={config.experimental_present_mode} onChange={(value) => onConfigChange('experimental_present_mode', value.data)} rgOptions={[ - { data: "", label: "Default (FIFO)" }, + { data: "", label: "Default" }, { data: "fifo", label: "FIFO" }, { data: "vsync", label: "VSync" }, { data: "mailbox", label: "Mailbox" }, @@ -130,7 +130,7 @@ export function ConfigurationSection({ 0 ? ` (${config.experimental_fps_limit} FPS)` : ' (Off)'}`} - description="Base framerate cap for DirectX games, before frame multiplier (0 = disabled)" + description="Base framerate cap for DirectX games, before frame multiplier" value={config.experimental_fps_limit} min={0} max={60} diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx index fb754b2..8ac94d8 100644 --- a/src/components/UsageInstructions.tsx +++ b/src/components/UsageInstructions.tsx @@ -33,7 +33,7 @@ export function UsageInstructions({ config }: UsageInstructionsProps) { }} > {config.enable - ? "LSFG is enabled. Add the launch option below to Steam games to activate frame generation." + ? "Add the launch option below (or use \"Launch Option Clipboard\") to Steam games to activate frame generation." : "LSFG is disabled. Enable it above and add the launch option to activate frame generation." } @@ -42,24 +42,23 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
- Required Launch Option: -
~/lsfg %command%
- + {/*
0 ? `${config.experimental_fps_limit} FPS` : "Off"}`}
-
+
*/}
- Add the launch option to each game's Properties → Launch Options in Steam. The lsfg script is automatically created during installation and connects your games to the plugin's configuration. 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 f1a3a283d9e79ec1534045a4276a93045abceb32 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 12:47:32 -0400 Subject: bump ver --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c94df3f..8a7f9da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lossless-scaling-vk", - "version": "0.4.0", + "version": "0.5.3", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { -- cgit v1.2.3 From 2bb4544db5a506fbd27e40881f924e839308f6a0 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 13:20:40 -0400 Subject: update descriptions --- src/components/ConfigurationSection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 8aeded7..5767d08 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -113,7 +113,7 @@ export function ConfigurationSection({ onConfigChange('experimental_present_mode', value.data)} @@ -130,7 +130,7 @@ export function ConfigurationSection({ 0 ? ` (${config.experimental_fps_limit} FPS)` : ' (Off)'}`} - description="Base framerate cap for DirectX games, before frame multiplier" + description="Base framerate cap for DirectX games, before frame multiplier (requires game re-launch)" value={config.experimental_fps_limit} min={0} max={60} -- cgit v1.2.3 From bb76e4e61a608b9ce77de6f2e2bce2ce1f3839ea Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 15:09:32 -0400 Subject: update descriptions --- package.json | 2 +- src/components/ConfigurationSection.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 8a7f9da..1f71d8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lossless-scaling-vk", - "version": "0.5.3", + "version": "0.5.4", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 5767d08..dc8da89 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -48,7 +48,7 @@ export function ConfigurationSection({ Date: Fri, 18 Jul 2025 15:19:42 -0400 Subject: update keywords and license --- LICENSE | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- package.json | 7 +++-- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/LICENSE b/LICENSE index b7e00f0..0bf1806 100644 --- a/LICENSE +++ b/LICENSE @@ -1,31 +1,94 @@ BSD 3-Clause License Copyright (c) 2025, Kurt Himebauch -Original Copyright (c) 2022-2024, Steam Deck Homebrew +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +This project includes third-party components under the following licenses: + +--- + +MIT License + +Includes components from lsfg-vk licensed under the MIT License: + +Copyright (c) 2025 lsfg-vk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +BSD 3-Clause License + +Includes components originally developed by Steam Deck Homebrew licensed under the BSD 3-Clause License: +Copyright (c) 2022-2024, Steam Deck Homebrew All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/package.json b/package.json index 1f71d8f..100dfa7 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,12 @@ "keywords": [ "decky", "plugin", - "plugin-template", "steam-deck", - "deck" + "deck", + "lossless-scaling", + "frame-generation", + "lsfg-vk", + "framegen" ], "author": "Kurt Himebauch ", "license": "BSD-3-Clause", -- cgit v1.2.3 From a07266ab062ba6ed6b74859c2c9f8f891e00fd14 Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 15:25:24 -0400 Subject: update readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1abcd7f..7615e62 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # Lossless Scaling for Steam Deck -**Unofficial Community Plugin**: This Decky Loader plugin is an independent project and is **not officially supported** by the creators of Lossless Scaling or lsfg-vk. Support is provided separately via the [Decky Lossless Discord Channel](https://discord.gg/SFhFy2Sd). +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/B0B71HZTAX) + +

+ Lossless Scaling for Steam Deck Logo +

+ +> **Note:** +> This is an **unofficial community plugin**. It is independently developed and **not officially supported** by the creators of Lossless Scaling or lsfg-vk. For support, please use the [Decky Lossless Discord Channel](https://discord.gg/SFhFy2Sd). ## What is this? -- cgit v1.2.3 From 26f13d4cfd15f752cb32892fea512891c3550eef Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 15:35:40 -0400 Subject: update readme --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7615e62..dbab5bd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scali ## Installation -**Note:** This plugin is not yet available on the Decky Plugin Store, it is in an experimental state, and likely to change drastically pending a full store release. +**Note:** This plugin is currently in active development. While functional, some features may change before the official Decky Plugin Store release. 1. **Download the plugin** from the [latest release](https://github.com/xXJSONDeruloXx/decky-lossless-scaling-vk/releases) - Download the "Lossless Scaling.zip" file to your Steam Deck @@ -30,28 +30,68 @@ A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scali 1. **Purchase and install** [Lossless Scaling](https://store.steampowered.com/app/993090/Lossless_Scaling/) from Steam 2. **Open the plugin** from the Decky menu 3. **Click "Install lsfg-vk"** to automatically set up the lsfg-vk vulkan layer -4. **Configure settings** using the plugin's UI. -5. **Apply launch commands** to the game you want to use frame generation with: - - **Option 1 (Recommended)**: `~/lsfg %command%` - Uses your plugin configuration - - **Option 2**: Manual environment variables like `ENABLE_LSFG=1 LSFG_MULTIPLIER=2 %COMMAND%` - - See the [LSFG-VK WIKI](https://github.com/PancakeTAS/lsfg-vk/wiki/Configuring-lsfg%E2%80%90vk) for more information on each available environment variable +4. **Configure settings** using the plugin's UI - adjust FPS multiplier, flow scale, performance mode, HDR settings, and experimental features +5. **Apply launch option** to games you want to use frame generation with: + - Add `~/lsfg %command%` to your game's launch options in Steam Properties + - 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 + +**Note**: Configuration changes are applied in real-time and will take effect immediately without restarting your game. + +## Configuration Options + +The plugin provides several configuration options to optimize frame generation for your games: + +### Core Settings +- **FPS Multiplier**: Choose between 2x, 3x, or 4x frame generation +- **Flow Scale**: Adjust motion estimation quality (lower = better performance, higher = better quality) +- **Performance Mode**: Uses a lighter processing model - recommended for most games +- **HDR Mode**: Enable for games that support HDR output + +### Experimental Features +- **Present Mode Override**: Force specific Vulkan presentation modes for compatibility +- **Base FPS Limit**: Set a base framerate cap before the multiplier is applied (useful for DirectX games) + +All settings (except base FPS limit) are saved automatically and can be changed while games are running. ## Feedback and Support For per-game feedback and community support, please join the [Decky Lossless Discord Channel](https://discord.gg/SFhFy2Sd) +## Troubleshooting + +**Frame generation not working?** +- Ensure you've added `LSFG_PROCESS=decky-lsfg-vk %command%` to your game's launch options +- Check that the Lossless Scaling DLL was detected correctly in the plugin +- Try enabling Performance Mode if you're experiencing crashes +- Make sure your game is running in fullscreen mode for best results + +**Performance issues?** +- Lower the Flow Scale setting for better performance +- Enable Performance Mode (recommended for most games) +- Try reducing the FPS multiplier from 4x to 2x or 3x +- Consider using the experimental FPS limit feature for DirectX games + ## What it does The plugin: -- Extracts the lsfg-vk library to `~/.local/lib/` -- Installs the Vulkan layer configuration to `~/.local/share/vulkan/implicit_layer.d/` -- Creates an executable `lsfg` script in the home directory with configurable settings -- Provides a user-friendly interface to configure LSFG settings (enable/disable, multiplier, flow scale, HDR, immediate mode) -- Automatically updates the `lsfg` script when settings are changed -- Provides easy uninstallation by removing these files when no longer needed +- Automatically downloads and installs the latest lsfg-vk Vulkan layer to `~/.local/lib/` +- Configures the Vulkan layer in `~/.local/share/vulkan/implicit_layer.d/` +- Creates a TOML configuration file in `~/.config/lsfg-vk/conf.toml` with your settings +- Automatically detects your Lossless Scaling DLL installation +- Provides an easy-to-use interface to configure frame generation settings: + - **FPS Multiplier**: Choose 2x, 3x, or 4x frame generation + - **Flow Scale**: Adjust motion estimation quality vs performance + - **Performance Mode**: Use lighter processing for better performance + - **HDR Mode**: Enable for HDR-compatible games + - **Experimental Features**: Override present mode and set FPS limits +- **Hot-reloading**: Configuration changes apply immediately without restarting games +- Easy uninstallation that removes all installed files when no longer needed ## Credits -[PancakeTAS](https://github.com/PancakeTAS/lsfg-vk) for creating the lsfg-vk compatibility layer. - -Special thanks to Deck Wizard for the video tutorial. +- **[PancakeTAS](https://github.com/PancakeTAS/lsfg-vk)** for creating the lsfg-vk Vulkan compatibility layer +- **[Lossless Scaling](https://store.steampowered.com/app/993090/Lossless_Scaling/)** developers for the original frame generation technology +- **[Deck Wizard](https://www.youtube.com/@DeckWizard)** for the helpful video tutorial +- The **Decky Loader** team for the plugin framework +- Community contributors and testers for feedback and bug reports -- cgit v1.2.3 From 38ae635fff9cae1cb97f0ebc4439ddcb5b5bb55a Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 18 Jul 2025 15:58:55 -0400 Subject: bump version and latest lsfg-vk --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 100dfa7..686db78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lossless-scaling-vk", - "version": "0.5.4", + "version": "0.5.5", "description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer", "type": "module", "scripts": { @@ -46,8 +46,8 @@ "remote_binary": [ { "name": "lsfg-vk_archlinux.zip", - "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/upstream-16374305472/lsfg-vk_archlinux.zip", - "sha256hash": "fc0bee74bd70fc7f955fd2f1dd0da1bba057114e7b1b69920c6018ab31f320b2" + "url": "https://github.com/xXJSONDeruloXx/lsfg-vk/releases/download/upstream-16378130046/lsfg-vk_archlinux.zip", + "sha256hash": "779f2081a7b3096a116f3395a0d12f436bccacdee0a05b9dcf0e11c61f51b2ad" } ], "pnpm": { -- cgit v1.2.3