import { ButtonItem, PanelSection, PanelSectionRow, staticClasses, ToggleField, SliderField } from "@decky/ui"; import { callable, definePlugin, toaster, } from "@decky/api" import { useState, useEffect } from "react"; import { FaDownload, FaTrash } from "react-icons/fa"; import { GiPlasticDuck } from "react-icons/gi"; // Function to install lsfg-vk const installLsfgVk = callable<[], { success: boolean; error?: string; message?: string }>("install_lsfg_vk"); // Function to uninstall lsfg-vk const uninstallLsfgVk = callable<[], { success: boolean; error?: string; message?: string; removed_files?: string[] }>("uninstall_lsfg_vk"); // Function to check if lsfg-vk is installed const checkLsfgVkInstalled = callable<[], { installed: boolean; lib_exists: boolean; json_exists: boolean; script_exists: boolean; lib_path: string; json_path: string; script_path: string; error?: string }>("check_lsfg_vk_installed"); // 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; immediate_mode: boolean }; error?: string }>("get_lsfg_config"); // Function to update lsfg configuration const updateLsfgConfig = callable<[boolean, number, number, boolean, boolean], { success: boolean; message?: string; error?: string }>("update_lsfg_config"); function Content() { const [isInstalled, setIsInstalled] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [isUninstalling, setIsUninstalling] = useState(false); const [installationStatus, setInstallationStatus] = useState(""); 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); const [immediateMode, setImmediateMode] = useState(false); // Check installation status on component mount useEffect(() => { const checkInstallation = async () => { try { const status = await checkLsfgVkInstalled(); setIsInstalled(status.installed); if (status.installed) { setInstallationStatus("lsfg-vk is installed"); } else { setInstallationStatus("lsfg-vk is not installed"); } } catch (error) { setInstallationStatus("Error checking installation status"); } }; const checkDllDetection = async () => { try { const result = await checkLosslessScalingDll(); setDllDetected(result.detected); if (result.detected) { setDllDetectionStatus(`Lossless Scaling App detected (${result.source})`); } else { setDllDetectionStatus(result.message || "Lossless Scaling App not detected"); } } catch (error) { setDllDetectionStatus("Error checking Lossless Scaling App"); } }; 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); setImmediateMode(result.config.immediate_mode); console.log("Loaded lsfg config:", result.config); } else { // If script doesn't exist or can't be read, keep default values console.log("lsfg config not available, using defaults:", result.error); } } catch (error) { console.error("Error loading lsfg config:", error); } }; checkInstallation(); checkDllDetection(); // Always try to load config, regardless of installation status // This handles cases where the script exists but plugin shows as not installed loadLsfgConfig(); }, []); // Add a second useEffect to reload config when isInstalled changes // This ensures UI reflects script state when plugin detects installation useEffect(() => { if (isInstalled) { const reloadConfig = 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); setImmediateMode(result.config.immediate_mode); console.log("Reloaded lsfg config after installation detected:", result.config); } } catch (error) { console.error("Error reloading lsfg config:", error); } }; reloadConfig(); } }, [isInstalled]); const handleInstall = async () => { setIsInstalling(true); setInstallationStatus("Installing lsfg-vk..."); try { const result = await installLsfgVk(); if (result.success) { setIsInstalled(true); setInstallationStatus("lsfg-vk installed successfully!"); toaster.toast({ 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); setImmediateMode(configResult.config.immediate_mode); } } catch (error) { console.error("Error reloading config after install:", error); } } else { setInstallationStatus(`Installation failed: ${result.error}`); toaster.toast({ title: "Installation Failed", body: result.error || "Unknown error occurred" }); } } catch (error) { setInstallationStatus(`Installation failed: ${error}`); toaster.toast({ title: "Installation Failed", body: `Error: ${error}` }); } finally { setIsInstalling(false); } }; const handleUninstall = async () => { setIsUninstalling(true); setInstallationStatus("Uninstalling lsfg-vk..."); try { const result = await uninstallLsfgVk(); if (result.success) { setIsInstalled(false); setInstallationStatus("lsfg-vk uninstalled successfully!"); toaster.toast({ title: "Uninstallation Complete", body: result.message || "lsfg-vk has been uninstalled successfully" }); } else { setInstallationStatus(`Uninstallation failed: ${result.error}`); toaster.toast({ title: "Uninstallation Failed", body: result.error || "Unknown error occurred" }); } } catch (error) { setInstallationStatus(`Uninstallation failed: ${error}`); toaster.toast({ title: "Uninstallation Failed", body: `Error: ${error}` }); } finally { setIsUninstalling(false); } }; const updateConfig = async (newEnableLsfg: boolean, newMultiplier: number, newFlowScale: number, newHdr: boolean, newImmediateMode: boolean) => { try { const result = await updateLsfgConfig(newEnableLsfg, newMultiplier, newFlowScale, newHdr, newImmediateMode); if (!result.success) { toaster.toast({ title: "Update Failed", body: result.error || "Failed to update configuration" }); } // Only show error notifications, not success notifications to avoid spam } catch (error) { toaster.toast({ title: "Update Failed", body: `Error: ${error}` }); } }; const handleEnableLsfgChange = async (value: boolean) => { setEnableLsfg(value); await updateConfig(value, multiplier, flowScale, hdr, immediateMode); }; const handleMultiplierChange = async (value: number) => { setMultiplier(value); await updateConfig(enableLsfg, value, flowScale, hdr, immediateMode); }; const handleFlowScaleChange = async (value: number) => { setFlowScale(value); await updateConfig(enableLsfg, multiplier, value, hdr, immediateMode); }; const handleHdrChange = async (value: boolean) => { setHdr(value); await updateConfig(enableLsfg, multiplier, flowScale, value, immediateMode); }; const handleImmediateModeChange = async (value: boolean) => { setImmediateMode(value); await updateConfig(enableLsfg, multiplier, flowScale, hdr, value); }; return (
{dllDetectionStatus}
Status: {installationStatus}
{isInstalling ? (
Installing...
) : isUninstalling ? (
Uninstalling...
) : isInstalled ? (
Uninstall lsfg-vk
) : (
Install lsfg-vk
)}
{/* Configuration Section - only show if installed */} {isInstalled && ( <>
LSFG Configuration
)}
Usage Instructions:
Option 1: Use the lsfg script (recommended):
~/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.25-1.0 - Flow scale (for performance)
• LSFG_HDR=1 - HDR mode (only if using HDR)
• MESA_VK_WSI_PRESENT_MODE=immediate - Disable vsync
); }; export default definePlugin(() => { console.log("Lossless Scaling plugin initializing") return { // The name shown in various decky menus name: "Lossless Scaling", // The element displayed at the top of your plugin's menu titleView:
Lossless Scaling
, // The content of your plugin's menu content: , // The icon displayed in the plugin list icon: , // The function triggered when your plugin unloads onDismount() { console.log("Lossless Scaling unloading") }, }; });