From e8927d3bc54b63d402c3f083ba9c1f5546a4eb8b Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Fri, 11 Jul 2025 20:20:19 -0400 Subject: feat: toggle settings in the plugin ui --- README.md | 12 +++- main.py | 134 +++++++++++++++++++++++++++++++++++++++++++ src/index.tsx | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 318 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 720ed73..d0edb9c 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,23 @@ This plugin automates the installation of lsfg-vk, a compatibility layer that al 2. **Purchase and install** [Lossless Scaling](https://store.steampowered.com/app/993090/Lossless_Scaling/) from Steam 3. **Open the plugin** from the Decky menu 4. **Click "Install lsfg-vk"** to automatically set up the compatibility layer -5. **Apply launch commands** to the game you want to use frame generation with. The plugin provides a short guide for these commands in its interface. +5. **Configure settings** using the plugin's UI controls: + - Enable/disable LSFG + - Set FPS multiplier (2-4) + - Adjust flow scale (0.25-1.0) + - Toggle HDR mode +6. **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%` ## 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) +- Automatically updates the `lsfg` script when settings are changed - Provides easy uninstallation by removing these files when no longer needed ## Credits diff --git a/main.py b/main.py index 8777cd4..346d1d3 100644 --- a/main.py +++ b/main.py @@ -55,6 +55,23 @@ class Plugin: # temp_dir will be automatically cleaned up + # Create the lsfg script in home directory + lsfg_script_path = os.path.join(user_home, "lsfg") + script_content = """#!/bin/bash + +export ENABLE_LSFG=1 +export LSFG_MULTIPLIER=2 +export LSFG_FLOW_SCALE=1.0 +# export LSFG_HDR=1 +""" + + with open(lsfg_script_path, 'w') as script_file: + script_file.write(script_content) + + # Make the script executable + os.chmod(lsfg_script_path, 0o755) + decky.logger.info(f"Created executable lsfg script at {lsfg_script_path}") + decky.logger.info("lsfg-vk installed successfully") return {"success": True, "message": "lsfg-vk installed successfully"} @@ -90,6 +107,7 @@ class Plugin: user_home = os.path.expanduser("~") lib_file = os.path.join(user_home, ".local", "lib", "liblsfg-vk.so") json_file = os.path.join(user_home, ".local", "share", "vulkan", "implicit_layer.d", "VkLayer_LS_frame_generation.json") + lsfg_script = os.path.join(user_home, "lsfg") removed_files = [] @@ -105,6 +123,12 @@ class Plugin: removed_files.append(json_file) decky.logger.info(f"Removed {json_file}") + # Remove lsfg script if it exists + if os.path.exists(lsfg_script): + os.remove(lsfg_script) + removed_files.append(lsfg_script) + decky.logger.info(f"Removed {lsfg_script}") + if not removed_files: return {"success": True, "message": "No lsfg-vk files found to remove"} @@ -172,6 +196,102 @@ class Plugin: "error": str(e) } + async def get_lsfg_config(self) -> dict: + """Read current lsfg script configuration""" + try: + user_home = os.path.expanduser("~") + lsfg_script_path = os.path.join(user_home, "lsfg") + + if not os.path.exists(lsfg_script_path): + return { + "success": False, + "error": "lsfg script not found" + } + + with open(lsfg_script_path, 'r') as f: + content = f.read() + + # Parse the script content to extract current values + config = { + "enable_lsfg": False, + "multiplier": 2, + "flow_scale": 1.0, + "hdr": False + } + + lines = content.split('\n') + for line in lines: + line = line.strip() + if line.startswith('export ENABLE_LSFG='): + config["enable_lsfg"] = line.split('=')[1] == '1' + elif line.startswith('export LSFG_MULTIPLIER='): + try: + config["multiplier"] = int(line.split('=')[1]) + except: + config["multiplier"] = 2 + elif line.startswith('export LSFG_FLOW_SCALE='): + try: + config["flow_scale"] = float(line.split('=')[1]) + except: + config["flow_scale"] = 1.0 + elif line.startswith('export LSFG_HDR='): + config["hdr"] = line.split('=')[1] == '1' + + return { + "success": True, + "config": config + } + + except Exception as e: + decky.logger.error(f"Error reading lsfg config: {str(e)}") + return { + "success": False, + "error": str(e) + } + + async def update_lsfg_config(self, enable_lsfg: bool, multiplier: int, flow_scale: float, hdr: bool) -> dict: + """Update lsfg script configuration""" + try: + user_home = os.path.expanduser("~") + lsfg_script_path = os.path.join(user_home, "lsfg") + + # Create script content based on parameters + script_content = "#!/bin/bash\n\n" + + if enable_lsfg: + script_content += "export ENABLE_LSFG=1\n" + else: + script_content += "# export ENABLE_LSFG=1\n" + + script_content += f"export LSFG_MULTIPLIER={multiplier}\n" + script_content += f"export LSFG_FLOW_SCALE={flow_scale}\n" + + if hdr: + script_content += "export LSFG_HDR=1\n" + else: + script_content += "# export LSFG_HDR=1\n" + + # Write the updated script + with open(lsfg_script_path, 'w') as f: + f.write(script_content) + + # Make sure it's executable + os.chmod(lsfg_script_path, 0o755) + + decky.logger.info(f"Updated lsfg script configuration: enable={enable_lsfg}, multiplier={multiplier}, flow_scale={flow_scale}, hdr={hdr}") + + return { + "success": True, + "message": "lsfg configuration updated successfully" + } + + except Exception as e: + decky.logger.error(f"Error updating lsfg config: {str(e)}") + return { + "success": False, + "error": str(e) + } + # Asyncio-compatible long-running code, executed in a task when the plugin is loaded async def _main(self): decky.logger.info("Lossless Scaling loaded!") @@ -192,10 +312,12 @@ class Plugin: user_home = os.path.expanduser("~") lib_file = os.path.join(user_home, ".local", "lib", "liblsfg-vk.so") json_file = os.path.join(user_home, ".local", "share", "vulkan", "implicit_layer.d", "VkLayer_LS_frame_generation.json") + lsfg_script = os.path.join(user_home, "lsfg") decky.logger.info(f"Checking for lsfg-vk files to clean up:") decky.logger.info(f" Library file: {lib_file}") decky.logger.info(f" JSON file: {json_file}") + decky.logger.info(f" lsfg script: {lsfg_script}") removed_files = [] @@ -223,6 +345,18 @@ class Plugin: else: decky.logger.info(f"JSON file not found: {json_file}") + # Remove lsfg script if it exists + if os.path.exists(lsfg_script): + decky.logger.info(f"Found lsfg script, attempting to remove: {lsfg_script}") + try: + os.remove(lsfg_script) + removed_files.append(lsfg_script) + decky.logger.info(f"Successfully removed {lsfg_script}") + except Exception as e: + decky.logger.error(f"Failed to remove {lsfg_script}: {str(e)}") + else: + decky.logger.info(f"lsfg script not found: {lsfg_script}") + if removed_files: decky.logger.info(f"Cleaned up {len(removed_files)} lsfg-vk files during plugin uninstall: {removed_files}") else: diff --git a/src/index.tsx b/src/index.tsx index 8465426..bddedf9 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,9 @@ import { ButtonItem, PanelSection, PanelSectionRow, - staticClasses + staticClasses, + ToggleField, + SliderField } from "@decky/ui"; import { callable, @@ -25,6 +27,12 @@ const checkLsfgVkInstalled = callable<[], { installed: boolean; lib_exists: bool // Function to check if Lossless Scaling DLL is available const checkLosslessScalingDll = callable<[], { detected: boolean; path?: string; source?: string; message?: string; error?: string }>("check_lossless_scaling_dll"); +// Function to get lsfg configuration +const getLsfgConfig = callable<[], { success: boolean; config?: { enable_lsfg: boolean; multiplier: number; flow_scale: number; hdr: boolean }; error?: string }>("get_lsfg_config"); + +// Function to update lsfg configuration +const updateLsfgConfig = callable<[boolean, number, number, boolean], { success: boolean; message?: string; error?: string }>("update_lsfg_config"); + function Content() { const [isInstalled, setIsInstalled] = useState(false); const [isInstalling, setIsInstalling] = useState(false); @@ -33,6 +41,12 @@ function Content() { const [dllDetected, setDllDetected] = useState(false); const [dllDetectionStatus, setDllDetectionStatus] = useState(""); + // LSFG configuration state + const [enableLsfg, setEnableLsfg] = useState(true); + const [multiplier, setMultiplier] = useState(2); + const [flowScale, setFlowScale] = useState(1.0); + const [hdr, setHdr] = useState(false); + // Check installation status on component mount useEffect(() => { const checkInstallation = async () => { @@ -63,11 +77,24 @@ function Content() { } }; + const loadLsfgConfig = async () => { + try { + const result = await getLsfgConfig(); + if (result.success && result.config) { + setEnableLsfg(result.config.enable_lsfg); + setMultiplier(result.config.multiplier); + setFlowScale(result.config.flow_scale); + setHdr(result.config.hdr); + } + } catch (error) { + console.error("Error loading lsfg config:", error); + } + }; + checkInstallation(); checkDllDetection(); - }, []); - - const handleInstall = async () => { + loadLsfgConfig(); + }, []); const handleInstall = async () => { setIsInstalling(true); setInstallationStatus("Installing lsfg-vk..."); @@ -80,6 +107,19 @@ function Content() { title: "Installation Complete", body: "lsfg-vk has been installed successfully" }); + + // Reload lsfg config after installation + try { + const configResult = await getLsfgConfig(); + if (configResult.success && configResult.config) { + setEnableLsfg(configResult.config.enable_lsfg); + setMultiplier(configResult.config.multiplier); + setFlowScale(configResult.config.flow_scale); + setHdr(configResult.config.hdr); + } + } catch (error) { + console.error("Error reloading config after install:", error); + } } else { setInstallationStatus(`Installation failed: ${result.error}`); toaster.toast({ @@ -101,7 +141,7 @@ function Content() { const handleUninstall = async () => { setIsUninstalling(true); setInstallationStatus("Uninstalling lsfg-vk..."); - + try { const result = await uninstallLsfgVk(); if (result.success) { @@ -129,6 +169,48 @@ function Content() { } }; + const updateConfig = async (newEnableLsfg: boolean, newMultiplier: number, newFlowScale: number, newHdr: boolean) => { + try { + const result = await updateLsfgConfig(newEnableLsfg, newMultiplier, newFlowScale, newHdr); + if (result.success) { + toaster.toast({ + title: "Configuration Updated", + body: "lsfg script configuration has been updated" + }); + } else { + toaster.toast({ + title: "Update Failed", + body: result.error || "Failed to update configuration" + }); + } + } catch (error) { + toaster.toast({ + title: "Update Failed", + body: `Error: ${error}` + }); + } + }; + + const handleEnableLsfgChange = async (value: boolean) => { + setEnableLsfg(value); + await updateConfig(value, multiplier, flowScale, hdr); + }; + + const handleMultiplierChange = async (value: number) => { + setMultiplier(value); + await updateConfig(enableLsfg, value, flowScale, hdr); + }; + + const handleFlowScaleChange = async (value: number) => { + setFlowScale(value); + await updateConfig(enableLsfg, multiplier, value, hdr); + }; + + const handleHdrChange = async (value: boolean) => { + setHdr(value); + await updateConfig(enableLsfg, multiplier, flowScale, value); + }; + return ( @@ -175,6 +257,72 @@ function Content() { )} + + {/* Configuration Section - only show if installed */} + {isInstalled && ( + <> + +
+ LSFG Configuration +
+
+ + + + + + + + + + + + + + + + + + )}
- Add to your game's launch options in Steam: + Option 1: Use the lsfg script (recommended):
- ENABLE_LSFG=1 LSFG_MULTIPLIER=2 %COMMAND% + ~/lsfg && %COMMAND% +
+
+ Option 2: Manual environment variables: +
+
+ ENABLE_LSFG=1 LSFG_MULTIPLIER={multiplier} %COMMAND%
+ The lsfg script uses your current configuration settings. +
• ENABLE_LSFG=1 - Enables frame generation
• LSFG_MULTIPLIER=2-4 - FPS multiplier (start with 2)
- • LSFG_FLOW_SCALE=0.1-1.0 - Flow scale (optional, for performance) + • LSFG_FLOW_SCALE=0.25-1.0 - Flow scale (for performance) +
+ • LSFG_HDR=1 - HDR mode (only if using HDR)
-- cgit v1.2.3