summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--justfile2
-rw-r--r--package.json4
-rw-r--r--py_modules/lsfg_vk/config_schema.py100
-rw-r--r--py_modules/lsfg_vk/plugin.py30
-rw-r--r--src/api/lsfgApi.ts7
-rw-r--r--src/components/ClipboardButton.tsx28
-rw-r--r--src/components/Content.tsx8
-rw-r--r--src/components/LaunchOptionInfo.tsx25
-rw-r--r--src/components/UsageInstructions.tsx26
-rw-r--r--src/components/index.ts3
10 files changed, 182 insertions, 51 deletions
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() {
<ButtonItem
layout="below"
onClick={handleClipboardClick}
+ description="Copy the launch option needed for Steam games"
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
- <FaExternalLinkAlt />
- <div>Launch Option Clipboard</div>
+ {copied ? <FaCheck style={{ color: "green" }} /> : <FaClipboard />}
+ <div>{copied ? "Copied!" : "Copy Launch Option"}</div>
</div>
</ButtonItem>
</PanelSectionRow>
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() {
/>
)}
- {/* <UsageInstructions config={config} /> */}
+ <UsageInstructions config={config} />
<WikiButton />
- {/* <ClipboardButton /> */}
+ <ClipboardButton />
{/* Plugin Update Checker */}
<PluginUpdateChecker />
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 (
+ <PanelSectionRow>
+ <Field
+ bottomSeparator="none"
+ label="Setup Instructions"
+ description={
+ <>
+ <div>For each game where you want to use lsfg-vk:</div>
+ <div style={{ marginTop: "8px" }}>
+ 1. Right-click the game in Steam → Properties<br/>
+ 2. Add this to Launch Options: <code>LSFG_PROCESS=decky-lsfg-vk %command%</code><br/>
+ 3. Or use the "Copy Launch Option" button above
+ </div>
+ <div style={{ marginTop: "8px", fontStyle: "italic" }}>
+ This temporary solution allows hot-reloading while keeping you on the latest lsfg-vk version.
+ </div>
+ </>
+ }
+ />
+ </PanelSectionRow>
+ );
+}
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,8 +33,8 @@ 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."
}
</div>
</PanelSectionRow>
@@ -45,6 +45,26 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
fontSize: "12px",
lineHeight: "1.4",
opacity: "0.8",
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
+ padding: "8px",
+ borderRadius: "4px",
+ fontFamily: "monospace",
+ marginTop: "8px",
+ marginBottom: "8px"
+ }}
+ >
+ Required Launch Option:
+ <br />
+ <strong>LSFG_PROCESS=decky-lsfg-vk %command%</strong>
+ </div>
+ </PanelSectionRow>
+
+ <PanelSectionRow>
+ <div
+ style={{
+ fontSize: "12px",
+ lineHeight: "1.4",
+ opacity: "0.8",
whiteSpace: "pre-wrap"
}}
>
@@ -69,7 +89,7 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
marginTop: "8px"
}}
>
- 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.
</div>
</PanelSectionRow>
</>
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";