summaryrefslogtreecommitdiff
path: root/py_modules
diff options
context:
space:
mode:
authorxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-06 22:04:54 -0400
committerxXJSONDeruloXx <danielhimebauch@gmail.com>2026-09-06 22:04:54 -0400
commit7bc5f685186b1ff03ce0f7c99e7bec411beabaeb (patch)
treecf94a5752af7f45f6d4a87e89546705f32096766 /py_modules
parent170d183fdafc1ec1a686c006fd6a2431c1f30c71 (diff)
downloaddecky-lsfg-vk-7bc5f685186b1ff03ce0f7c99e7bec411beabaeb.tar.gz
decky-lsfg-vk-7bc5f685186b1ff03ce0f7c99e7bec411beabaeb.zip
feat: make ui suck less, frfr
Diffstat (limited to 'py_modules')
-rw-r--r--py_modules/lsfg_vk/config_schema.py52
-rw-r--r--py_modules/lsfg_vk/configuration.py56
-rw-r--r--py_modules/lsfg_vk/installation.py6
-rw-r--r--py_modules/lsfg_vk/plugin.py128
-rw-r--r--py_modules/lsfg_vk/types.py31
5 files changed, 28 insertions, 245 deletions
diff --git a/py_modules/lsfg_vk/config_schema.py b/py_modules/lsfg_vk/config_schema.py
index 4ae1f4c..675ab61 100644
--- a/py_modules/lsfg_vk/config_schema.py
+++ b/py_modules/lsfg_vk/config_schema.py
@@ -4,18 +4,14 @@ import json
import sys
import tomllib
from pathlib import Path
-from typing import Any, Dict, TypedDict, cast
+from typing import Any, Dict, TypedDict
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
-DEFAULT_PROFILE_NAME = "default"
ConfigurationData = Dict[str, Any]
class ProfileData(TypedDict):
- # Internal compatibility field; the public API no longer exposes a
- # currently selected profile.
- current_profile: str
profiles: Dict[str, Dict[str, Any]]
global_config: Dict[str, Any]
@@ -58,19 +54,6 @@ class ConfigurationManager:
return {**GLOBAL_DEFAULTS, **PROFILE_DEFAULTS}
@staticmethod
- def get_field_names() -> list[str]:
- return list(ConfigurationManager.get_defaults())
-
- @staticmethod
- def get_field_types() -> Dict[str, str]:
- return {
- "dll": "string", "no_fp16": "boolean", "active_in": "array",
- "pacing_mode": "string", "multiplier": "integer", "flow_scale": "float",
- "performance_mode": "boolean", "override_present_mode": "boolean",
- "preserve_swapchain_image_count": "boolean",
- }
-
- @staticmethod
def validate_config(config: Dict[str, Any]) -> Dict[str, Any]:
result = {**PROFILE_DEFAULTS, **GLOBAL_DEFAULTS}
result.update({key: value for key, value in config.items() if key in result})
@@ -111,15 +94,6 @@ class ConfigurationManager:
return ConfigurationManager.validate_config(raw)
@staticmethod
- def generate_toml_content(config: Dict[str, Any]) -> str:
- data: ProfileData = {
- "current_profile": DEFAULT_PROFILE_NAME,
- "profiles": {DEFAULT_PROFILE_NAME: dict(config)},
- "global_config": {"dll": config.get("dll", ""), "no_fp16": config.get("no_fp16", False)},
- }
- return ConfigurationManager.generate_toml_content_multi_profile(data)
-
- @staticmethod
def generate_toml_content_multi_profile(profile_data: ProfileData) -> str:
global_config = {**GLOBAL_DEFAULTS, **profile_data.get("global_config", {})}
lines = ["version = 2", "", "[global]"]
@@ -127,7 +101,9 @@ class ConfigurationManager:
if dll:
lines.append(f"dll = {_toml_value(dll)}")
lines.append(f"allow_fp16 = {_toml_value(not bool(global_config.get('no_fp16', False)))}")
- profiles = sorted(profile_data["profiles"].items(), key=lambda item: (item[0] != DEFAULT_PROFILE_NAME, item[0]))
+ profiles = sorted(profile_data["profiles"].items())
+ if not profiles:
+ profiles = [("", {})]
for name, raw in profiles:
config = ConfigurationManager.validate_config({**raw, **global_config})
lines.extend(["", "[[profile]]", f"name = {_toml_value(name)}"])
@@ -157,16 +133,11 @@ class ConfigurationManager:
profiles: Dict[str, Dict[str, Any]] = {}
source_profiles = data.get("game", []) if version == 1 else data.get("profile", [])
for profile in source_profiles:
- name = str(profile.get("exe" if version == 1 else "name", DEFAULT_PROFILE_NAME))
- profiles[name] = ConfigurationManager._config_from_profile(profile, global_config)
- if not profiles:
- profiles[DEFAULT_PROFILE_NAME] = ConfigurationManager.validate_config(global_config)
- elif DEFAULT_PROFILE_NAME not in profiles:
- source = profiles.get("decky-lsfg-vk", next(iter(profiles.values())))
- profiles[DEFAULT_PROFILE_NAME] = {**source, "active_in": []}
- if profiles.get("decky-lsfg-vk", {}).get("active_in", []) == []:
- profiles.pop("decky-lsfg-vk", None)
- return {"current_profile": DEFAULT_PROFILE_NAME, "profiles": profiles, "global_config": global_config}
+ name = str(profile.get("exe" if version == 1 else "name", ""))
+ config = ConfigurationManager._config_from_profile(profile, global_config)
+ if config["active_in"]:
+ profiles[name] = config
+ return {"profiles": profiles, "global_config": global_config}
@staticmethod
def is_legacy_v1(content: str) -> bool:
@@ -174,8 +145,3 @@ class ConfigurationManager:
return tomllib.loads(content).get("version") == 1
except tomllib.TOMLDecodeError:
return False
-
- @staticmethod
- def parse_toml_content(content: str) -> Dict[str, Any]:
- data = ConfigurationManager.parse_toml_content_multi_profile(content)
- return cast(Dict[str, Any], data["profiles"][DEFAULT_PROFILE_NAME])
diff --git a/py_modules/lsfg_vk/configuration.py b/py_modules/lsfg_vk/configuration.py
index a4ee2cc..bec3828 100644
--- a/py_modules/lsfg_vk/configuration.py
+++ b/py_modules/lsfg_vk/configuration.py
@@ -2,7 +2,7 @@ import re
from typing import Any, Dict
from .base_service import BaseService
-from .config_schema import ConfigurationManager, DEFAULT_PROFILE_NAME, ProfileData
+from .config_schema import ConfigurationManager, ProfileData
from .runtime_service import RuntimeService
@@ -14,8 +14,7 @@ class ConfigurationService(BaseService):
self.runtime_service = runtime_service or RuntimeService(logger=self.log)
def _default_data(self) -> ProfileData:
- defaults = ConfigurationManager.validate_config({})
- return {"current_profile": DEFAULT_PROFILE_NAME, "profiles": {DEFAULT_PROFILE_NAME: defaults}, "global_config": {"dll": "", "no_fp16": False}}
+ return {"profiles": {}, "global_config": {"dll": "", "no_fp16": False}}
def _get_profile_data(self) -> ProfileData:
if not self.config_file_path.exists():
@@ -32,8 +31,6 @@ class ConfigurationService(BaseService):
name = str(game_name).strip()
if not name:
raise ValueError("game name is required")
- if name == DEFAULT_PROFILE_NAME:
- name = f"{name} ({appid})"
existing = data["profiles"].get(name)
if existing is not None and str(appid) not in existing.get("active_in", []):
name = f"{name} ({appid})"
@@ -54,14 +51,6 @@ class ConfigurationService(BaseService):
def _public_config(config: Dict[str, Any]) -> Dict[str, Any]:
return ConfigurationManager.validate_config(config)
- def get_config(self) -> Dict[str, Any]:
- try:
- data = self._get_profile_data()
- return self._success_response(dict, config=self._public_config(data["profiles"][DEFAULT_PROFILE_NAME]))
- except Exception as error:
- self.log.error(f"Error reading lsfg config: {error}")
- return self._error_response(dict, str(error), config=None)
-
def get_game_configs(self) -> Dict[str, Any]:
try:
data = self._get_profile_data()
@@ -71,26 +60,25 @@ class ConfigurationService(BaseService):
if len(active_in) != 1 or not re.fullmatch(r"-?[0-9]+", str(active_in[0])):
continue
games.append({"appid": str(active_in[0]), "profile": name, "config": self._public_config(raw)})
- return self._success_response(dict, default=self._public_config(data["profiles"][DEFAULT_PROFILE_NAME]), games=games)
+ return self._success_response(dict, global_config=dict(data["global_config"]), games=games)
except Exception as error:
self.log.error(f"Error reading game configs: {error}")
- return self._error_response(dict, str(error), default=None, games=[])
-
- def get_game_config(self, appid: str) -> Dict[str, Any]:
- try:
- data = self._get_profile_data()
- _, profile = self._profile_for_appid(data, appid)
- return self._success_response(dict, appid=str(appid), exists=profile is not None, config=self._public_config(profile or data["profiles"][DEFAULT_PROFILE_NAME]))
- except Exception as error:
- return self._error_response(dict, str(error), appid=str(appid), exists=False, config=None)
+ return self._error_response(dict, str(error), games=[])
def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]:
try:
data = self._get_profile_data()
old_name, _ = self._profile_for_appid(data, appid)
name = self._profile_name(data, appid, game_name)
- validated = self._public_config(config)
+ merged_config = {**data["global_config"], **config}
+ if not config.get("dll"):
+ merged_config["dll"] = data["global_config"].get("dll", "")
+ validated = self._public_config(merged_config)
validated["active_in"] = [str(appid)]
+ data["global_config"] = {
+ "dll": validated["dll"],
+ "no_fp16": validated["no_fp16"],
+ }
if old_name and old_name != name:
data["profiles"].pop(old_name, None)
data["profiles"][name] = validated
@@ -106,27 +94,15 @@ class ConfigurationService(BaseService):
if name:
data["profiles"].pop(name, None)
self._save_profile_data(data)
- return self._success_response(dict, appid=str(appid), config=self._public_config(data["profiles"][DEFAULT_PROFILE_NAME]))
+ return self._success_response(dict, appid=str(appid), exists=False)
except Exception as error:
return self._error_response(dict, str(error), appid=str(appid), config=None)
def reset_all_game_configs(self) -> Dict[str, Any]:
try:
data = self._get_profile_data()
- data["profiles"] = {DEFAULT_PROFILE_NAME: data["profiles"][DEFAULT_PROFILE_NAME]}
- self._save_profile_data(data)
- return self._success_response(dict, default=self._public_config(data["profiles"][DEFAULT_PROFILE_NAME]), games=[])
- except Exception as error:
- return self._error_response(dict, str(error), default=None, games=[])
-
- def update_config_from_dict(self, config: Dict[str, Any]) -> Dict[str, Any]:
- try:
- data = self._get_profile_data()
- validated = self._public_config(config)
- validated["active_in"] = []
- data["profiles"][DEFAULT_PROFILE_NAME] = validated
- data["global_config"] = {"dll": validated.get("dll", ""), "no_fp16": validated.get("no_fp16", False)}
+ data["profiles"] = {}
self._save_profile_data(data)
- return self._success_response(dict, config=validated)
+ return self._success_response(dict, global_config=dict(data["global_config"]), games=[])
except Exception as error:
- return self._error_response(dict, str(error), config=None)
+ return self._error_response(dict, str(error), games=[])
diff --git a/py_modules/lsfg_vk/installation.py b/py_modules/lsfg_vk/installation.py
index 0a728c7..09bd3a3 100644
--- a/py_modules/lsfg_vk/installation.py
+++ b/py_modules/lsfg_vk/installation.py
@@ -7,7 +7,7 @@ from pathlib import Path
from typing import Dict
from .base_service import BaseService
-from .config_schema import ConfigurationManager, DEFAULT_PROFILE_NAME, ProfileData
+from .config_schema import ConfigurationManager, ProfileData
from .constants import (
ARCHIVE_FILENAME,
BIN_DIR,
@@ -141,8 +141,7 @@ class InstallationService(BaseService):
else:
default = dict(ConfigurationManager.get_defaults())
profile_data = ProfileData(
- current_profile=DEFAULT_PROFILE_NAME,
- profiles={DEFAULT_PROFILE_NAME: default},
+ profiles={},
global_config={
"dll": default.get("dll", ""),
"no_fp16": default.get("no_fp16", False),
@@ -155,7 +154,6 @@ class InstallationService(BaseService):
profile_data["profiles"][profile_name] = ConfigurationManager.validate_config(
{**defaults, **raw_profile, **profile_data["global_config"]}
)
- profile_data["current_profile"] = DEFAULT_PROFILE_NAME
return profile_data
def _resolve_dll_path(self, profile_data: ProfileData) -> bool:
diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py
index 8c788a8..6977b9c 100644
--- a/py_modules/lsfg_vk/plugin.py
+++ b/py_modules/lsfg_vk/plugin.py
@@ -7,13 +7,11 @@ Vulkan layer for frame generation on Steam Deck.
import os
from typing import Dict, Any
-from pathlib import Path
import decky
from .installation import InstallationService
from .configuration import ConfigurationService
-from .config_schema import ConfigurationManager
from .flatpak_service import FlatpakService
from .runtime_service import RuntimeService
from .steam_service import SteamService
@@ -63,46 +61,12 @@ class Plugin:
"""
return self.installation_service.uninstall()
- async def get_lsfg_config(self) -> Dict[str, Any]:
- """Read current lsfg script configuration
-
- Returns:
- ConfigurationResponse dict with current configuration or error
- """
- return self.configuration_service.get_config()
-
- async def get_config_schema(self) -> Dict[str, Any]:
- """Get configuration schema information for frontend
-
- Returns:
- Dict with field names, types, defaults, and profile information
- """
- return {
- "field_names": ConfigurationManager.get_field_names(),
- "field_types": ConfigurationManager.get_field_types(),
- "defaults": ConfigurationManager.get_defaults(),
- }
-
- async def update_lsfg_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
- """Update lsfg TOML configuration using object-based API (single source of truth)
-
- Args:
- config: Configuration data dictionary containing all settings
-
- Returns:
- ConfigurationResponse dict with success status
- """
- return self.configuration_service.update_config_from_dict(config)
-
async def get_game_configs(self) -> Dict[str, Any]:
return self.configuration_service.get_game_configs()
async def get_installed_games(self) -> Dict[str, Any]:
return self.steam_service.get_installed_games()
- async def get_game_config(self, appid: str) -> Dict[str, Any]:
- return self.configuration_service.get_game_config(appid)
-
async def update_game_config(self, appid: str, game_name: str, config: Dict[str, Any]) -> Dict[str, Any]:
return self.configuration_service.update_game_config(appid, game_name, config)
@@ -112,72 +76,6 @@ class Plugin:
async def reset_all_game_configs(self) -> Dict[str, Any]:
return self.configuration_service.reset_all_game_configs()
- async def _legacy_get_profiles(self) -> Dict[str, Any]:
- """Get list of all profiles and current profile
-
- Returns:
- ProfilesResponse dict with profile list and current profile
- """
- return self.configuration_service.get_game_configs()
-
- async def _legacy_create_profile(self, profile_name: str, source_profile: str = None) -> Dict[str, Any]:
- """Create a new profile
-
- Args:
- profile_name: Name for the new profile
- source_profile: Optional source profile to copy from (default: current profile)
-
- Returns:
- ProfileResponse dict with success status
- """
- return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"}
-
- async def _legacy_delete_profile(self, profile_name: str) -> Dict[str, Any]:
- """Delete a profile
-
- Args:
- profile_name: Name of the profile to delete
-
- Returns:
- ProfileResponse dict with success status
- """
- return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"}
-
- async def _legacy_rename_profile(self, old_name: str, new_name: str) -> Dict[str, Any]:
- """Rename a profile
-
- Args:
- old_name: Current profile name
- new_name: New profile name
-
- Returns:
- ProfileResponse dict with success status
- """
- return {"success": False, "error": "Named profiles were replaced by per-game AppID profiles"}
-
- async def _legacy_set_current_profile(self, profile_name: str) -> Dict[str, Any]:
- """Set the current active profile
-
- Args:
- profile_name: Name of the profile to set as current
-
- Returns:
- ProfileResponse dict with success status
- """
- return {"success": False, "error": "There is no globally selected profile"}
-
- async def _legacy_update_profile_config(self, profile_name: str, config: Dict[str, Any]) -> Dict[str, Any]:
- """Update configuration for a specific profile
-
- Args:
- profile_name: Name of the profile to update
- config: Configuration data dictionary containing settings
-
- Returns:
- ConfigurationResponse dict with success status
- """
- return {"success": False, "error": "Use update_game_config with a Steam AppID"}
-
async def get_config_file_content(self) -> Dict[str, Any]:
"""Get the current config file content
@@ -209,32 +107,6 @@ class Plugin:
"error": f"Error reading config file: {str(e)}"
}
- async def check_fgmod_directory(self) -> Dict[str, Any]:
- """Check if the fgmod directory exists in the home directory
-
- Returns:
- Dict with exists status and directory path
- """
- try:
- home_path = Path(decky.DECKY_USER_HOME)
- fgmod_path = home_path / "fgmod"
-
- exists = fgmod_path.exists() and fgmod_path.is_dir()
-
- return {
- "success": True,
- "exists": exists,
- "path": str(fgmod_path)
- }
-
- except Exception as e:
- decky.logger.error(f"Error checking fgmod directory: {e}")
- return {
- "success": False,
- "exists": False,
- "error": str(e)
- }
-
async def check_flatpak_extension_status(self) -> Dict[str, Any]:
"""Check status of lsfg-vk Flatpak runtime extensions
diff --git a/py_modules/lsfg_vk/types.py b/py_modules/lsfg_vk/types.py
index ef6bab7..7b85708 100644
--- a/py_modules/lsfg_vk/types.py
+++ b/py_modules/lsfg_vk/types.py
@@ -2,8 +2,7 @@
Type definitions for the lsfg-vk plugin responses.
"""
-from typing import TypedDict, Optional, List, Dict, Any
-from .config_schema import ConfigurationData
+from typing import TypedDict, Optional, List
class BaseResponse(TypedDict):
@@ -53,31 +52,3 @@ class SteamBranchStatusResponse(TypedDict):
target_branch: str
needs_switch: bool
restart_required: bool
-
-class ConfigurationResponse(BaseResponse):
- """Response for configuration operations"""
- config: Optional[ConfigurationData]
- message: Optional[str]
- error: Optional[str]
-
-
-class ProfileConfig(TypedDict):
- """Configuration for a single profile"""
- exe: str
- config: ConfigurationData
-
-
-class ProfilesResponse(BaseResponse):
- """Response for per-game upstream profiles"""
- default: Optional[ConfigurationData]
- games: Optional[List[Dict[str, Any]]]
- message: Optional[str]
- error: Optional[str]
-
-
-class ProfileResponse(BaseResponse):
- """Response for a per-game upstream profile"""
- appid: Optional[str]
- config: Optional[ConfigurationData]
- message: Optional[str]
- error: Optional[str]