summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore8
-rw-r--r--main.py4
-rw-r--r--package.json2
-rw-r--r--py_modules/lsfg_vk/__init__.py3
-rw-r--r--py_modules/lsfg_vk/base_service.py10
-rw-r--r--py_modules/lsfg_vk/configuration.py76
-rw-r--r--py_modules/lsfg_vk/constants.py8
-rw-r--r--py_modules/lsfg_vk/dll_detection.py15
-rw-r--r--py_modules/lsfg_vk/flatpak_service.py34
-rw-r--r--py_modules/lsfg_vk/installation.py8
-rw-r--r--py_modules/lsfg_vk/plugin.py285
-rw-r--r--shared_config.py5
-rw-r--r--src/components/Content.tsx14
-rw-r--r--src/components/SmartClipboardButton.tsx7
-rw-r--r--src/config/configSchema.ts4
-rw-r--r--src/hooks/useInstallationActions.ts2
-rw-r--r--src/hooks/useLsfgHooks.ts3
-rwxr-xr-xsrc/index.tsx6
-rw-r--r--src/utils/clipboardUtils.ts6
19 files changed, 22 insertions, 478 deletions
diff --git a/.gitignore b/.gitignore
index c74264d..e384d9d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,10 +56,10 @@ cli/
cli/*
cli/decky
-# generated files
-py_modules/lsfg_vk/config_schema_generated.py
-py_modules/lsfg_vk/configuration_helpers_generated.py
-src/config/generatedConfigSchema.ts
+# generated files (uncommented for now, need in git for store build deploys)
+# py_modules/lsfg_vk/config_schema_generated.py
+# py_modules/lsfg_vk/configuration_helpers_generated.py
+# src/config/generatedConfigSchema.ts
# Additional development artifacts
*.pyc
diff --git a/main.py b/main.py
index 18b93f9..b4ae724 100644
--- a/main.py
+++ b/main.py
@@ -2,12 +2,8 @@
Main entry point for the lsfg-vk Decky Loader plugin.
This file imports and exposes the Plugin class from the lsfg_vk package.
-The actual implementation has been refactored into separate service modules
-for better maintainability and testability.
"""
-# Import the refactored Plugin class
from lsfg_vk import Plugin
-# Re-export Plugin at module level for Decky Loader compatibility
__all__ = ['Plugin']
diff --git a/package.json b/package.json
index dc45048..09f8844 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "decky-lsfg-vk",
- "version": "0.12.0",
+ "version": "0.12.1",
"description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer",
"type": "module",
"scripts": {
diff --git a/py_modules/lsfg_vk/__init__.py b/py_modules/lsfg_vk/__init__.py
index 8343853..31c553b 100644
--- a/py_modules/lsfg_vk/__init__.py
+++ b/py_modules/lsfg_vk/__init__.py
@@ -5,10 +5,9 @@ This package provides services for installing and managing the lsfg-vk
Vulkan layer for Lossless Scaling frame generation.
"""
-# Import will be available once plugin.py exists
try:
from .plugin import Plugin
__all__ = ['Plugin']
except ImportError:
- # During development, plugin may not exist yet
__all__ = []
+
diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py
index 9c3dec3..262e2b0 100644
--- a/py_modules/lsfg_vk/base_service.py
+++ b/py_modules/lsfg_vk/base_service.py
@@ -12,7 +12,6 @@ import decky
from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME, CONFIG_DIR, CONFIG_FILENAME
-# Generic type for response dictionaries
ResponseType = TypeVar('ResponseType', bound=Dict[str, Any])
@@ -30,12 +29,11 @@ class BaseService:
else:
self.log = logger
- # Initialize common paths using pathlib
self.user_home = Path.home()
self.local_lib_dir = self.user_home / LOCAL_LIB
self.local_share_dir = self.user_home / VULKAN_LAYER_DIR
self.lsfg_script_path = self.user_home / SCRIPT_NAME
- self.lsfg_launch_script_path = self.user_home / SCRIPT_NAME # ~/lsfg launch script
+ self.lsfg_launch_script_path = self.user_home / SCRIPT_NAME
self.config_dir = self.user_home / CONFIG_DIR
self.config_file_path = self.config_dir / CONFIG_FILENAME
@@ -82,13 +80,11 @@ class BaseService:
OSError: If write fails
"""
try:
- # Write directly to the file
with open(path, 'w', encoding='utf-8') as f:
f.write(content)
- f.flush() # Ensure data is written to disk
- os.fsync(f.fileno()) # Force filesystem sync
+ f.flush()
+ os.fsync(f.fileno())
- # Set permissions
path.chmod(mode)
self.log.info(f"Wrote to {path}")
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index b61a06d..9f8b028 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -22,9 +22,7 @@ class ConfigurationService(BaseService):
ConfigurationResponse with current configuration or error
"""
try:
- # Get TOML configuration (with defaults if file doesn't exist)
if not self.config_file_path.exists():
- # Return default configuration with DLL detection if file doesn't exist
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
toml_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -32,7 +30,6 @@ class ConfigurationService(BaseService):
content = self.config_file_path.read_text(encoding='utf-8')
toml_config = ConfigurationManager.parse_toml_content(content)
- # Get script environment variables (if script exists)
script_values = {}
if self.lsfg_script_path.exists():
try:
@@ -42,7 +39,6 @@ class ConfigurationService(BaseService):
except Exception as e:
self.log.warning(f"Failed to parse launch script: {str(e)}")
- # Merge TOML config with script values
config = ConfigurationManager.merge_config_with_script(toml_config, script_values)
return self._success_response(ConfigurationResponse, config=config)
@@ -54,7 +50,6 @@ class ConfigurationService(BaseService):
except Exception as e:
error_msg = f"Error parsing config file: {str(e)}"
self.log.error(error_msg)
- # Return defaults with DLL detection if parsing fails
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -75,7 +70,6 @@ class ConfigurationService(BaseService):
profile_data = self._get_profile_data()
current_profile = profile_data["current_profile"]
- # Update the current profile's config
return self.update_profile_config(current_profile, config)
except (OSError, IOError) as e:
@@ -97,10 +91,8 @@ class ConfigurationService(BaseService):
ConfigurationResponse with success status
"""
try:
- # Create configuration from keyword arguments using generated function
config = ConfigurationManager.create_config_from_args(**kwargs)
- # Update using the new profile-aware method
return self.update_config_from_dict(config)
except (OSError, IOError) as e:
@@ -112,45 +104,6 @@ class ConfigurationService(BaseService):
self.log.error(error_msg)
return self._error_response(ConfigurationResponse, str(e), config=None)
- def update_dll_path(self, dll_path: str) -> ConfigurationResponse:
- """Update just the DLL path in the configuration
-
- Args:
- dll_path: Path to the Lossless.dll file
-
- Returns:
- ConfigurationResponse with success status
- """
- try:
- profile_data = self._get_profile_data()
-
- # Update global config (DLL path is global)
- profile_data["global_config"]["dll"] = dll_path
-
- # Also update current profile's config for backward compatibility
- current_profile = profile_data["current_profile"]
- from .config_schema_generated import DLL
- profile_data["profiles"][current_profile][DLL] = dll_path
-
- # Save to file
- self._save_profile_data(profile_data)
-
- # Update launch script
- script_result = self.update_lsfg_script_from_profile_data(profile_data)
- if not script_result["success"]:
- self.log.warning(f"Failed to update launch script: {script_result['error']}")
-
- self.log.info(f"Updated DLL path in lsfg configuration: '{dll_path}'")
-
- return self._success_response(ConfigurationResponse,
- f"DLL path updated to: {dll_path}",
- config=profile_data["profiles"][current_profile])
-
- except Exception as e:
- error_msg = f"Error updating DLL path: {str(e)}"
- self.log.error(error_msg)
- return self._error_response(ConfigurationResponse, str(e), config=None)
-
def update_lsfg_script(self, config: ConfigurationData) -> ConfigurationResponse:
"""Update the ~/lsfg launch script with current configuration
@@ -163,7 +116,6 @@ class ConfigurationService(BaseService):
try:
script_content = self._generate_script_content(config)
- # Write the script file
self._write_file(self.lsfg_script_path, script_content, 0o755)
self.log.info(f"Updated lsfg launch script at {self.lsfg_script_path}")
@@ -189,14 +141,12 @@ class ConfigurationService(BaseService):
lines = [
"#!/bin/bash",
"# lsfg-vk launch script generated by decky-lossless-scaling-vk plugin",
- "# This script sets up the environment for lsfg-vk to work with the plugin configuration"
+ "# This script sets up the environment for lsfg-vk to work with the plugin configuration",
]
- # Use auto-generated script generation logic
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(config))
- # Always add the LSFG_PROCESS export and execution line
lines.extend([
"export LSFG_PROCESS=decky-lsfg-vk",
'exec "$@"'
@@ -216,23 +166,18 @@ class ConfigurationService(BaseService):
current_profile = profile_data["current_profile"]
config = profile_data["profiles"].get(current_profile, ConfigurationManager.get_defaults())
- # Merge global config with profile config
merged_config = dict(config)
for field_name, value in profile_data["global_config"].items():
merged_config[field_name] = value
lines = [
"#!/bin/bash",
- "# lsfg-vk launch script generated by decky-lossless-scaling-vk plugin",
f"# Current profile: {current_profile}",
- "# This script sets up the environment for lsfg-vk to work with the plugin configuration"
]
- # Use auto-generated script generation logic
generate_script_lines = get_script_generation_logic()
lines.extend(generate_script_lines(merged_config))
- # Export LSFG_PROCESS with current profile name
lines.extend([
f"export LSFG_PROCESS={current_profile}",
'exec "$@"'
@@ -243,7 +188,6 @@ class ConfigurationService(BaseService):
def _get_profile_data(self) -> ProfileData:
"""Get current profile data from config file"""
if not self.config_file_path.exists():
- # Return default profile structure if file doesn't exist
from .dll_detection import DllDetectionService
dll_service = DllDetectionService(self.log)
default_config = ConfigurationManager.get_defaults_with_dll_detection(dll_service)
@@ -263,13 +207,10 @@ class ConfigurationService(BaseService):
"""Save profile data to config file"""
toml_content = ConfigurationManager.generate_toml_content_multi_profile(profile_data)
- # Ensure config directory exists
self.config_dir.mkdir(parents=True, exist_ok=True)
- # Write the updated config directly to preserve inode for file watchers
self._write_file(self.config_file_path, toml_content, 0o644)
- # Profile management methods
def get_profiles(self) -> ProfilesResponse:
"""Get list of all profiles and current profile
@@ -303,14 +244,11 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Use current profile as source if not specified
if not source_profile:
source_profile = profile_data["current_profile"]
- # Create the new profile
new_profile_data = ConfigurationManager.create_profile(profile_data, profile_name, source_profile)
- # Save to file
self._save_profile_data(new_profile_data)
self.log.info(f"Created profile '{profile_name}' from '{source_profile}'")
@@ -340,13 +278,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Delete the profile
new_profile_data = ConfigurationManager.delete_profile(profile_data, profile_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script if current profile changed
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -379,13 +314,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Rename the profile
new_profile_data = ConfigurationManager.rename_profile(profile_data, old_name, new_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script if current profile changed
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -417,13 +349,10 @@ class ConfigurationService(BaseService):
try:
profile_data = self._get_profile_data()
- # Set current profile
new_profile_data = ConfigurationManager.set_current_profile(profile_data, profile_name)
- # Save to file
self._save_profile_data(new_profile_data)
- # Update launch script with new current profile
script_result = self.update_lsfg_script_from_profile_data(new_profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
@@ -469,16 +398,13 @@ class ConfigurationService(BaseService):
if field_name in config:
profile_data["global_config"][field_name] = config[field_name]
- # Save to file
self._save_profile_data(profile_data)
- # Update launch script if this is the current profile
if profile_name == profile_data["current_profile"]:
script_result = self.update_lsfg_script_from_profile_data(profile_data)
if not script_result["success"]:
self.log.warning(f"Failed to update launch script: {script_result['error']}")
- # Log with dynamic field listing
field_values = ", ".join(f"{k}={repr(v)}" for k, v in config.items())
self.log.info(f"Updated profile '{profile_name}' configuration: {field_values}")
diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py
index 79d8585..3d8e44a 100644
--- a/py_modules/lsfg_vk/constants.py
+++ b/py_modules/lsfg_vk/constants.py
@@ -4,36 +4,30 @@ Constants for the lsfg-vk plugin.
from pathlib import Path
-# Directory paths
LOCAL_LIB = ".local/lib"
LOCAL_SHARE_BASE = ".local/share"
VULKAN_LAYER_DIR = ".local/share/vulkan/implicit_layer.d"
CONFIG_DIR = ".config/lsfg-vk"
-# File names
SCRIPT_NAME = "lsfg"
CONFIG_FILENAME = "conf.toml"
LIB_FILENAME = "liblsfg-vk.so"
JSON_FILENAME = "VkLayer_LS_frame_generation.json"
ZIP_FILENAME = "lsfg-vk_noui.zip"
-# Flatpak files
FLATPAK_23_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_23.08.flatpak"
FLATPAK_24_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_24.08.flatpak"
FLATPAK_25_08_FILENAME = "org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak"
-# File extensions
SO_EXT = ".so"
JSON_EXT = ".json"
-# Directory for the zip file
BIN_DIR = "bin"
-# Lossless Scaling paths
STEAM_COMMON_PATH = Path("steamapps/common/Lossless Scaling")
LOSSLESS_DLL_NAME = "Lossless.dll"
-# Environment variable names
ENV_LSFG_DLL_PATH = "LSFG_DLL_PATH"
ENV_XDG_DATA_HOME = "XDG_DATA_HOME"
ENV_HOME = "HOME"
+
diff --git a/py_modules/lsfg_vk/dll_detection.py b/py_modules/lsfg_vk/dll_detection.py
index e547405..f7ba444 100644
--- a/py_modules/lsfg_vk/dll_detection.py
+++ b/py_modules/lsfg_vk/dll_detection.py
@@ -31,27 +31,22 @@ class DllDetectionService(BaseService):
DllDetectionResponse with detection status and path information
"""
try:
- # Check environment variable first
dll_path = self._check_env_dll_path()
if dll_path:
return dll_path
- # Check XDG_DATA_HOME path
xdg_path = self._check_xdg_data_home()
if xdg_path:
return xdg_path
- # Check HOME/.local/share path
home_path = self._check_home_local_share()
if home_path:
return home_path
- # Check all Steam library folders (including SD cards)
steam_libraries_path = self._check_steam_library_folders()
if steam_libraries_path:
return steam_libraries_path
- # DLL not found in any expected location
return {
"detected": False,
"path": None,
@@ -164,25 +159,20 @@ class DllDetectionService(BaseService):
"""
library_paths = []
- # Try different possible Steam installation locations
steam_paths = []
- # XDG_DATA_HOME path
data_dir = os.getenv(ENV_XDG_DATA_HOME)
if data_dir and data_dir.strip():
steam_paths.append(Path(data_dir.strip()) / "Steam")
- # HOME/.local/share path (most common on Steam Deck)
home_dir = os.getenv(ENV_HOME)
if home_dir and home_dir.strip():
steam_paths.append(Path(home_dir.strip()) / ".local" / "share" / "Steam")
for steam_path in steam_paths:
if steam_path.exists():
- # Add the main Steam directory as a library
library_paths.append(str(steam_path))
- # Parse libraryfolders.vdf for additional libraries
vdf_path = steam_path / "steamapps" / "libraryfolders.vdf"
if vdf_path.exists():
try:
@@ -191,7 +181,6 @@ class DllDetectionService(BaseService):
except Exception as e:
self.log.warning(f"Failed to parse {vdf_path}: {str(e)}")
- # Remove duplicates while preserving order
seen = set()
unique_paths = []
for path in library_paths:
@@ -217,17 +206,13 @@ class DllDetectionService(BaseService):
with open(vdf_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
- # Look for "path" entries in the VDF file
- # The format is typically: "path" "/path/to/library"
path_pattern = r'"path"\s*"([^"]+)"'
matches = re.findall(path_pattern, content, re.IGNORECASE)
for path_match in matches:
- # Convert Windows paths to Unix paths if needed
path = path_match.replace('\\\\', '/').replace('\\', '/')
library_path = Path(path)
- # Verify the library folder exists and has a steamapps directory
if library_path.exists() and (library_path / "steamapps").exists():
library_paths.append(str(library_path))
self.log.info(f"Found additional Steam library: {library_path}")
diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py
index 0e3977f..c9be0ec 100644
--- a/py_modules/lsfg_vk/flatpak_service.py
+++ b/py_modules/lsfg_vk/flatpak_service.py
@@ -50,22 +50,18 @@ class FlatpakService(BaseService):
self.extension_id_23_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/23.08"
self.extension_id_24_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/24.08"
self.extension_id_25_08 = "org.freedesktop.Platform.VulkanLayer.lsfgvk/x86_64/25.08"
- self.flatpak_command = None # Will be set when flatpak is detected
+ self.flatpak_command = None
def _get_clean_env(self):
"""Get a clean environment without PyInstaller's bundled libraries"""
- # Create a clean environment without PyInstaller's bundled libraries
env = os.environ.copy()
- # Remove LD_LIBRARY_PATH that might point to PyInstaller's bundled libs
if 'LD_LIBRARY_PATH' in env:
del env['LD_LIBRARY_PATH']
- # Ensure PATH includes standard binary locations
standard_paths = ['/usr/bin', '/usr/local/bin', '/bin']
current_path = env.get('PATH', '')
- # Add standard paths if they're not already there
path_parts = current_path.split(':') if current_path else []
for std_path in standard_paths:
if std_path not in path_parts:
@@ -82,21 +78,17 @@ class FlatpakService(BaseService):
env = self._get_clean_env()
- # Log environment info for debugging
self.log.info(f"Running flatpak with PATH: {env.get('PATH')}")
self.log.info(f"LD_LIBRARY_PATH removed: {'LD_LIBRARY_PATH' not in env}")
- # Run the command with the clean environment
return subprocess.run([self.flatpak_command] + args, env=env, **kwargs)
def check_flatpak_available(self) -> bool:
"""Check if flatpak command is available and store the working command"""
- # Log environment info for debugging
self.log.info(f"PATH: {os.environ.get('PATH', 'Not set')}")
self.log.info(f"HOME: {os.environ.get('HOME', 'Not set')}")
self.log.info(f"USER: {os.environ.get('USER', 'Not set')}")
- # Try common flatpak installation paths, starting with the standard command
flatpak_paths = [
"flatpak",
"/usr/bin/flatpak",
@@ -132,7 +124,6 @@ class FlatpakService(BaseService):
error_msg,
installed_23_08=False, installed_24_08=False, installed_25_08=False)
- # Get list of installed runtimes
result = self._run_flatpak_command(
["list", "--runtime"],
capture_output=True, text=True, check=True
@@ -140,7 +131,6 @@ class FlatpakService(BaseService):
installed_runtimes = result.stdout
- # Check for all versions by looking for the base extension name and version
base_extension_name = "org.freedesktop.Platform.VulkanLayer.lsfgvk"
installed_23_08 = False
installed_24_08 = False
@@ -187,20 +177,18 @@ class FlatpakService(BaseService):
if not self.check_flatpak_available():
return self._error_response(BaseResponse, "Flatpak is not available on this system")
- # Get the path to the flatpak file
plugin_dir = Path(__file__).parent.parent.parent
if version == "23.08":
filename = FLATPAK_23_08_FILENAME
elif version == "24.08":
filename = FLATPAK_24_08_FILENAME
- else: # 25.08
+ else:
filename = FLATPAK_25_08_FILENAME
flatpak_path = plugin_dir / BIN_DIR / filename
if not flatpak_path.exists():
return self._error_response(BaseResponse, f"Flatpak file not found: {flatpak_path}")
- # Install the extension
result = self._run_flatpak_command(
["install", "--user", "--noninteractive", str(flatpak_path)],
capture_output=True, text=True
@@ -232,10 +220,9 @@ class FlatpakService(BaseService):
extension_id = self.extension_id_23_08
elif version == "24.08":
extension_id = self.extension_id_24_08
- else: # 25.08
+ else:
extension_id = self.extension_id_25_08
- # Uninstall the extension
result = self._run_flatpak_command(
["uninstall", "--user", "--noninteractive", extension_id],
capture_output=True, text=True
@@ -265,7 +252,6 @@ class FlatpakService(BaseService):
error_msg,
apps=[], total_apps=0)
- # Get list of installed apps
result = self._run_flatpak_command(
["list", "--app"],
capture_output=True, text=True, check=True
@@ -276,7 +262,6 @@ class FlatpakService(BaseService):
if not line.strip():
continue
- # Parse flatpak list output (Name\tApp ID\tVersion\tBranch\tInstallation)
parts = line.split('\t')
if len(parts) >= 2:
app_name = parts[0].strip()
@@ -318,9 +303,6 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # More precise checking - look for exact filesystem entries
- # Flatpak output format typically shows filesystem entries like:
- # filesystems=/path1;/path2;/path3
filesystem_section = ""
in_context = False
@@ -334,15 +316,12 @@ class FlatpakService(BaseService):
filesystem_section = line
break
- # Check each required filesystem override
has_config_fs = config_path in filesystem_section
has_dll_fs = dll_path in filesystem_section
has_lsfg_fs = lsfg_path in filesystem_section
- # All three filesystem overrides must be present
filesystem_override = has_config_fs and has_dll_fs and has_lsfg_fs
- # Check for environment override in the [Environment] section
env_override = False
in_environment = False
@@ -377,14 +356,12 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # Set all filesystem overrides
filesystem_overrides = [
f"--filesystem={dll_path}",
f"--filesystem={config_path}:rw",
f"--filesystem={lsfg_path}:rw"
]
- # Apply filesystem overrides
for override in filesystem_overrides:
result = self._run_flatpak_command(
["override", "--user", override, app_id],
@@ -395,7 +372,6 @@ class FlatpakService(BaseService):
return self._error_response(FlatpakOverrideResponse, error_msg,
app_id=app_id, operation="set")
- # Set environment override
result = self._run_flatpak_command(
["override", "--user", f"--env=LSFG_CONFIG={config_path}/conf.toml", app_id],
capture_output=True, text=True
@@ -430,7 +406,6 @@ class FlatpakService(BaseService):
dll_path = f"{home_path}/.local/share/Steam/steamapps/common/Lossless Scaling/Lossless.dll"
lsfg_path = f"{home_path}/lsfg"
- # First, try to reset all overrides for this app to clean slate
reset_result = self._run_flatpak_command(
["override", "--user", "--reset", app_id],
capture_output=True, text=True
@@ -442,10 +417,8 @@ class FlatpakService(BaseService):
f"All overrides reset for {app_id}",
app_id=app_id, operation="remove")
- # If reset fails, try individual removal (fallback)
self.log.debug(f"Reset failed, trying individual removal: {reset_result.stderr}")
- # Remove all filesystem overrides individually
filesystem_overrides = [
f"--nofilesystem={dll_path}",
f"--nofilesystem={config_path}",
@@ -463,7 +436,6 @@ class FlatpakService(BaseService):
if result.returncode != 0:
removal_errors.append(f"{override}: {result.stderr}")
- # Remove environment override
result = self._run_flatpak_command(
["override", "--user", "--unset-env=LSFG_CONFIG", app_id],
capture_output=True, text=True
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index a3b000f..4329d49 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -26,7 +26,6 @@ class InstallationService(BaseService):
def __init__(self, logger=None):
super().__init__(logger)
- # File paths using constants
self.lib_file = self.local_lib_dir / LIB_FILENAME
self.json_file = self.local_share_dir / JSON_FILENAME
@@ -37,26 +36,20 @@ class InstallationService(BaseService):
InstallationResponse with success status and message/error
"""
try:
- # Get the path to the zip file - need to go up to plugin root from py_modules/lsfg_vk/
plugin_dir = Path(__file__).parent.parent.parent
zip_path = plugin_dir / BIN_DIR / ZIP_FILENAME
- # Check if the zip file exists
if not zip_path.exists():
error_msg = f"{ZIP_FILENAME} not found at {zip_path}"
self.log.error(error_msg)
return self._error_response(InstallationResponse, error_msg, message="")
- # Create directories if they don't exist
self._ensure_directories()
- # Extract and install files
self._extract_and_install_files(zip_path)
- # Create the config file
self._create_config_file()
- # Create the lsfg launch script
self._create_lsfg_launch_script()
self.log.info("lsfg-vk installed successfully")
@@ -67,7 +60,6 @@ class InstallationService(BaseService):
self.log.error(error_msg)
return self._error_response(InstallationResponse, str(e), message="")
except Exception as e:
- # Catch unexpected errors but log them separately
error_msg = f"Unexpected error installing lsfg-vk: {str(e)}"
self.log.error(error_msg)
return self._error_response(InstallationResponse, str(e), message="")
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 7731558..cb59b4f 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -6,10 +6,7 @@ Vulkan layer for frame generation on Steam Deck.
"""
import os
-import json
import subprocess
-import urllib.request
-import ssl
import hashlib
from typing import Dict, Any
from pathlib import Path
@@ -34,13 +31,11 @@ class Plugin:
def __init__(self):
"""Initialize the plugin with all necessary services"""
- # Initialize services - they will use decky.logger by default
self.installation_service = InstallationService()
self.dll_detection_service = DllDetectionService()
self.configuration_service = ConfigurationService()
self.flatpak_service = FlatpakService()
- # Installation methods
async def install_lsfg_vk(self) -> Dict[str, Any]:
"""Install lsfg-vk by extracting the zip file to ~/.local
@@ -65,7 +60,6 @@ class Plugin:
"""
return self.installation_service.uninstall()
- # DLL detection methods
async def check_lossless_scaling_dll(self) -> Dict[str, Any]:
"""Check if Lossless Scaling DLL is available at the expected paths
@@ -74,38 +68,6 @@ class Plugin:
"""
return self.dll_detection_service.check_lossless_scaling_dll()
- async def check_lossless_scaling_dll_and_update_config(self) -> Dict[str, Any]:
- """Check for DLL and automatically update configuration if found
-
- This method should only be used during installation or when explicitly
- requested by the user, not for routine DLL detection checks.
-
- Returns:
- DllDetectionResponse dict with detection status and path info
- """
- result = self.dll_detection_service.check_lossless_scaling_dll()
-
- # Convert to dict to allow modification
- result_dict = dict(result)
-
- # If DLL was detected, automatically update the configuration
- if result.get("detected") and result.get("path"):
- try:
- dll_path = result["path"]
- if dll_path: # Type guard
- update_result = self.configuration_service.update_dll_path(dll_path)
- if update_result.get("success"):
- result_dict["config_updated"] = True
- result_dict["message"] = f"DLL detected and configuration updated: {dll_path}"
- else:
- result_dict["config_updated"] = False
- result_dict["message"] = f"DLL detected but config update failed: {update_result.get('error', 'Unknown error')}"
- except Exception as e:
- result_dict["config_updated"] = False
- result_dict["message"] = f"DLL detected but config update failed: {str(e)}"
-
- return result_dict
-
async def get_dll_stats(self) -> Dict[str, Any]:
"""Get detailed statistics about the detected DLL
@@ -113,7 +75,6 @@ class Plugin:
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"):
@@ -135,11 +96,9 @@ class Plugin:
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()
@@ -167,7 +126,6 @@ class Plugin:
"dll_sha256": None
}
- # Configuration methods
async def get_lsfg_config(self) -> Dict[str, Any]:
"""Read current lsfg script configuration
@@ -183,7 +141,6 @@ class Plugin:
Dict with field names, types, defaults, and profile information
"""
try:
- # Get profile information
profiles_response = self.configuration_service.get_profiles()
schema_data = {
@@ -192,7 +149,6 @@ class Plugin:
"defaults": ConfigurationManager.get_defaults()
}
- # Add profile information if available
if profiles_response.get("success"):
schema_data["profiles"] = profiles_response.get("profiles", [])
schema_data["current_profile"] = profiles_response.get("current_profile")
@@ -203,7 +159,6 @@ class Plugin:
return schema_data
except (ValueError, KeyError, AttributeError) as e:
- # Fallback to basic schema without profile info
self.configuration_service.log.warning(f"Failed to get full schema, using fallback: {e}")
return {
"field_names": ConfigurationManager.get_field_names(),
@@ -222,24 +177,10 @@ class Plugin:
Returns:
ConfigurationResponse dict with success status
"""
- # Validate and extract configuration from the config dict
validated_config = ConfigurationManager.validate_config(config)
- # Use dynamic parameter passing based on schema
return self.configuration_service.update_config_from_dict(validated_config)
- async def update_dll_path(self, dll_path: str) -> Dict[str, Any]:
- """Update the DLL path in the configuration when detected
-
- Args:
- dll_path: Path to the detected Lossless.dll file
-
- Returns:
- ConfigurationResponse dict with success status
- """
- return self.configuration_service.update_dll_path(dll_path)
-
- # Profile management methods
async def get_profiles(self) -> Dict[str, Any]:
"""Get list of all profiles and current profile
@@ -304,212 +245,10 @@ class Plugin:
Returns:
ConfigurationResponse dict with success status
"""
- # Validate and extract configuration from the config dict
validated_config = ConfigurationManager.validate_config(config)
return self.configuration_service.update_profile_config(profile_name, validated_config)
- # Self-updater methods
- async def check_for_plugin_update(self) -> Dict[str, Any]:
- """Check for plugin updates by comparing current version with most recent GitHub release
-
- Checks for the most recent release including pre-releases, not just the latest stable.
-
- Returns:
- Dict containing update information:
- {
- "update_available": bool,
- "current_version": str,
- "latest_version": str,
- "release_notes": str,
- "release_date": str,
- "download_url": str,
- "error": str (if error occurred)
- }
- """
- try:
- # Read current version from package.json
- package_json_path = Path(decky.DECKY_PLUGIN_DIR) / "package.json"
- current_version = "0.0.0"
-
- if package_json_path.exists():
- try:
- with open(package_json_path, 'r', encoding='utf-8') as f:
- package_data = json.load(f)
- current_version = package_data.get('version', '0.0.0')
- except Exception as e:
- decky.logger.warning(f"Failed to read package.json: {e}")
-
- # Fetch most recent release from GitHub (including pre-releases)
- api_url = "https://api.github.com/repos/xXJSONDeruloXx/decky-lsfg-vk/releases"
-
- try:
- # Create SSL context that doesn't verify certificates
- # This is needed on Steam Deck where certificate verification often fails
- ssl_context = ssl.create_default_context()
- ssl_context.check_hostname = False
- ssl_context.verify_mode = ssl.CERT_NONE
-
- # Use urllib to fetch all releases (sorted by most recent first)
- with urllib.request.urlopen(api_url, context=ssl_context) as response:
- releases_data = json.loads(response.read().decode('utf-8'))
-
- # Get the most recent release (first item in the array)
- if not releases_data:
- raise Exception("No releases found")
-
- release_data = releases_data[0]
-
- latest_version = release_data.get('tag_name', '').lstrip('v')
- release_notes = release_data.get('body', '')
- release_date = release_data.get('published_at', '')
-
- # Find the plugin zip download URL
- download_url = ""
- for asset in release_data.get('assets', []):
- if asset.get('name', '').endswith('.zip'):
- download_url = asset.get('browser_download_url', '')
- break
-
- # Compare versions
- update_available = self._compare_versions(current_version, latest_version)
-
- return {
- "success": True,
- "update_available": update_available,
- "current_version": current_version,
- "latest_version": latest_version,
- "release_notes": release_notes,
- "release_date": release_date,
- "download_url": download_url
- }
-
- except Exception as e:
- decky.logger.error(f"Failed to fetch release info: {e}")
- return {
- "success": False,
- "error": f"Failed to check for updates: {str(e)}"
- }
-
- except Exception as e:
- return {
- "success": False,
- "error": f"Update check failed: {str(e)}"
- }
-
- async def download_plugin_update(self, download_url: str) -> Dict[str, Any]:
- """Download the plugin update zip file to ~/Downloads
-
- Args:
- download_url: URL to download the plugin zip from
-
- Returns:
- Dict containing download result:
- {
- "success": bool,
- "download_path": str,
- "error": str (if error occurred)
- }
- """
- try:
- # Create download path
- downloads_dir = Path.home() / "Downloads"
- downloads_dir.mkdir(exist_ok=True)
- download_path = downloads_dir / "decky-lsfg-vk.zip"
-
- # Remove existing file if it exists
- if download_path.exists():
- download_path.unlink()
-
- # Download the file
- decky.logger.info(f"Downloading plugin update from {download_url}")
-
- try:
- # Create SSL context that doesn't verify certificates
- # This is needed on Steam Deck where certificate verification often fails
- ssl_context = ssl.create_default_context()
- ssl_context.check_hostname = False
- ssl_context.verify_mode = ssl.CERT_NONE
-
- # Use urllib to download the file with SSL context
- with urllib.request.urlopen(download_url, context=ssl_context) as response:
- with open(download_path, 'wb') as f:
- f.write(response.read())
-
- # Verify the file was downloaded successfully
- if download_path.exists() and download_path.stat().st_size > 0:
- decky.logger.info(f"Plugin update downloaded successfully to {download_path}")
- return {
- "success": True,
- "download_path": str(download_path)
- }
- else:
- return {
- "success": False,
- "error": "Download completed but file is empty or missing"
- }
-
- except Exception as e:
- decky.logger.error(f"Download failed: {e}")
- return {
- "success": False,
- "error": f"Download failed: {str(e)}"
- }
-
- except Exception as e:
- return {
- "success": False,
- "error": f"Download preparation failed: {str(e)}"
- }
-
- def _compare_versions(self, current: str, latest: str) -> bool:
- """Compare two version strings to determine if an update is available
-
- Args:
- current: Current version string (e.g., "1.2.3")
- latest: Latest version string (e.g., "1.2.4")
-
- Returns:
- True if latest version is newer than current version
- """
- try:
- # Remove 'v' prefix if present and split by dots
- current_parts = current.lstrip('v').split('.')
- latest_parts = latest.lstrip('v').split('.')
-
- # Pad with zeros if needed to ensure equal length
- max_len = max(len(current_parts), len(latest_parts))
- current_parts.extend(['0'] * (max_len - len(current_parts)))
- latest_parts.extend(['0'] * (max_len - len(latest_parts)))
-
- # Compare each part numerically
- for i in range(max_len):
- try:
- current_num = int(current_parts[i])
- latest_num = int(latest_parts[i])
-
- if latest_num > current_num:
- return True
- elif latest_num < current_num:
- return False
- # If equal, continue to next part
- except ValueError:
- # If conversion fails, do string comparison
- if latest_parts[i] > current_parts[i]:
- return True
- elif latest_parts[i] < current_parts[i]:
- return False
-
- # All parts are equal
- return False
-
- except (IndexError, AttributeError, TypeError) as e:
- # If comparison fails, assume no update available
- self.configuration_service.log.warning(f"Version comparison failed: {e}")
- 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
@@ -522,7 +261,6 @@ class Plugin:
"explanation": "The lsfg script is created during installation and sets up the environment for the plugin"
}
- # File content methods
async def get_config_file_content(self) -> Dict[str, Any]:
"""Get the current config file content
@@ -612,7 +350,6 @@ class Plugin:
"error": str(e)
}
- # Flatpak management methods
async def check_flatpak_extension_status(self) -> Dict[str, Any]:
"""Check status of lsfg-vk Flatpak runtime extensions
@@ -673,9 +410,6 @@ class Plugin:
"""
return self.flatpak_service.remove_app_override(app_id)
- # Decky Loader lifecycle methods
-
- # Lifecycle methods
async def _main(self):
"""
Main entry point for the plugin.
@@ -696,13 +430,12 @@ class Plugin:
async def _uninstall(self):
"""
- Cleanup tasks when the plugin is uninstalled.
+ Called 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 and
- uninstalls any flatpak extensions.
+ Performs cleanup of plugin files and flatpak extensions.
"""
- decky.logger.info("decky-lsfg-vk plugin uninstalled - starting cleanup")
+ decky.logger.info("decky-lsfg-vk plugin being uninstalled")
# Clean up lsfg-vk files when the plugin is uninstalled
self.installation_service.cleanup_on_uninstall()
@@ -711,11 +444,9 @@ class Plugin:
try:
decky.logger.info("Checking for flatpak extensions to uninstall")
- # Get current extension status
extension_status = self.flatpak_service.get_extension_status()
if extension_status.get("success"):
- # Uninstall 23.08 runtime if installed
if extension_status.get("installed_23_08"):
decky.logger.info("Uninstalling lsfg-vk flatpak runtime 23.08")
result = self.flatpak_service.uninstall_extension("23.08")
@@ -724,7 +455,6 @@ class Plugin:
else:
decky.logger.warning(f"Failed to uninstall flatpak runtime 23.08: {result.get('error')}")
- # Uninstall 24.08 runtime if installed
if extension_status.get("installed_24_08"):
decky.logger.info("Uninstalling lsfg-vk flatpak runtime 24.08")
result = self.flatpak_service.uninstall_extension("24.08")
@@ -739,7 +469,6 @@ class Plugin:
except Exception as e:
decky.logger.error(f"Error during flatpak cleanup: {e}")
- # Don't fail the uninstall if flatpak cleanup fails
decky.logger.info("decky-lsfg-vk plugin uninstall cleanup completed")
@@ -752,21 +481,13 @@ class Plugin:
"""
decky.logger.info("Running decky-lsfg-vk plugin migrations")
- # Migrate logs from old location
- # ~/.config/decky-lossless-scaling-vk/lossless-scaling-vk.log -> decky.DECKY_LOG_DIR/lossless-scaling-vk.log
decky.migrate_logs(os.path.join(decky.DECKY_USER_HOME,
".config", "decky-lossless-scaling-vk", "lossless-scaling-vk.log"))
- # Migrate settings from old locations
- # ~/homebrew/settings/lossless-scaling-vk.json -> decky.DECKY_SETTINGS_DIR/lossless-scaling-vk.json
- # ~/.config/decky-lossless-scaling-vk/ -> decky.DECKY_SETTINGS_DIR/
decky.migrate_settings(
os.path.join(decky.DECKY_HOME, "settings", "lossless-scaling-vk.json"),
os.path.join(decky.DECKY_USER_HOME, ".config", "decky-lossless-scaling-vk"))
- # Migrate runtime data from old locations
- # ~/homebrew/lossless-scaling-vk/ -> decky.DECKY_RUNTIME_DIR/
- # ~/.local/share/decky-lossless-scaling-vk/ -> decky.DECKY_RUNTIME_DIR/
decky.migrate_runtime(
os.path.join(decky.DECKY_HOME, "lossless-scaling-vk"),
os.path.join(decky.DECKY_USER_HOME, ".local", "share", "decky-lossless-scaling-vk"))
diff --git a/shared_config.py b/shared_config.py
index 5b0a45d..997717b 100644
--- a/shared_config.py
+++ b/shared_config.py
@@ -18,14 +18,13 @@ class ConfigFieldType(str, Enum):
STRING = "string"
-# Canonical configuration schema - source of truth
CONFIG_SCHEMA_DEF = {
"dll": {
"name": "dll",
"fieldType": ConfigFieldType.STRING,
"default": "/games/Lossless Scaling/Lossless.dll",
"description": "specify where Lossless.dll is stored",
- "location": "toml" # where this field is stored/used
+ "location": "toml"
},
"no_fp16": {
@@ -81,7 +80,7 @@ CONFIG_SCHEMA_DEF = {
"fieldType": ConfigFieldType.INTEGER,
"default": 0,
"description": "base framerate cap for DirectX games before frame multiplier",
- "location": "script" # script-only field
+ "location": "script"
},
"enable_wow64": {
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 28eefa7..3dc2696 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -39,25 +39,20 @@ export function Content() {
const { isInstalling, isUninstalling, handleInstall, handleUninstall } = useInstallationActions();
- // Reload config when installation status changes
useEffect(() => {
if (isInstalled) {
loadLsfgConfig();
}
}, [isInstalled, loadLsfgConfig]);
- // Generic configuration change handler
const handleConfigChange = async (fieldName: keyof ConfigurationData, value: boolean | number | string) => {
- // If we have a current profile, update that profile specifically
if (currentProfile) {
const newConfig = { ...config, [fieldName]: value };
const result = await updateProfileConfig(currentProfile, newConfig);
if (result.success) {
- // Reload config to reflect the changes from the backend
await loadLsfgConfig();
}
} else {
- // Fallback to the original method for backward compatibility
await updateField(fieldName, value);
}
};
@@ -80,7 +75,6 @@ export function Content() {
return (
<PanelSection>
- {/* Show installation components at top when not fully installed */}
{!isInstalled && (
<>
<InstallationButton
@@ -100,7 +94,6 @@ export function Content() {
</>
)}
- {/* FPS multiplier controls stay above profile selection when installed */}
{isInstalled && (
<>
<PanelSectionRow>
@@ -126,7 +119,6 @@ export function Content() {
</>
)}
- {/* Profile Management - only show if installed */}
{isInstalled && (
<ProfileManagement
currentProfile={currentProfile}
@@ -137,7 +129,6 @@ export function Content() {
/>
)}
- {/* Configuration Section - only show if installed */}
{isInstalled && (
<ConfigurationSection
config={config}
@@ -145,7 +136,6 @@ export function Content() {
/>
)}
- {/* Clipboard buttons sit beside usage info for quick access */}
{isInstalled && (
<>
<SmartClipboardButton />
@@ -153,10 +143,8 @@ export function Content() {
</>
)}
- {/* Usage instructions - always visible for user guidance */}
<UsageInstructions config={config} />
- {/* Nerd Stuff Button */}
<PanelSectionRow>
<ButtonItem
layout="below"
@@ -166,7 +154,6 @@ export function Content() {
</ButtonItem>
</PanelSectionRow>
- {/* Flatpaks Button */}
<PanelSectionRow>
<ButtonItem
layout="below"
@@ -176,7 +163,6 @@ export function Content() {
</ButtonItem>
</PanelSectionRow>
- {/* Status and uninstall sit at bottom when installed to match desired layout */}
{isInstalled && (
<>
<StatusDisplay
diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx
index 7be3b2f..c90515a 100644
--- a/src/components/SmartClipboardButton.tsx
+++ b/src/components/SmartClipboardButton.tsx
@@ -9,7 +9,6 @@ export function SmartClipboardButton() {
const [isLoading, setIsLoading] = useState(false);
const [showSuccess, setShowSuccess] = useState(false);
- // Reset success state after 3 seconds
useEffect(() => {
if (showSuccess) {
const timer = setTimeout(() => {
@@ -38,10 +37,8 @@ export function SmartClipboardButton() {
const { success, verified } = await copyWithVerification(text);
if (success) {
- // Show success feedback in the button instead of toast
setShowSuccess(true);
if (!verified) {
- // Copy worked but verification failed - still show success
console.log('Copy verification failed but copy likely worked');
}
} else {
@@ -64,9 +61,7 @@ export function SmartClipboardButton() {
>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
{showSuccess ? (
- <FaCheck style={{
- color: "#4CAF50" // Green color for success
- }} />
+ <FaCheck style={{ color: "#4CAF50" }} />
) : isLoading ? (
<FaClipboard style={{
animation: "pulse 1s ease-in-out infinite",
diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts
index d7da4f5..6c6cf19 100644
--- a/src/config/configSchema.ts
+++ b/src/config/configSchema.ts
@@ -10,7 +10,6 @@ import type { ConfigurationData } from './generatedConfigSchema';
import { getDefaults } from './generatedConfigSchema';
import { updateLsfgConfig } from "../api/lsfgApi";
-// Re-export all auto-generated configuration constants
export {
ConfigFieldType,
ConfigField,
@@ -19,7 +18,6 @@ export {
getFieldNames,
getDefaults,
getFieldTypes,
- // Field name constants for type-safe access
DLL, NO_FP16, MULTIPLIER, FLOW_SCALE, PERFORMANCE_MODE, HDR_MODE,
EXPERIMENTAL_PRESENT_MODE, DXVK_FRAME_RATE, ENABLE_WOW64,
DISABLE_STEAMDECK_MODE, MANGOHUD_WORKAROUND, DISABLE_VKBASALT,
@@ -34,7 +32,6 @@ export class ConfigurationManager {
private static instance: ConfigurationManager;
private _config: ConfigurationData | null = null;
- // Callable methods for backend communication
private getConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("get_configuration");
private resetConfiguration = callable<[], { success: boolean; data?: ConfigurationData; error?: string }>("reset_configuration");
@@ -131,5 +128,4 @@ export class ConfigurationManager {
}
}
-// Export singleton instance
export const configManager = ConfigurationManager.getInstance();
diff --git a/src/hooks/useInstallationActions.ts b/src/hooks/useInstallationActions.ts
index 18de6b5..f184145 100644
--- a/src/hooks/useInstallationActions.ts
+++ b/src/hooks/useInstallationActions.ts
@@ -23,7 +23,7 @@ export function useInstallationActions() {
const result = await installLsfgVk();
if (result.success) {
setIsInstalled(true);
- setInstallationStatus("lsfg-vk installed successfully!");
+ setInstallationStatus("lsfg-vk installed");
showInstallSuccessToast();
// Reload lsfg config after installation
diff --git a/src/hooks/useLsfgHooks.ts b/src/hooks/useLsfgHooks.ts
index e5dea63..adc18ba 100644
--- a/src/hooks/useLsfgHooks.ts
+++ b/src/hooks/useLsfgHooks.ts
@@ -71,7 +71,6 @@ export function useDllDetection() {
}
export function useLsfgConfig() {
- // Use centralized configuration for initial state
const [config, setConfig] = useState<ConfigurationData>(() => ConfigurationManager.getDefaults());
const loadLsfgConfig = useCallback(async () => {
@@ -114,7 +113,7 @@ export function useLsfgConfig() {
useEffect(() => {
loadLsfgConfig();
- }, []); // Empty dependency array to prevent infinite loop
+ }, []);
return {
config,
diff --git a/src/index.tsx b/src/index.tsx
index bbe4cd3..36945ba 100755
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -7,17 +7,11 @@ export default definePlugin(() => {
console.log("decky-lsfg-vk plugin initializing");
return {
- // The name shown in various decky menus
name: "Decky LSFG-VK",
- // The element displayed at the top of your plugin's menu
titleView: <div className={staticClasses.Title}>Decky LSFG-VK</div>,
- // Always render to retain state when panel is toggled
alwaysRender: true,
- // The content of your plugin's menu
content: <Content />,
- // The icon displayed in the plugin list
icon: <GiPlasticDuck />,
- // The function triggered when your plugin unloads
onDismount() {
console.log("decky-lsfg-vk unloading");
}
diff --git a/src/utils/clipboardUtils.ts b/src/utils/clipboardUtils.ts
index 2d480fc..8a04caa 100644
--- a/src/utils/clipboardUtils.ts
+++ b/src/utils/clipboardUtils.ts
@@ -7,7 +7,6 @@
* This is especially important in gaming mode where clipboard APIs may behave differently
*/
export async function copyToClipboard(text: string): Promise<boolean> {
- // Use the proven input simulation method
const tempInput = document.createElement('input');
tempInput.value = text;
tempInput.style.position = 'absolute';
@@ -15,18 +14,15 @@ export async function copyToClipboard(text: string): Promise<boolean> {
document.body.appendChild(tempInput);
try {
- // Focus and select the text
tempInput.focus();
tempInput.select();
- // Try copying using execCommand first (most reliable in gaming mode)
let copySuccess = false;
try {
if (document.execCommand('copy')) {
copySuccess = true;
}
} catch (e) {
- // If execCommand fails, try navigator.clipboard as fallback
try {
await navigator.clipboard.writeText(text);
copySuccess = true;
@@ -37,7 +33,6 @@ export async function copyToClipboard(text: string): Promise<boolean> {
return copySuccess;
} finally {
- // Clean up
document.body.removeChild(tempInput);
}
}
@@ -50,7 +45,6 @@ export async function verifyCopy(expectedText: string): Promise<boolean> {
const readBack = await navigator.clipboard.readText();
return readBack === expectedText;
} catch (e) {
- // Verification not available, assume success
return true;
}
}