summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--main.py134
-rwxr-xr-xsrc/index.tsx181
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<boolean>(false);
const [isInstalling, setIsInstalling] = useState<boolean>(false);
@@ -33,6 +41,12 @@ function Content() {
const [dllDetected, setDllDetected] = useState<boolean>(false);
const [dllDetectionStatus, setDllDetectionStatus] = useState<string>("");
+ // LSFG configuration state
+ const [enableLsfg, setEnableLsfg] = useState<boolean>(true);
+ const [multiplier, setMultiplier] = useState<number>(2);
+ const [flowScale, setFlowScale] = useState<number>(1.0);
+ const [hdr, setHdr] = useState<boolean>(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 (
<PanelSection>
<PanelSectionRow>
@@ -175,6 +257,72 @@ function Content() {
)}
</ButtonItem>
</PanelSectionRow>
+
+ {/* Configuration Section - only show if installed */}
+ {isInstalled && (
+ <>
+ <PanelSectionRow>
+ <div style={{
+ fontSize: "14px",
+ fontWeight: "bold",
+ marginTop: "16px",
+ marginBottom: "8px",
+ borderBottom: "1px solid rgba(255, 255, 255, 0.2)",
+ paddingBottom: "4px"
+ }}>
+ LSFG Configuration
+ </div>
+ </PanelSectionRow>
+
+ <PanelSectionRow>
+ <ToggleField
+ label="Enable LSFG"
+ description="Enables the frame generation layer"
+ checked={enableLsfg}
+ onChange={handleEnableLsfgChange}
+ />
+ </PanelSectionRow>
+
+ <PanelSectionRow>
+ <SliderField
+ label="FPS Multiplier"
+ description="Traditional FPS multiplier value (2-4)"
+ value={multiplier}
+ min={2}
+ max={4}
+ step={1}
+ notchCount={3}
+ notchLabels={[
+ { notchIndex: 0, label: "2" },
+ { notchIndex: 1, label: "3" },
+ { notchIndex: 2, label: "4" }
+ ]}
+ onChange={handleMultiplierChange}
+ />
+ </PanelSectionRow>
+
+ <PanelSectionRow>
+ <SliderField
+ label="Flow Scale"
+ description="Lowers the flow scale for performance (0.25-1.0)"
+ value={flowScale}
+ min={0.25}
+ max={1.0}
+ step={0.01}
+ onChange={handleFlowScaleChange}
+ />
+ </PanelSectionRow>
+
+ <PanelSectionRow>
+ <ToggleField
+ label="HDR Mode"
+ description="Enable HDR mode (only if using HDR)"
+ checked={hdr}
+ onChange={handleHdrChange}
+ />
+ </PanelSectionRow>
+ </>
+ )}
<PanelSectionRow>
<div style={{
@@ -188,7 +336,7 @@ function Content() {
Usage Instructions:
</div>
<div style={{ marginBottom: "4px" }}>
- Add to your game's launch options in Steam:
+ Option 1: Use the lsfg script (recommended):
</div>
<div style={{
fontFamily: "monospace",
@@ -198,14 +346,31 @@ function Content() {
fontSize: "12px",
marginBottom: "6px"
}}>
- ENABLE_LSFG=1 LSFG_MULTIPLIER=2 %COMMAND%
+ ~/lsfg && %COMMAND%
+ </div>
+ <div style={{ marginBottom: "4px" }}>
+ Option 2: Manual environment variables:
+ </div>
+ <div style={{
+ fontFamily: "monospace",
+ backgroundColor: "rgba(0, 0, 0, 0.3)",
+ padding: "4px",
+ borderRadius: "2px",
+ fontSize: "12px",
+ marginBottom: "6px"
+ }}>
+ ENABLE_LSFG=1 LSFG_MULTIPLIER={multiplier} %COMMAND%
</div>
<div style={{ fontSize: "11px", opacity: 0.8 }}>
+ The lsfg script uses your current configuration settings.
+ <br />
• ENABLE_LSFG=1 - Enables frame generation
<br />
• LSFG_MULTIPLIER=2-4 - FPS multiplier (start with 2)
<br />
- • LSFG_FLOW_SCALE=0.1-1.0 - Flow scale (optional, for performance)
+ • LSFG_FLOW_SCALE=0.25-1.0 - Flow scale (for performance)
+ <br />
+ • LSFG_HDR=1 - HDR mode (only if using HDR)
</div>
</div>
</PanelSectionRow>