summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>2025-07-19 08:40:49 -0400
committerGitHub <noreply@github.com>2025-07-19 08:40:49 -0400
commitf010473bfdf6b9a58960a5dac71fa48837ae6a1c (patch)
treec0392ef13b7cc47bc27369e7840e58fa23e745b7
parentb9302548a4def670c8600086ba8685c075ceac3d (diff)
parent75a94598341899eea5260206975686c05e793956 (diff)
downloaddecky-lsfg-vk-f010473bfdf6b9a58960a5dac71fa48837ae6a1c.tar.gz
decky-lsfg-vk-f010473bfdf6b9a58960a5dac71fa48837ae6a1c.zip
Merge pull request #41 from xXJSONDeruloXx/nerd-stuffv0.6.4
Nerd stuff
-rw-r--r--package.json2
-rw-r--r--py_modules/lsfg_vk/config_schema.py34
-rw-r--r--py_modules/lsfg_vk/configuration.py7
-rw-r--r--py_modules/lsfg_vk/plugin.py67
-rw-r--r--src/api/lsfgApi.ts13
-rw-r--r--src/components/ConfigurationSection.tsx28
-rw-r--r--src/components/Content.tsx17
-rw-r--r--src/components/NerdStuffModal.tsx95
-rw-r--r--src/components/UsageInstructions.tsx6
-rw-r--r--src/components/index.ts1
-rw-r--r--src/config/configSchema.ts10
-rw-r--r--tests/test_configuration.py2
12 files changed, 207 insertions, 75 deletions
diff --git a/package.json b/package.json
index d224bf6..dc41930 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "lossless-scaling-vk",
- "version": "0.6.1",
+ "version": "0.6.3",
"description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer",
"type": "module",
"scripts": {
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index 460b0a0..45afc2d 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -38,13 +38,6 @@ class ConfigField:
# Configuration schema definition
CONFIG_SCHEMA: Dict[str, ConfigField] = {
- "enable": ConfigField(
- name="enable",
- field_type=ConfigFieldType.BOOLEAN,
- default=True,
- description="enable/disable lsfg on every game"
- ),
-
"dll": ConfigField(
name="dll",
field_type=ConfigFieldType.STRING,
@@ -55,7 +48,7 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = {
"multiplier": ConfigField(
name="multiplier",
field_type=ConfigFieldType.INTEGER,
- default=2,
+ default=1,
description="change the fps multiplier"
),
@@ -112,7 +105,6 @@ CONFIG_SCHEMA: Dict[str, ConfigField] = {
class ConfigurationData(TypedDict):
"""Type-safe configuration data structure"""
- enable: bool
dll: str
multiplier: int
flow_scale: float
@@ -219,25 +211,21 @@ class ConfigurationManager:
# 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"]:
+ # Skip dll field - dll goes in global section
+ if field_name == "dll":
continue
value = config[field_name]
- # 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}")
+ # Add field description comment
+ 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) and value: # Only add non-empty strings
lines.append(f'{field_name} = "{value}"')
- elif isinstance(value, (int, float)) and value != 0: # Only add non-zero numbers
+ elif isinstance(value, (int, float)): # Always include numbers, even if 0 or 1
lines.append(f"{field_name} = {value}")
lines.append("") # Empty line for readability
@@ -306,12 +294,7 @@ class ConfigurationManager:
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
+ config[key] = parsed_value
elif field_def.field_type == ConfigFieldType.FLOAT:
config[key] = float(value)
elif field_def.field_type == ConfigFieldType.STRING:
@@ -327,7 +310,7 @@ class ConfigurationManager:
return ConfigurationManager.get_defaults()
@staticmethod
- def create_config_from_args(enable: bool, dll: str, multiplier: int, flow_scale: float,
+ def create_config_from_args(dll: str, multiplier: int, flow_scale: float,
performance_mode: bool, hdr_mode: bool,
experimental_present_mode: str = "",
experimental_fps_limit: int = 0,
@@ -335,7 +318,6 @@ class ConfigurationManager:
disable_steamdeck_mode: bool = False) -> ConfigurationData:
"""Create configuration from individual arguments"""
return cast(ConfigurationData, {
- "enable": enable,
"dll": dll,
"multiplier": multiplier,
"flow_scale": flow_scale,
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index ae0194b..1952a49 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -65,7 +65,7 @@ class ConfigurationService(BaseService):
"error": None
}
- def update_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float,
+ def update_config(self, dll: str, multiplier: int, flow_scale: float,
performance_mode: bool, hdr_mode: bool,
experimental_present_mode: str = "",
experimental_fps_limit: int = 0,
@@ -74,7 +74,6 @@ class ConfigurationService(BaseService):
"""Update TOML configuration
Args:
- enable: Whether to enable LSFG
dll: Path to Lossless.dll
multiplier: LSFG multiplier value
flow_scale: LSFG flow scale value
@@ -91,7 +90,7 @@ class ConfigurationService(BaseService):
try:
# Create configuration from individual arguments
config = ConfigurationManager.create_config_from_args(
- enable, dll, multiplier, flow_scale, performance_mode, hdr_mode,
+ dll, multiplier, flow_scale, performance_mode, hdr_mode,
experimental_present_mode, experimental_fps_limit, enable_wow64, disable_steamdeck_mode
)
@@ -109,7 +108,7 @@ class ConfigurationService(BaseService):
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
- self.log.info(f"Updated lsfg TOML configuration: enable={enable}, "
+ self.log.info(f"Updated lsfg TOML configuration: "
f"dll='{dll}', multiplier={multiplier}, flow_scale={flow_scale}, "
f"performance_mode={performance_mode}, hdr_mode={hdr_mode}, "
f"experimental_present_mode='{experimental_present_mode}', "
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index a44840d..4e19a2a 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -10,6 +10,7 @@ import json
import subprocess
import urllib.request
import ssl
+import hashlib
from typing import Dict, Any
from pathlib import Path
@@ -101,6 +102,67 @@ class Plugin:
return result_dict
+ async def get_dll_stats(self) -> Dict[str, Any]:
+ """Get detailed statistics about the detected DLL
+
+ Returns:
+ Dict containing DLL path, SHA256 hash, and other stats
+ """
+ try:
+ # First check if DLL is detected
+ dll_result = self.dll_detection_service.check_lossless_scaling_dll()
+
+ if not dll_result.get("detected") or not dll_result.get("path"):
+ return {
+ "success": False,
+ "error": "DLL not detected",
+ "dll_path": None,
+ "dll_sha256": None
+ }
+
+ dll_path = dll_result["path"]
+ if dll_path is None:
+ return {
+ "success": False,
+ "error": "DLL path is None",
+ "dll_path": None,
+ "dll_sha256": None
+ }
+
+ dll_path_obj = Path(dll_path)
+
+ # Calculate SHA256 hash
+ sha256_hash = hashlib.sha256()
+ try:
+ with open(dll_path_obj, "rb") as f:
+ # Read file in chunks to handle large files efficiently
+ for chunk in iter(lambda: f.read(4096), b""):
+ sha256_hash.update(chunk)
+ dll_sha256 = sha256_hash.hexdigest()
+ except Exception as e:
+ return {
+ "success": False,
+ "error": f"Failed to calculate SHA256: {str(e)}",
+ "dll_path": dll_path,
+ "dll_sha256": None
+ }
+
+ return {
+ "success": True,
+ "dll_path": dll_path,
+ "dll_sha256": dll_sha256,
+ "dll_source": dll_result.get("source"),
+ "error": None
+ }
+
+ except Exception as e:
+ return {
+ "success": False,
+ "error": f"Failed to get DLL stats: {str(e)}",
+ "dll_path": None,
+ "dll_sha256": None
+ }
+
# Configuration methods
async def get_lsfg_config(self) -> Dict[str, Any]:
"""Read current lsfg script configuration
@@ -122,7 +184,7 @@ class Plugin:
"defaults": ConfigurationManager.get_defaults()
}
- async def update_lsfg_config(self, enable: bool, dll: str, multiplier: int, flow_scale: float,
+ async def update_lsfg_config(self, dll: str, multiplier: int, flow_scale: float,
performance_mode: bool, hdr_mode: bool,
experimental_present_mode: str = "",
experimental_fps_limit: int = 0,
@@ -131,7 +193,6 @@ class Plugin:
"""Update lsfg TOML configuration
Args:
- enable: Whether to enable LSFG
dll: Path to Lossless.dll
multiplier: LSFG multiplier value
flow_scale: LSFG flow scale value
@@ -146,7 +207,7 @@ class Plugin:
ConfigurationResponse dict with success status
"""
return self.configuration_service.update_config(
- enable, dll, multiplier, flow_scale, performance_mode, hdr_mode,
+ dll, multiplier, flow_scale, performance_mode, hdr_mode,
experimental_present_mode, experimental_fps_limit, enable_wow64, disable_steamdeck_mode
)
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts
index 5770c43..4c23955 100644
--- a/src/api/lsfgApi.ts
+++ b/src/api/lsfgApi.ts
@@ -28,6 +28,14 @@ export interface DllDetectionResult {
error?: string;
}
+export interface DllStatsResult {
+ success: boolean;
+ dll_path?: string;
+ dll_sha256?: string;
+ dll_source?: string;
+ error?: string;
+}
+
// Use centralized configuration data type
export type LsfgConfig = ConfigurationData;
@@ -77,20 +85,21 @@ export const installLsfgVk = callable<[], InstallationResult>("install_lsfg_vk")
export const uninstallLsfgVk = callable<[], InstallationResult>("uninstall_lsfg_vk");
export const checkLsfgVkInstalled = callable<[], InstallationStatus>("check_lsfg_vk_installed");
export const checkLosslessScalingDll = callable<[], DllDetectionResult>("check_lossless_scaling_dll");
+export const getDllStats = callable<[], DllStatsResult>("get_dll_stats");
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<
- [boolean, string, number, number, boolean, boolean, string, number, boolean, boolean],
+ [string, number, number, boolean, boolean, string, number, boolean, boolean],
ConfigUpdateResult
>("update_lsfg_config");
// Helper function to create config update from configuration object
export const updateLsfgConfigFromObject = async (config: ConfigurationData): Promise<ConfigUpdateResult> => {
const args = ConfigurationManager.createArgsFromConfig(config);
- return updateLsfgConfig(...args as [boolean, string, number, number, boolean, boolean, string, number, boolean, boolean]);
+ return updateLsfgConfig(...args as [string, number, number, boolean, boolean, string, number, boolean, boolean]);
};
// Self-updater API functions
diff --git a/src/components/ConfigurationSection.tsx b/src/components/ConfigurationSection.tsx
index 00717bc..ad99c28 100644
--- a/src/components/ConfigurationSection.tsx
+++ b/src/components/ConfigurationSection.tsx
@@ -28,24 +28,6 @@ export function ConfigurationSection({
</div>
</PanelSectionRow>
- {/* <PanelSectionRow>
- <ToggleField
- label="Enable LSFG"
- description="Enables lsfg globally (apply before launching games)"
- checked={config.enable}
- onChange={(value) => onConfigChange('enable', value)}
- />
- </PanelSectionRow> */}
-
- {/* <PanelSectionRow>
- <TextField
- label="Lossless.dll Path"
- description="specify where Lossless.dll is stored"
- value={config.dll}
- onChange={(e) => onConfigChange('dll', e.target.value)}
- />
- </PanelSectionRow> */}
-
<PanelSectionRow>
<SliderField
label="FPS Multiplier"
@@ -56,11 +38,13 @@ export function ConfigurationSection({
step={1}
notchCount={4}
notchLabels={[
- { notchIndex: 1, label: "OFF" },
- { notchIndex: 2, label: "2X" },
- { notchIndex: 3, label: "3X" },
- { notchIndex: 4, label: "4X" }
+ { notchIndex: 0, label: "OFF", value: 1 },
+ { notchIndex: 1, label: "2X", value: 2 },
+ { notchIndex: 2, label: "3X", value: 3 },
+ { notchIndex: 3, label: "4X", value: 4 }
]}
+ showValue={false}
+ notchTicksVisible={true}
onChange={(value) => onConfigChange('multiplier', value)}
/>
</PanelSectionRow>
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 613e722..9ce4c35 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -1,5 +1,5 @@
import { useEffect } from "react";
-import { PanelSection } from "@decky/ui";
+import { PanelSection, showModal, ButtonItem, PanelSectionRow } from "@decky/ui";
import { useInstallationStatus, useDllDetection, useLsfgConfig } from "../hooks/useLsfgHooks";
import { useInstallationActions } from "../hooks/useInstallationActions";
import { StatusDisplay } from "./StatusDisplay";
@@ -9,6 +9,7 @@ import { UsageInstructions } from "./UsageInstructions";
import { WikiButton } from "./WikiButton";
import { ClipboardButton } from "./ClipboardButton";
import { PluginUpdateChecker } from "./PluginUpdateChecker";
+import { NerdStuffModal } from "./NerdStuffModal";
import { ConfigurationData } from "../config/configSchema";
export function Content() {
@@ -49,6 +50,10 @@ export function Content() {
handleUninstall(setIsInstalled, setInstallationStatus);
};
+ const handleShowNerdStuff = () => {
+ showModal(<NerdStuffModal />);
+ };
+
return (
<PanelSection>
<InstallationButton
@@ -81,6 +86,16 @@ export function Content() {
{/* Plugin Update Checker */}
<PluginUpdateChecker />
+
+ {/* Nerd Stuff Button */}
+ <PanelSectionRow>
+ <ButtonItem
+ layout="below"
+ onClick={handleShowNerdStuff}
+ >
+ Nerd Stuff
+ </ButtonItem>
+ </PanelSectionRow>
</PanelSection>
);
}
diff --git a/src/components/NerdStuffModal.tsx b/src/components/NerdStuffModal.tsx
new file mode 100644
index 0000000..4dd53b1
--- /dev/null
+++ b/src/components/NerdStuffModal.tsx
@@ -0,0 +1,95 @@
+import { useState, useEffect } from "react";
+import {
+ ModalRoot,
+ Field,
+ Focusable
+} from "@decky/ui";
+import { getDllStats, DllStatsResult } from "../api/lsfgApi";
+
+interface NerdStuffModalProps {
+ closeModal?: () => void;
+}
+
+export function NerdStuffModal({ closeModal }: NerdStuffModalProps) {
+ const [dllStats, setDllStats] = useState<DllStatsResult | null>(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState<string | null>(null);
+
+ useEffect(() => {
+ const loadDllStats = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const result = await getDllStats();
+ setDllStats(result);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to load DLL stats");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadDllStats();
+ }, []);
+
+ const formatSHA256 = (hash: string) => {
+ // Format SHA256 hash for better readability (add spaces every 8 characters)
+ return hash.replace(/(.{8})/g, '$1 ').trim();
+ };
+
+ const copyToClipboard = async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ // Could add a toast notification here if desired
+ } catch (err) {
+ console.error("Failed to copy to clipboard:", err);
+ }
+ };
+
+ return (
+ <ModalRoot onCancel={closeModal} onOK={closeModal}>
+ {loading && (
+ <div>Loading DLL information...</div>
+ )}
+
+ {error && (
+ <div>Error: {error}</div>
+ )}
+
+ {!loading && !error && dllStats && (
+ <>
+ {!dllStats.success ? (
+ <div>{dllStats.error || "Failed to get DLL stats"}</div>
+ ) : (
+ <div>
+ <Field label="DLL Path">
+ <Focusable
+ onClick={() => dllStats.dll_path && copyToClipboard(dllStats.dll_path)}
+ onActivate={() => dllStats.dll_path && copyToClipboard(dllStats.dll_path)}
+ >
+ {dllStats.dll_path || "Not available"}
+ </Focusable>
+ </Field>
+
+ <Field label="SHA256 Hash">
+ <Focusable
+ onClick={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)}
+ onActivate={() => dllStats.dll_sha256 && copyToClipboard(dllStats.dll_sha256)}
+ >
+ {dllStats.dll_sha256 ? formatSHA256(dllStats.dll_sha256) : "Not available"}
+ </Focusable>
+ </Field>
+
+ {dllStats.dll_source && (
+ <Field label="Detection Source">
+ <div>{dllStats.dll_source}</div>
+ </Field>
+ )}
+
+ </div>
+ )}
+ </>
+ )}
+ </ModalRoot>
+ );
+}
diff --git a/src/components/UsageInstructions.tsx b/src/components/UsageInstructions.tsx
index be48a82..5de1fcf 100644
--- a/src/components/UsageInstructions.tsx
+++ b/src/components/UsageInstructions.tsx
@@ -33,10 +33,7 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
whiteSpace: "pre-wrap"
}}
>
- {config.enable
- ? "Add the launch option below (or use \"Launch Option Clipboard\") to Steam games to activate frame generation."
- : "LSFG is disabled. Enable it above and add the launch option to activate frame generation."
- }
+ Add the launch option below (or use "Launch Option Clipboard") to Steam games to activate frame generation.
</div>
</PanelSectionRow>
@@ -69,7 +66,6 @@ export function UsageInstructions({ config }: UsageInstructionsProps) {
}}
>
{`Current Configuration:
-• Enable: ${config.enable ? "Yes" : "No"}
• DLL Path: ${config.dll}
• Multiplier: ${config.multiplier}x
• Flow Scale: ${Math.round(config.flow_scale * 100)}%
diff --git a/src/components/index.ts b/src/components/index.ts
index ed0b803..c0c4804 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -7,3 +7,4 @@ export { WikiButton } from "./WikiButton";
export { ClipboardButton } from "./ClipboardButton";
export { LaunchOptionInfo } from "./LaunchOptionInfo";
export { PluginUpdateChecker } from "./PluginUpdateChecker";
+export { NerdStuffModal } from "./NerdStuffModal";
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts
index 8715057..fa54336 100644
--- a/src/config/configSchema.ts
+++ b/src/config/configSchema.ts
@@ -23,13 +23,6 @@ export interface ConfigField {
// Configuration schema - must match Python CONFIG_SCHEMA
export const CONFIG_SCHEMA: Record<string, ConfigField> = {
- enable: {
- name: "enable",
- fieldType: ConfigFieldType.BOOLEAN,
- default: true,
- description: "enable/disable lsfg on every game"
- },
-
dll: {
name: "dll",
fieldType: ConfigFieldType.STRING,
@@ -40,7 +33,7 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = {
multiplier: {
name: "multiplier",
fieldType: ConfigFieldType.INTEGER,
- default: 2,
+ default: 1,
description: "change the fps multiplier"
},
@@ -96,7 +89,6 @@ export const CONFIG_SCHEMA: Record<string, ConfigField> = {
// Type-safe configuration data structure
export interface ConfigurationData {
- enable: boolean;
dll: string;
multiplier: number;
flow_scale: number;
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index f3fdcbe..1b10413 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -128,7 +128,6 @@ def test_config_roundtrip():
# Update config
result = service.update_config(
- enable=True,
dll="/path/to/dll",
multiplier=3,
flow_scale=1.5,
@@ -147,7 +146,6 @@ def test_config_roundtrip():
assert read_result["success"] is True
config = read_result["config"]
- assert config["enable"] is True
assert config["dll"] == "/path/to/dll"
assert config["multiplier"] == 3
assert config["flow_scale"] == 1.5