summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE2
-rw-r--r--package.json7
-rw-r--r--py_modules/lsfg_vk/base_service.py8
-rw-r--r--py_modules/lsfg_vk/config_schema.py8
-rw-r--r--py_modules/lsfg_vk/constants.py1
-rw-r--r--py_modules/lsfg_vk/flatpak_service.py51
-rw-r--r--py_modules/lsfg_vk/installation.py7
-rw-r--r--py_modules/lsfg_vk/plugin.py19
-rw-r--r--src/api/lsfgApi.ts1
-rw-r--r--src/components/Content.tsx4
-rw-r--r--src/components/FlatpaksModal.tsx40
-rw-r--r--src/components/index.ts2
12 files changed, 102 insertions, 48 deletions
diff --git a/LICENSE b/LICENSE
index 0bf1806..dfff591 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
BSD 3-Clause License
-Copyright (c) 2025, Kurt Himebauch
+Copyright (c) 2025, Kurt Himebauch (JSON Derulo)
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/package.json b/package.json
index 9ffcd8c..4cc6b40 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "decky-lsfg-vk",
- "version": "0.11.0",
+ "version": "0.11.2",
"description": "Use Lossless Scaling on the Steam Deck using the lsfg-vk vulkan layer",
"type": "module",
"scripts": {
@@ -58,6 +58,11 @@
"name": "org.freedesktop.Platform.VulkanLayer.lsfg_vk_24.08.flatpak",
"url": "https://github.com/PancakeTAS/lsfg-vk/releases/download/v0.9.0/org.freedesktop.Platform.VulkanLayer.lsfg_vk_24.08.flatpak",
"sha256hash": "70ea6d9d2a8adad63b8e5c2c3d94a636764ad4d0266ce9f37cb9dc7943d8fc3a"
+ },
+ {
+ "name": "org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak",
+ "url": "https://github.com/PancakeTAS/lsfg-vk/releases/download/v1.0.0/org.freedesktop.Platform.VulkanLayer.lsfg_vk_25.08.flatpak",
+ "sha256hash": "0651bda96751ef0f1314a5179585926a0cd354476790ca2616662c39fe6fae54"
}
],
"pnpm": {
diff --git a/py_modules/lsfg_vk/base_service.py b/py_modules/lsfg_vk/base_service.py
index b684ec9..9c3dec3 100644
--- a/py_modules/lsfg_vk/base_service.py
+++ b/py_modules/lsfg_vk/base_service.py
@@ -2,11 +2,14 @@
Base service class with common functionality.
"""
+import logging
import os
import shutil
from pathlib import Path
from typing import Any, Optional, TypeVar, Dict
+import decky
+
from .constants import LOCAL_LIB, LOCAL_SHARE_BASE, VULKAN_LAYER_DIR, SCRIPT_NAME, CONFIG_DIR, CONFIG_FILENAME
# Generic type for response dictionaries
@@ -23,7 +26,6 @@ class BaseService:
logger: Logger instance, defaults to decky.logger if None
"""
if logger is None:
- import decky
self.log = decky.logger
else:
self.log = logger
@@ -90,8 +92,8 @@ class BaseService:
path.chmod(mode)
self.log.info(f"Wrote to {path}")
- except Exception:
- self.log.error(f"Failed to write to {path}")
+ except (OSError, IOError, PermissionError) as e:
+ self.log.error(f"Failed to write to {path}: {e}")
raise
def _success_response(self, response_type: type, message: str = "", **kwargs) -> Any:
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index 66aeb69..807c798 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -8,6 +8,7 @@ This module defines the complete configuration structure for lsfg-vk, managing T
- Type definitions
"""
+import logging
import re
import sys
from typing import TypedDict, Dict, Any, Union, cast, List
@@ -124,9 +125,9 @@ class ConfigurationManager:
dll_result = dll_detection_service.check_lossless_scaling_dll()
if dll_result.get("detected") and dll_result.get("path"):
defaults["dll"] = dll_result["path"]
- except Exception:
+ except (OSError, IOError, KeyError, TypeError) as e:
# If detection fails, keep empty default
- pass
+ logging.getLogger(__name__).debug(f"DLL detection failed: {e}")
# If DLL path is still empty, use a reasonable fallback
if not defaults["dll"]:
@@ -403,8 +404,9 @@ class ConfigurationManager:
global_config=global_config
)
- except Exception:
+ except (ValueError, KeyError, TypeError, AttributeError) as e:
# If parsing fails completely, return default profile structure
+ logging.getLogger(__name__).warning(f"Failed to parse TOML profiles, using defaults: {e}")
return ProfileData(
current_profile=DEFAULT_PROFILE_NAME,
profiles={DEFAULT_PROFILE_NAME: ConfigurationManager.get_defaults()},
diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py
index 614e2fc..79d8585 100644
--- a/py_modules/lsfg_vk/constants.py
+++ b/py_modules/lsfg_vk/constants.py
@@ -20,6 +20,7 @@ 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"
diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py
index cdf4a55..0e3977f 100644
--- a/py_modules/lsfg_vk/flatpak_service.py
+++ b/py_modules/lsfg_vk/flatpak_service.py
@@ -9,7 +9,7 @@ from typing import Dict, Any, List, Optional
from .base_service import BaseService
from .constants import (
- FLATPAK_23_08_FILENAME, FLATPAK_24_08_FILENAME, BIN_DIR, CONFIG_DIR
+ FLATPAK_23_08_FILENAME, FLATPAK_24_08_FILENAME, FLATPAK_25_08_FILENAME, BIN_DIR, CONFIG_DIR
)
from .types import BaseResponse
@@ -17,10 +17,11 @@ from .types import BaseResponse
class FlatpakExtensionStatus(BaseResponse):
"""Response for Flatpak extension status"""
def __init__(self, success: bool = False, message: str = "", error: str = "",
- installed_23_08: bool = False, installed_24_08: bool = False):
+ installed_23_08: bool = False, installed_24_08: bool = False, installed_25_08: bool = False):
super().__init__(success, message, error)
self.installed_23_08 = installed_23_08
self.installed_24_08 = installed_24_08
+ self.installed_25_08 = installed_25_08
class FlatpakAppInfo(BaseResponse):
@@ -48,17 +49,11 @@ class FlatpakService(BaseService):
super().__init__(logger)
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.flatpak_command = None # Will be set when flatpak is detected
- def __init__(self, logger=None):
- super().__init__(logger)
- 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
def _get_clean_env(self):
"""Get a clean environment without PyInstaller's bundled libraries"""
- import os
-
# Create a clean environment without PyInstaller's bundled libraries
env = os.environ.copy()
@@ -96,8 +91,6 @@ class FlatpakService(BaseService):
def check_flatpak_available(self) -> bool:
"""Check if flatpak command is available and store the working command"""
- import os
-
# 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')}")
@@ -137,7 +130,7 @@ class FlatpakService(BaseService):
self.log.error(error_msg)
return self._error_response(FlatpakExtensionStatus,
error_msg,
- installed_23_08=False, installed_24_08=False)
+ installed_23_08=False, installed_24_08=False, installed_25_08=False)
# Get list of installed runtimes
result = self._run_flatpak_command(
@@ -147,10 +140,11 @@ class FlatpakService(BaseService):
installed_runtimes = result.stdout
- # Check for both versions by looking for the base extension name and version
+ # 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
+ installed_25_08 = False
for line in installed_runtimes.split('\n'):
if base_extension_name in line:
@@ -158,12 +152,16 @@ class FlatpakService(BaseService):
installed_23_08 = True
elif "24.08" in line:
installed_24_08 = True
+ elif "25.08" in line:
+ installed_25_08 = True
status_msg = []
if installed_23_08:
status_msg.append("23.08 runtime extension installed")
if installed_24_08:
status_msg.append("24.08 runtime extension installed")
+ if installed_25_08:
+ status_msg.append("25.08 runtime extension installed")
if not status_msg:
status_msg.append("No lsfg-vk runtime extensions installed")
@@ -171,26 +169,32 @@ class FlatpakService(BaseService):
return self._success_response(FlatpakExtensionStatus,
"; ".join(status_msg),
installed_23_08=installed_23_08,
- installed_24_08=installed_24_08)
+ installed_24_08=installed_24_08,
+ installed_25_08=installed_25_08)
except subprocess.CalledProcessError as e:
error_msg = f"Error checking Flatpak extensions: {e.stderr if e.stderr else str(e)}"
self.log.error(error_msg)
return self._error_response(FlatpakExtensionStatus, error_msg,
- installed_23_08=False, installed_24_08=False)
+ installed_23_08=False, installed_24_08=False, installed_25_08=False)
def install_extension(self, version: str) -> BaseResponse:
"""Install a specific version of the lsfg-vk Flatpak extension"""
try:
- if version not in ["23.08", "24.08"]:
- return self._error_response(BaseResponse, "Invalid version. Must be '23.08' or '24.08'")
+ if version not in ["23.08", "24.08", "25.08"]:
+ return self._error_response(BaseResponse, "Invalid version. Must be '23.08', '24.08', or '25.08'")
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
- filename = FLATPAK_23_08_FILENAME if version == "23.08" else FLATPAK_24_08_FILENAME
+ if version == "23.08":
+ filename = FLATPAK_23_08_FILENAME
+ elif version == "24.08":
+ filename = FLATPAK_24_08_FILENAME
+ else: # 25.08
+ filename = FLATPAK_25_08_FILENAME
flatpak_path = plugin_dir / BIN_DIR / filename
if not flatpak_path.exists():
@@ -218,13 +222,18 @@ class FlatpakService(BaseService):
def uninstall_extension(self, version: str) -> BaseResponse:
"""Uninstall a specific version of the lsfg-vk Flatpak extension"""
try:
- if version not in ["23.08", "24.08"]:
- return self._error_response(BaseResponse, "Invalid version. Must be '23.08' or '24.08'")
+ if version not in ["23.08", "24.08", "25.08"]:
+ return self._error_response(BaseResponse, "Invalid version. Must be '23.08', '24.08', or '25.08'")
if not self.check_flatpak_available():
return self._error_response(BaseResponse, "Flatpak is not available on this system")
- extension_id = self.extension_id_23_08 if version == "23.08" else self.extension_id_24_08
+ if version == "23.08":
+ extension_id = self.extension_id_23_08
+ elif version == "24.08":
+ extension_id = self.extension_id_24_08
+ else: # 25.08
+ extension_id = self.extension_id_25_08
# Uninstall the extension
result = self._run_flatpak_command(
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index 3b47be0..763154f 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -4,6 +4,7 @@ Installation service for lsfg-vk.
import os
import shutil
+import traceback
import zipfile
import tempfile
import json
@@ -188,8 +189,9 @@ class InstallationService(BaseService):
final_config = ConfigurationManager.parse_toml_content(final_content)
if final_config.get(DLL):
self.log.info(f"Configured DLL path: {final_config[DLL]}")
- except Exception:
- pass # Don't fail installation if we can't log the DLL path
+ except (OSError, IOError, ValueError, KeyError) as e:
+ # Don't fail installation if we can't log the DLL path
+ self.log.debug(f"Could not log DLL path: {e}")
def _create_lsfg_launch_script(self) -> None:
"""Create the ~/lsfg launch script for easier game setup"""
@@ -328,7 +330,6 @@ class InstallationService(BaseService):
except Exception as e:
self.log.error(f"Error cleaning up lsfg-vk files during uninstall: {str(e)}")
- import traceback
self.log.error(f"Traceback: {traceback.format_exc()}")
def _merge_config_with_defaults(self, existing_profile_data, dll_service):
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index ed4b552..7731558 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -14,6 +14,8 @@ import hashlib
from typing import Dict, Any
from pathlib import Path
+import decky
+
from .installation import InstallationService
from .dll_detection import DllDetectionService
from .configuration import ConfigurationService
@@ -200,8 +202,9 @@ class Plugin:
return schema_data
- except Exception:
+ 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(),
"field_types": {name: field_type.value for name, field_type in ConfigurationManager.get_field_types().items()},
@@ -325,8 +328,6 @@ class Plugin:
}
"""
try:
- import decky
-
# Read current version from package.json
package_json_path = Path(decky.DECKY_PLUGIN_DIR) / "package.json"
current_version = "0.0.0"
@@ -411,8 +412,6 @@ class Plugin:
}
"""
try:
- import decky
-
# Create download path
downloads_dir = Path.home() / "Downloads"
downloads_dir.mkdir(exist_ok=True)
@@ -504,8 +503,9 @@ class Plugin:
# All parts are equal
return False
- except Exception:
+ 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
@@ -580,7 +580,6 @@ class Plugin:
}
except Exception as e:
- import decky
decky.logger.error(f"Error reading launch script: {e}")
return {
"success": False,
@@ -594,7 +593,6 @@ class Plugin:
Dict with exists status and directory path
"""
try:
- import decky
home_path = Path(decky.DECKY_USER_HOME)
fgmod_path = home_path / "fgmod"
@@ -607,7 +605,6 @@ class Plugin:
}
except Exception as e:
- import decky
decky.logger.error(f"Error checking fgmod directory: {e}")
return {
"success": False,
@@ -686,7 +683,6 @@ class Plugin:
This method is called by Decky Loader when the plugin is loaded.
Any initialization code should go here.
"""
- import decky
decky.logger.info("decky-lsfg-vk plugin loaded")
async def _unload(self):
@@ -696,7 +692,6 @@ class Plugin:
This method is called by Decky Loader when the plugin is being unloaded.
Any cleanup code should go here.
"""
- import decky
decky.logger.info("decky-lsfg-vk plugin unloaded")
async def _uninstall(self):
@@ -707,7 +702,6 @@ class Plugin:
It automatically cleans up any lsfg-vk files that were installed and
uninstalls any flatpak extensions.
"""
- import decky
decky.logger.info("decky-lsfg-vk plugin uninstalled - starting cleanup")
# Clean up lsfg-vk files when the plugin is uninstalled
@@ -756,7 +750,6 @@ class Plugin:
This method is called by Decky Loader for plugin migrations.
Currently migrates logs, settings, and runtime data from old locations.
"""
- import decky
decky.logger.info("Running decky-lsfg-vk plugin migrations")
# Migrate logs from old location
diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts
index d08cd42..dda6c23 100644
--- a/src/api/lsfgApi.ts
+++ b/src/api/lsfgApi.ts
@@ -103,6 +103,7 @@ export interface FlatpakExtensionStatus {
error?: string;
installed_23_08: boolean;
installed_24_08: boolean;
+ installed_25_08: boolean;
}
export interface FlatpakApp {
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
index 978f4ed..d3c04d3 100644
--- a/src/components/Content.tsx
+++ b/src/components/Content.tsx
@@ -13,7 +13,7 @@ import { ClipboardButton } from "./ClipboardButton";
import { SmartClipboardButton } from "./SmartClipboardButton";
import { FgmodClipboardButton } from "./FgmodClipboardButton";
// import { ClipboardDisplay } from "./ClipboardDisplay";
-import { PluginUpdateChecker } from "./PluginUpdateChecker";
+// import { PluginUpdateChecker } from "./PluginUpdateChecker";
import { NerdStuffModal } from "./NerdStuffModal";
import FlatpaksModal from "./FlatpaksModal";
import { ConfigurationData } from "../config/configSchema";
@@ -139,7 +139,7 @@ export function Content() {
<ClipboardButton />
{/* Plugin Update Checker */}
- <PluginUpdateChecker />
+ {/* <PluginUpdateChecker /> */}
{/* Show installation components at bottom when fully installed */}
{isInstalled && (
diff --git a/src/components/FlatpaksModal.tsx b/src/components/FlatpaksModal.tsx
index bd81013..ae0c333 100644
--- a/src/components/FlatpaksModal.tsx
+++ b/src/components/FlatpaksModal.tsx
@@ -216,6 +216,46 @@ const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => {
</ButtonItem>
</Field>
</PanelSectionRow>
+
+ {/* 25.08 Runtime */}
+ <PanelSectionRow>
+ <Field
+ label="Runtime 25.08"
+ description={extensionStatus.installed_25_08 ? "Installed" : "Not installed"}
+ icon={extensionStatus.installed_25_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />}
+ >
+ <ButtonItem
+ layout="below"
+ onClick={() => {
+ const operation = extensionStatus.installed_25_08 ? 'uninstall' : 'install';
+ const action = () => handleExtensionOperation(operation, '25.08');
+
+ if (operation === 'uninstall') {
+ confirmOperation(
+ action,
+ 'Uninstall Runtime Extension',
+ 'Are you sure you want to uninstall the 25.08 runtime extension?'
+ );
+ } else {
+ action();
+ }
+ }}
+ disabled={operationInProgress === 'install-25.08' || operationInProgress === 'uninstall-25.08'}
+ >
+ {operationInProgress === 'install-25.08' || operationInProgress === 'uninstall-25.08' ? (
+ <Spinner />
+ ) : extensionStatus.installed_25_08 ? (
+ <>
+ <FaTrash /> Uninstall
+ </>
+ ) : (
+ <>
+ <FaDownload /> Install
+ </>
+ )}
+ </ButtonItem>
+ </Field>
+ </PanelSectionRow>
</>
) : (
<PanelSectionRow>
diff --git a/src/components/index.ts b/src/components/index.ts
index 50480be..260d192 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -8,7 +8,7 @@ export { WikiButton } from "./WikiButton";
export { SmartClipboardButton } from "./SmartClipboardButton";
export { FgmodClipboardButton } from "./FgmodClipboardButton";
// export { ClipboardDisplay } from "./ClipboardDisplay";
-export { PluginUpdateChecker } from "./PluginUpdateChecker";
+// export { PluginUpdateChecker } from "./PluginUpdateChecker";
export { NerdStuffModal } from "./NerdStuffModal";
export { default as FlatpaksModal } from "./FlatpaksModal";
export { ProfileManagement } from "./ProfileManagement";