diff options
| -rw-r--r-- | README.md | 8 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/config_schema.py | 33 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/config_schema_generated.py | 14 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/configuration.py | 25 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/flatpak_service.py | 12 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/installation.py | 34 | ||||
| -rw-r--r-- | scripts/generate_python_boilerplate.py | 1 | ||||
| -rw-r--r-- | shared_config.py | 28 | ||||
| -rw-r--r-- | src/components/ConfigurationSection.tsx | 42 | ||||
| -rw-r--r-- | src/config/configSchema.ts | 4 | ||||
| -rw-r--r-- | src/config/generatedConfigSchema.ts | 40 | ||||
| -rw-r--r-- | tests/test_lsfg_v2_migration.py | 98 |
12 files changed, 61 insertions, 278 deletions
@@ -35,7 +35,7 @@ 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 - adjust FPS multiplier, flow scale, performance mode, FP16, automatic profile matching, and optional GPU selection +4. **Configure settings** using the plugin's UI - adjust FPS multiplier, flow scale, performance mode, FP16, and launch workarounds 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 @@ -50,9 +50,6 @@ The plugin provides several configuration options to optimize frame generation f - **Flow Scale**: Adjust motion estimation quality (lower = better performance, higher = better quality) - **Performance Mode**: Uses a lighter processing model - **Allow FP16**: Enable half-precision acceleration; disable it for older NVIDIA GPUs -- **Active In**: Optionally match a profile to executable names, Wine executables, or process names -- **Automatic Profile Matching**: Explicitly let lsfg-vk choose an `Active In` profile; otherwise the Decky-selected profile is always forced -- **GPU**: Optionally select the GPU by name, vendor/device ID, or PCI bus ID - **Disable Frame Generation**: Disable the layer for the next launch without creating an invalid 1x profile ## Feedback and Support @@ -65,7 +62,6 @@ For per-game feedback and community support, please join the [decky-lsfg-vk Disc - Ensure you've added `~/lsfg %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 -- Disable **Automatic Profile Matching** to force the profile selected in Decky, even when it has an `Active In` value - Make sure your game is running in fullscreen mode for best results **Performance issues?** @@ -88,7 +84,7 @@ The plugin: - **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 - - **FP16, Active In, GPU, and explicit automatic matching**: Use v2 profile parameters without ambiguous profile selection + - **FP16 and explicit Decky profile selection**: Use v2 profile parameters without ambiguous profile selection - **Launch workarounds**: Preserve existing DXVK, Gamescope, Zink, MangoHud, and vkBasalt launch options - **Hot-reloading**: Multiplier, flow scale, and performance-mode changes are reloaded while games run; other changes may require a swapchain recreation or restart - Easy uninstallation that removes all installed files when no longer needed diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py index f8a9fe6..184d27a 100644 --- a/py_modules/lsfg_vk/config_schema.py +++ b/py_modules/lsfg_vk/config_schema.py @@ -1,7 +1,6 @@ """lsfg-vk v2 configuration and Decky profile management.""" import json -import logging import re import sys import tomllib @@ -31,12 +30,12 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = { ) for name, definition in CONFIG_SCHEMA_DEF.items() } -GLOBAL_SECTION_FIELDS = {"dll", "allow_fp16"} +GLOBAL_SECTION_FIELDS = {"allow_fp16"} SCRIPT_ONLY_FIELDS = { name for name, definition in CONFIG_SCHEMA_DEF.items() if definition["location"] == "script" } -PROFILE_TOML_FIELDS = {"active_in", "gpu", "multiplier", "flow_scale", "performance_mode", "pacing"} +PROFILE_TOML_FIELDS = {"multiplier", "flow_scale", "performance_mode", "pacing"} DEFAULT_PROFILE_NAME = "decky-lsfg-vk" CURRENT_PROFILE_COMMENT = re.compile(r'^\s*#\s*decky-current-profile\s*=\s*"([^"]+)"\s*$') @@ -59,18 +58,6 @@ class ConfigurationManager: return cast(ConfigurationData, dict(get_defaults())) @staticmethod - def get_defaults_with_dll_detection(dll_detection_service=None) -> ConfigurationData: - defaults = ConfigurationManager.get_defaults() - if dll_detection_service: - try: - result = dll_detection_service.check_lossless_scaling_dll() - if result.get("detected") and result.get("path"): - defaults["dll"] = result["path"] - except (OSError, IOError, KeyError, TypeError) as error: - logging.getLogger(__name__).debug("DLL detection failed: %s", error) - return defaults - - @staticmethod def get_field_names() -> list[str]: return list(CONFIG_SCHEMA) @@ -108,8 +95,6 @@ class ConfigurationManager: if field not in profile: continue value = profile[field] - if field == "active_in" and isinstance(value, list): - value = ", ".join(str(item) for item in value) config[field] = value for field in GLOBAL_SECTION_FIELDS: if field in global_config: @@ -134,20 +119,11 @@ class ConfigurationManager: "", "[global]", ] - if global_config.get("dll"): - lines.append(f"dll = {_toml_string(str(global_config['dll']))}") lines.append(f"allow_fp16 = {str(bool(global_config.get('allow_fp16', True))).lower()}") for profile_name, raw_config in profile_data["profiles"].items(): config = ConfigurationManager.validate_config({**raw_config, **global_config}) lines.extend(["", "[[profile]]", f"name = {_toml_string(profile_name)}"]) - active_in = [entry.strip() for entry in config["active_in"].split(",") if entry.strip()] - if len(active_in) == 1: - lines.append(f"active_in = {_toml_string(active_in[0])}") - elif active_in: - lines.append("active_in = [" + ", ".join(_toml_string(entry) for entry in active_in) + "]") - if config["gpu"]: - lines.append(f"gpu = {_toml_string(config['gpu'])}") lines.extend([ f"multiplier = {config['multiplier']}", f"flow_scale = {config['flow_scale']}", @@ -160,7 +136,6 @@ class ConfigurationManager: def _profile_data_from_v1(data: Dict[str, Any]) -> ProfileData: old_global = data.get("global", {}) global_config = { - "dll": str(old_global.get("dll", "")), "allow_fp16": not bool(old_global.get("no_fp16", False)), } profiles: Dict[str, ConfigurationData] = {} @@ -199,7 +174,6 @@ class ConfigurationManager: raw_global = dict(data.get("global", {})) global_config = { - "dll": str(raw_global.get("dll", "")), "allow_fp16": bool(raw_global.get("allow_fp16", True)), } profiles: Dict[str, ConfigurationData] = {} @@ -260,9 +234,6 @@ class ConfigurationManager: raise ValueError(f"Profile '{normalized}' already exists") source = source_profile if source_profile in profile_data["profiles"] else profile_data["current_profile"] new_profile = dict(profile_data["profiles"][source]) - # A cloned profile may inherit Active In values that collide with its source. - # Force the newly selected profile until the user explicitly enables native matching. - new_profile["use_native_matching"] = False return ProfileData( current_profile=profile_data["current_profile"], profiles={**profile_data["profiles"], normalized: new_profile}, diff --git a/py_modules/lsfg_vk/config_schema_generated.py b/py_modules/lsfg_vk/config_schema_generated.py index 9cfda51..db337fe 100644 --- a/py_modules/lsfg_vk/config_schema_generated.py +++ b/py_modules/lsfg_vk/config_schema_generated.py @@ -13,15 +13,11 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from shared_config import CONFIG_SCHEMA_DEF, ConfigFieldType # Field name constants for type-safe access -DLL = "dll" ALLOW_FP16 = "allow_fp16" MULTIPLIER = "multiplier" FLOW_SCALE = "flow_scale" PERFORMANCE_MODE = "performance_mode" PACING = "pacing" -ACTIVE_IN = "active_in" -USE_NATIVE_MATCHING = "use_native_matching" -GPU = "gpu" DISABLE_LSFGVK = "disable_lsfgvk" DXVK_FRAME_RATE = "dxvk_frame_rate" ENABLE_WOW64 = "enable_wow64" @@ -35,15 +31,11 @@ ENABLE_ZINK = "enable_zink" class ConfigurationData(TypedDict): """Type-safe configuration data structure - AUTO-GENERATED""" - dll: str allow_fp16: bool multiplier: int flow_scale: float performance_mode: bool pacing: str - active_in: str - use_native_matching: bool - gpu: str disable_lsfgvk: bool dxvk_frame_rate: int enable_wow64: bool @@ -70,8 +62,6 @@ def get_script_parsing_logic(): value = value.strip() # Auto-generated parsing logic: - if key == "DECKY_LSFGVK_AUTO_PROFILE": - script_values["use_native_matching"] = value == "1" if key == "DISABLE_LSFGVK": script_values["disable_lsfgvk"] = value == "1" if key == "DXVK_FRAME_RATE": @@ -106,8 +96,6 @@ def get_script_generation_logic(): """Return the script generation logic as a callable""" def generate_script_lines(config): lines = [] - if config.get("use_native_matching", False): - lines.append("export DECKY_LSFGVK_AUTO_PROFILE=1") if config.get("disable_lsfgvk", False): lines.append("export DISABLE_LSFGVK=1") dxvk_frame_rate = config.get("dxvk_frame_rate", 0) @@ -133,4 +121,4 @@ def get_script_generation_logic(): return generate_script_lines -ALL_FIELDS = ['dll', 'allow_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'pacing', 'active_in', 'use_native_matching', 'gpu', 'disable_lsfgvk', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink'] +ALL_FIELDS = ['allow_fp16', 'multiplier', 'flow_scale', 'performance_mode', 'pacing', 'disable_lsfgvk', 'dxvk_frame_rate', 'enable_wow64', 'disable_steamdeck_mode', 'mangohud_workaround', 'disable_vkbasalt', 'force_enable_vkbasalt', 'enable_wsi', 'enable_zink'] diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py index 6511745..490689c 100644 --- a/py_modules/lsfg_vk/configuration.py +++ b/py_modules/lsfg_vk/configuration.py @@ -17,10 +17,8 @@ class ConfigurationService(BaseService): """Service for managing TOML-based lsfg configuration""" @staticmethod - def _profile_selection_lines(profile_name: str, config: ConfigurationData) -> list[str]: - """Force the selected profile unless the user explicitly opts into native matching.""" - if config.get("use_native_matching", False): - return ["# lsfg-vk will select a matching profile from Active In."] + def _profile_selection_lines(profile_name: str) -> list[str]: + """Force the profile selected in Decky for every launch.""" return [f"export LSFGVK_PROFILE={shlex.quote(profile_name)}"] def get_config(self) -> ConfigurationResponse: @@ -31,9 +29,7 @@ class ConfigurationService(BaseService): """ try: if not self.config_file_path.exists(): - from .dll_detection import DllDetectionService - dll_service = DllDetectionService(self.log) - toml_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + toml_config = ConfigurationManager.get_defaults() else: content = self.config_file_path.read_text(encoding='utf-8') toml_config = ConfigurationManager.parse_toml_content(content) @@ -58,9 +54,7 @@ class ConfigurationService(BaseService): except Exception as e: error_msg = f"Error parsing config file: {str(e)}" self.log.error(error_msg) - from .dll_detection import DllDetectionService - dll_service = DllDetectionService(self.log) - config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + config = ConfigurationManager.get_defaults() return self._success_response(ConfigurationResponse, f"Using default configuration due to parse error: {str(e)}", config=config) @@ -134,7 +128,7 @@ class ConfigurationService(BaseService): lines.extend([ f"export LSFGVK_CONFIG={shlex.quote(str(self.config_file_path))}", - *self._profile_selection_lines(DEFAULT_PROFILE_NAME, config), + *self._profile_selection_lines(DEFAULT_PROFILE_NAME), ]) lines.extend(self._generate_game_launch_lines()) @@ -166,7 +160,7 @@ class ConfigurationService(BaseService): lines.extend([ f"export LSFGVK_CONFIG={shlex.quote(str(self.config_file_path))}", - *self._profile_selection_lines(current_profile, cast(ConfigurationData, merged_config)), + *self._profile_selection_lines(current_profile), ]) lines.extend(self._generate_game_launch_lines()) @@ -193,14 +187,11 @@ class ConfigurationService(BaseService): def _get_profile_data(self) -> ProfileData: """Get current profile data from config file""" if not self.config_file_path.exists(): - from .dll_detection import DllDetectionService - dll_service = DllDetectionService(self.log) - default_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + default_config = ConfigurationManager.get_defaults() return ProfileData( current_profile=DEFAULT_PROFILE_NAME, profiles={DEFAULT_PROFILE_NAME: default_config}, global_config={ - "dll": default_config.get("dll", ""), "allow_fp16": default_config.get("allow_fp16", True) } ) @@ -416,7 +407,7 @@ class ConfigurationService(BaseService): profile_data["profiles"][profile_name] = config # Update global config fields if they're in the config - for field_name in ["dll", "allow_fp16"]: + for field_name in ["allow_fp16"]: if field_name in config: profile_data["global_config"][field_name] = config[field_name] diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py index 8410496..da01d8b 100644 --- a/py_modules/lsfg_vk/flatpak_service.py +++ b/py_modules/lsfg_vk/flatpak_service.py @@ -8,7 +8,6 @@ from pathlib import Path from typing import Dict, Any, List, Optional from .base_service import BaseService -from .config_schema import ConfigurationManager from .constants import ( BIN_DIR, FLATPAK_23_08_FILENAME, @@ -63,17 +62,6 @@ class FlatpakService(BaseService): """Return the v2 config directory and directory containing Lossless.dll.""" config_path = str(self.config_dir) dll_directory = str(self.user_home / ".local/share/Steam/steamapps/common") - if not self.config_file_path.exists(): - return config_path, dll_directory - try: - profile_data = ConfigurationManager.parse_toml_content_multi_profile( - self.config_file_path.read_text(encoding="utf-8") - ) - configured_dll = profile_data["global_config"].get("dll", "") - if configured_dll: - dll_directory = str(Path(str(configured_dll)).parent) - except (OSError, ValueError, KeyError, TypeError) as error: - self.log.debug("Could not read configured DLL path for Flatpak override: %s", error) return config_path, dll_directory def _get_bundled_extension_path(self, version: str) -> Path: diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py index 1ba091a..899804b 100644 --- a/py_modules/lsfg_vk/installation.py +++ b/py_modules/lsfg_vk/installation.py @@ -183,9 +183,6 @@ class InstallationService(BaseService): def _create_config_file(self) -> ProfileData: """Migrate v1 once, normalize v2, and retain Decky launch workarounds.""" - from .dll_detection import DllDetectionService - - dll_service = DllDetectionService(self.log) profile_data: ProfileData was_legacy = False self._config_recovery_backup = None @@ -201,23 +198,23 @@ class InstallationService(BaseService): self._config_recovery_backup, error, ) - default = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + default = ConfigurationManager.get_defaults() profile_data = ProfileData( current_profile="decky-lsfg-vk", profiles={"decky-lsfg-vk": default}, - global_config={"dll": default["dll"], "allow_fp16": default["allow_fp16"]}, + global_config={"allow_fp16": default["allow_fp16"]}, ) if was_legacy: self._backup_legacy_config(content) else: - default = ConfigurationManager.get_defaults_with_dll_detection(dll_service) + default = ConfigurationManager.get_defaults() profile_data = ProfileData( current_profile="decky-lsfg-vk", profiles={"decky-lsfg-vk": default}, - global_config={"dll": default["dll"], "allow_fp16": default["allow_fp16"]}, + global_config={"allow_fp16": default["allow_fp16"]}, ) - profile_data = self._merge_config_with_defaults(profile_data, dll_service) + profile_data = self._merge_config_with_defaults(profile_data) script_values = self._read_script_values() current_profile = profile_data["current_profile"] profile_data["profiles"][current_profile] = ConfigurationManager.merge_config_with_script( @@ -267,21 +264,12 @@ class InstallationService(BaseService): script = configuration_service._generate_script_content_for_profile(profile_data) self._write_file(self.lsfg_launch_script_path, script, 0o755) - def _merge_config_with_defaults(self, existing: ProfileData, dll_service) -> ProfileData: - defaults = ConfigurationManager.get_defaults_with_dll_detection(dll_service) - global_config = dict(existing.get("global_config", {})) - configured_dll = str(global_config.get("dll", "") or "").strip() - detected_dll = str(defaults.get("dll", "") or "").strip() - if configured_dll and Path(configured_dll).is_file(): - global_config["dll"] = configured_dll - elif detected_dll and Path(detected_dll).is_file(): - self.log.warning("Replacing stale configured Lossless.dll path %s with %s", configured_dll, detected_dll) - global_config["dll"] = detected_dll - else: - if configured_dll: - self.log.warning("Clearing stale configured Lossless.dll path %s", configured_dll) - global_config["dll"] = "" - global_config.setdefault("allow_fp16", defaults["allow_fp16"]) + def _merge_config_with_defaults(self, existing: ProfileData) -> ProfileData: + defaults = ConfigurationManager.get_defaults() + existing_global = existing.get("global_config", {}) + global_config = { + "allow_fp16": existing_global.get("allow_fp16", defaults["allow_fp16"]), + } profiles: Dict[str, Any] = {} for name, existing_config in existing.get("profiles", {}).items(): diff --git a/scripts/generate_python_boilerplate.py b/scripts/generate_python_boilerplate.py index 5027023..1445b79 100644 --- a/scripts/generate_python_boilerplate.py +++ b/scripts/generate_python_boilerplate.py @@ -35,7 +35,6 @@ def get_env_var_name(field_name: str) -> str: "disable_steamdeck_mode": "SteamDeck", "mangohud_workaround": "MANGOHUD", "disable_lsfgvk": "DISABLE_LSFGVK", - "use_native_matching": "DECKY_LSFGVK_AUTO_PROFILE", "disable_vkbasalt": "DISABLE_VKBASALT", "force_enable_vkbasalt": "ENABLE_VKBASALT", "enable_wsi": "ENABLE_GAMESCOPE_WSI", diff --git a/shared_config.py b/shared_config.py index 77b4c01..f89272f 100644 --- a/shared_config.py +++ b/shared_config.py @@ -12,13 +12,6 @@ class ConfigFieldType(str, Enum): CONFIG_SCHEMA_DEF = { - "dll": { - "name": "dll", - "fieldType": ConfigFieldType.STRING, - "default": "", - "description": "optional full path to Lossless.dll; leave blank for automatic discovery", - "location": "global", - }, "allow_fp16": { "name": "allow_fp16", "fieldType": ConfigFieldType.BOOLEAN, @@ -54,27 +47,6 @@ CONFIG_SCHEMA_DEF = { "description": "frame pacing mode (currently only none is supported)", "location": "profile", }, - "active_in": { - "name": "active_in", - "fieldType": ConfigFieldType.STRING, - "default": "", - "description": "optional executable or process names, separated by commas", - "location": "profile", - }, - "use_native_matching": { - "name": "use_native_matching", - "fieldType": ConfigFieldType.BOOLEAN, - "default": False, - "description": "let lsfg-vk choose a profile from Active In instead of forcing the selected profile", - "location": "script", - }, - "gpu": { - "name": "gpu", - "fieldType": ConfigFieldType.STRING, - "default": "", - "description": "optional GPU name, vendor:device ID, or PCI bus ID", - "location": "profile", - }, "disable_lsfgvk": { "name": "disable_lsfgvk", "fieldType": ConfigFieldType.BOOLEAN, diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx index 538975e..1ab570b 100644 --- a/src/components/ConfigurationSection.tsx +++ b/src/components/ConfigurationSection.tsx @@ -1,10 +1,10 @@ -import { PanelSectionRow, ToggleField, SliderField, ButtonItem, TextField } from "@decky/ui"; +import { PanelSectionRow, ToggleField, SliderField, ButtonItem } from "@decky/ui"; import { useState, useEffect } from "react"; import { RiArrowDownSFill, RiArrowUpSFill } from "react-icons/ri"; import { ConfigurationData } from "../config/configSchema"; import { - ACTIVE_IN, ALLOW_FP16, DISABLE_LSFGVK, DLL, FLOW_SCALE, GPU, - PERFORMANCE_MODE, USE_NATIVE_MATCHING, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE, + ALLOW_FP16, DISABLE_LSFGVK, FLOW_SCALE, + PERFORMANCE_MODE, DXVK_FRAME_RATE, DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK } from "../config/generatedConfigSchema"; @@ -114,15 +114,6 @@ export function ConfigurationSection({ {!configCollapsed && ( <> <PanelSectionRow> - <TextField - label="Lossless.dll Path" - description="Optional full path to Lossless.dll. Leave blank for lsfg-vk automatic discovery." - value={config.dll} - onChange={(event) => onConfigChange(DLL, event.currentTarget.value)} - /> - </PanelSectionRow> - - <PanelSectionRow> <SliderField label={`Flow Scale (${Math.round(config.flow_scale * 100)}%)`} description="Lowers internal motion estimation resolution, improving performance slightly" @@ -153,33 +144,6 @@ export function ConfigurationSection({ </PanelSectionRow> <PanelSectionRow> - <TextField - label="GPU" - description="Optional GPU name, vendor:device ID, or PCI bus ID." - value={config.gpu} - onChange={(event) => onConfigChange(GPU, event.currentTarget.value)} - /> - </PanelSectionRow> - - <PanelSectionRow> - <TextField - label="Active In" - description="Executable/process names separated by commas." - value={config.active_in} - onChange={(event) => onConfigChange(ACTIVE_IN, event.currentTarget.value)} - /> - </PanelSectionRow> - - <PanelSectionRow> - <ToggleField - label="Use Automatic Profile Matching" - description="Let lsfg-vk choose a matching Active In profile instead of forcing the profile selected in Decky." - checked={config.use_native_matching} - onChange={(value) => onConfigChange(USE_NATIVE_MATCHING, value)} - /> - </PanelSectionRow> - - <PanelSectionRow> <SliderField label={`Base FPS Cap${config.dxvk_frame_rate > 0 ? ` (${config.dxvk_frame_rate} FPS)` : " (Off)"}`} description="Base framerate cap for DirectX games, before frame multiplier. (Requires game restart to apply)" diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts index 7b608c3..2054758 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -6,8 +6,8 @@ export { getFieldNames, getDefaults, getFieldTypes, - DLL, ALLOW_FP16, MULTIPLIER, FLOW_SCALE, PERFORMANCE_MODE, PACING, - ACTIVE_IN, USE_NATIVE_MATCHING, GPU, DISABLE_LSFGVK, DXVK_FRAME_RATE, ENABLE_WOW64, + ALLOW_FP16, MULTIPLIER, FLOW_SCALE, PERFORMANCE_MODE, PACING, + DISABLE_LSFGVK, DXVK_FRAME_RATE, ENABLE_WOW64, DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT, FORCE_ENABLE_VKBASALT, ENABLE_WSI, ENABLE_ZINK } from './generatedConfigSchema'; diff --git a/src/config/generatedConfigSchema.ts b/src/config/generatedConfigSchema.ts index 5c5df39..7c0c8b0 100644 --- a/src/config/generatedConfigSchema.ts +++ b/src/config/generatedConfigSchema.ts @@ -8,15 +8,11 @@ export enum ConfigFieldType { } // Field name constants for type-safe access -export const DLL = "dll" as const; export const ALLOW_FP16 = "allow_fp16" as const; export const MULTIPLIER = "multiplier" as const; export const FLOW_SCALE = "flow_scale" as const; export const PERFORMANCE_MODE = "performance_mode" as const; export const PACING = "pacing" as const; -export const ACTIVE_IN = "active_in" as const; -export const USE_NATIVE_MATCHING = "use_native_matching" as const; -export const GPU = "gpu" as const; export const DISABLE_LSFGVK = "disable_lsfgvk" as const; export const DXVK_FRAME_RATE = "dxvk_frame_rate" as const; export const ENABLE_WOW64 = "enable_wow64" as const; @@ -37,12 +33,6 @@ export interface ConfigField { // Configuration schema - auto-generated from Python export const CONFIG_SCHEMA: Record<string, ConfigField> = { - dll: { - name: "dll", - fieldType: ConfigFieldType.STRING, - default: "", - description: "optional full path to Lossless.dll; leave blank for automatic discovery" - }, allow_fp16: { name: "allow_fp16", fieldType: ConfigFieldType.BOOLEAN, @@ -73,24 +63,6 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = { default: "none", description: "frame pacing mode (currently only none is supported)" }, - active_in: { - name: "active_in", - fieldType: ConfigFieldType.STRING, - default: "", - description: "optional executable or process names, separated by commas" - }, - use_native_matching: { - name: "use_native_matching", - fieldType: ConfigFieldType.BOOLEAN, - default: false, - description: "let lsfg-vk choose a profile from Active In instead of forcing the selected profile" - }, - gpu: { - name: "gpu", - fieldType: ConfigFieldType.STRING, - default: "", - description: "optional GPU name, vendor:device ID, or PCI bus ID" - }, disable_lsfgvk: { name: "disable_lsfgvk", fieldType: ConfigFieldType.BOOLEAN, @@ -149,15 +121,11 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = { // Type-safe configuration data structure export interface ConfigurationData { - dll: string; allow_fp16: boolean; multiplier: number; flow_scale: number; performance_mode: boolean; pacing: string; - active_in: string; - use_native_matching: boolean; - gpu: string; disable_lsfgvk: boolean; dxvk_frame_rate: number; enable_wow64: boolean; @@ -176,15 +144,11 @@ export function getFieldNames(): string[] { export function getDefaults(): ConfigurationData { return { - dll: "", allow_fp16: true, multiplier: 2, flow_scale: 0.9, performance_mode: false, pacing: "none", - active_in: "", - use_native_matching: false, - gpu: "", disable_lsfgvk: false, dxvk_frame_rate: 0, enable_wow64: false, @@ -199,15 +163,11 @@ export function getDefaults(): ConfigurationData { export function getFieldTypes(): Record<string, ConfigFieldType> { return { - dll: ConfigFieldType.STRING, allow_fp16: ConfigFieldType.BOOLEAN, multiplier: ConfigFieldType.INTEGER, flow_scale: ConfigFieldType.FLOAT, performance_mode: ConfigFieldType.BOOLEAN, pacing: ConfigFieldType.STRING, - active_in: ConfigFieldType.STRING, - use_native_matching: ConfigFieldType.BOOLEAN, - gpu: ConfigFieldType.STRING, disable_lsfgvk: ConfigFieldType.BOOLEAN, dxvk_frame_rate: ConfigFieldType.INTEGER, enable_wow64: ConfigFieldType.BOOLEAN, diff --git a/tests/test_lsfg_v2_migration.py b/tests/test_lsfg_v2_migration.py index 8b2e2a3..242bf85 100644 --- a/tests/test_lsfg_v2_migration.py +++ b/tests/test_lsfg_v2_migration.py @@ -48,6 +48,35 @@ class ConfigurationMigrationTests(unittest.TestCase): self.assertNotIn("no_fp16", serialized) self.assertNotIn("hdr_mode", serialized) self.assertNotIn("experimental_present_mode", serialized) + self.assertNotIn("dll =", serialized) + self.assertNotIn("active_in", serialized) + self.assertNotIn("gpu", serialized) + + def test_removed_v2_controls_are_not_reintroduced_when_normalizing(self): + content = '''\ +version = 2 + +[global] +dll = "/tmp/Lossless.dll" +allow_fp16 = true + +[[profile]] +name = "decky-lsfg-vk" +active_in = "Game.exe" +gpu = "0x10DE:0x2684" +multiplier = 2 +flow_scale = 0.9 +performance_mode = false +pacing = "none" +''' + + serialized = ConfigurationManager.generate_toml_content_multi_profile( + ConfigurationManager.parse_toml_content_multi_profile(content) + ) + + self.assertNotIn("dll =", serialized) + self.assertNotIn("active_in", serialized) + self.assertNotIn("gpu", serialized) def test_v2_normalization_is_idempotent(self): migrated = ConfigurationManager.parse_toml_content_multi_profile(LEGACY_CONFIG) @@ -66,7 +95,7 @@ class ConfigurationMigrationTests(unittest.TestCase): profile_data = { "current_profile": "decky-lsfg-vk", "profiles": {"decky-lsfg-vk": ConfigurationManager.get_defaults()}, - "global_config": {"dll": "/games/Lossless Scaling/Lossless.dll", "allow_fp16": True}, + "global_config": {"allow_fp16": True}, } script = service._generate_script_content_for_profile(profile_data) @@ -74,38 +103,13 @@ class ConfigurationMigrationTests(unittest.TestCase): self.assertIn("LSFGVK_PROFILE=decky-lsfg-vk", script) self.assertNotIn("LSFG_PROCESS", script) - def test_active_in_does_not_implicitly_override_the_selected_profile(self): + def test_launch_script_always_forces_selected_profile(self): config = ConfigurationManager.get_defaults() - config["active_in"] = "Game.exe, GameThread" self.assertEqual( - ConfigurationService._profile_selection_lines("decky-lsfg-vk", config), + ConfigurationService._profile_selection_lines("decky-lsfg-vk"), ["export LSFGVK_PROFILE=decky-lsfg-vk"], ) - config["use_native_matching"] = True - self.assertEqual( - ConfigurationService._profile_selection_lines("decky-lsfg-vk", config), - ["# lsfg-vk will select a matching profile from Active In."], - ) - self.assertEqual( - ConfigurationManager.parse_script_content("export DECKY_LSFGVK_AUTO_PROFILE=1\n"), - {"use_native_matching": True}, - ) - - def test_cloned_profile_disables_native_matching_until_explicitly_enabled(self): - config = ConfigurationManager.get_defaults() - config["active_in"] = "Game.exe" - config["use_native_matching"] = True - profile_data = { - "current_profile": "decky-lsfg-vk", - "profiles": {"decky-lsfg-vk": config}, - "global_config": {"dll": "", "allow_fp16": True}, - } - - cloned = ConfigurationManager.create_profile(profile_data, "Other") - self.assertEqual(cloned["profiles"]["Other"]["active_in"], "Game.exe") - self.assertFalse(cloned["profiles"]["Other"]["use_native_matching"]) - class InstallerInfrastructureTests(unittest.TestCase): def test_layer_archive_selection_has_no_legacy_arm_override(self): @@ -192,44 +196,6 @@ class InstallerInfrastructureTests(unittest.TestCase): self.assertIn("version = 2", config_path.read_text(encoding="utf-8")) self.assertEqual(service._config_recovery_backup, config_dir / "conf.toml.unrecognized.bak") - def test_stale_dll_path_is_replaced_when_detection_finds_a_valid_file(self): - from lsfg_vk.installation import InstallationService - - with tempfile.TemporaryDirectory() as directory: - home = Path(directory) - config_dir = home / ".config" / "lsfg-vk" - config_dir.mkdir(parents=True) - config_path = config_dir / "conf.toml" - detected_dll = home / "Lossless.dll" - detected_dll.write_bytes(b"dll") - profile_data = { - "current_profile": "decky-lsfg-vk", - "profiles": {"decky-lsfg-vk": ConfigurationManager.get_defaults()}, - "global_config": {"dll": str(home / "missing" / "Lossless.dll"), "allow_fp16": True}, - } - config_path.write_text( - ConfigurationManager.generate_toml_content_multi_profile(profile_data), - encoding="utf-8", - ) - - service = InstallationService.__new__(InstallationService) - service.log = mock.Mock() - service.user_home = home - service.config_dir = config_dir - service.config_file_path = config_path - service.lsfg_script_path = home / "lsfg" - service.lsfg_launch_script_path = home / "lsfg" - - with mock.patch("lsfg_vk.dll_detection.DllDetectionService") as detection_service: - detection_service.return_value.check_lossless_scaling_dll.return_value = { - "detected": True, - "path": str(detected_dll), - } - migrated = service._create_config_file() - - self.assertEqual(migrated["global_config"]["dll"], str(detected_dll)) - self.assertIn(str(detected_dll), config_path.read_text(encoding="utf-8")) - def test_invalid_layer_manifest_fails_without_copying_unmodified_json(self): from lsfg_vk.installation import InstallationService |
