diff options
| -rw-r--r-- | justfile | 2 | ||||
| -rw-r--r-- | package.json | 4 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/config_schema.py | 100 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/plugin.py | 30 | ||||
| -rw-r--r-- | src/api/lsfgApi.ts | 7 | ||||
| -rw-r--r-- | src/components/ClipboardButton.tsx | 28 | ||||
| -rw-r--r-- | src/components/Content.tsx | 8 | ||||
| -rw-r--r-- | src/components/LaunchOptionInfo.tsx | 25 | ||||
| -rw-r--r-- | src/components/UsageInstructions.tsx | 26 | ||||
| -rw-r--r-- | src/components/index.ts | 3 |
10 files changed, 182 insertions, 51 deletions
@@ -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"; |
