diff options
| author | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-09-22 09:38:20 -0400 |
|---|---|---|
| committer | xXJSONDeruloXx <danielhimebauch@gmail.com> | 2025-09-22 09:38:20 -0400 |
| commit | 0da3682755e551a7d3c23fa979686d8dbcdd4f7b (patch) | |
| tree | f07d360ebaeefba28fe5b5be730c807ce337bddc | |
| parent | 84de5901cd1fb7d89031e4e7b3b47ed805e324c8 (diff) | |
| download | decky-lsfg-vk-0da3682755e551a7d3c23fa979686d8dbcdd4f7b.tar.gz decky-lsfg-vk-0da3682755e551a7d3c23fa979686d8dbcdd4f7b.zip | |
bring forward old flatpak modal attempt
| -rw-r--r-- | py_modules/lsfg_vk/constants.py | 4 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/flatpak_service.py | 400 | ||||
| -rw-r--r-- | py_modules/lsfg_vk/plugin.py | 63 | ||||
| -rw-r--r-- | src/api/lsfgApi.ts | 40 | ||||
| -rw-r--r-- | src/components/Content.tsx | 15 | ||||
| -rw-r--r-- | src/components/FlatpaksModal.tsx | 291 | ||||
| -rw-r--r-- | src/components/index.ts | 1 |
7 files changed, 814 insertions, 0 deletions
diff --git a/py_modules/lsfg_vk/constants.py b/py_modules/lsfg_vk/constants.py index 69989c1..614e2fc 100644 --- a/py_modules/lsfg_vk/constants.py +++ b/py_modules/lsfg_vk/constants.py @@ -17,6 +17,10 @@ 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" + # File extensions SO_EXT = ".so" JSON_EXT = ".json" diff --git a/py_modules/lsfg_vk/flatpak_service.py b/py_modules/lsfg_vk/flatpak_service.py index e69de29..3fb621d 100644 --- a/py_modules/lsfg_vk/flatpak_service.py +++ b/py_modules/lsfg_vk/flatpak_service.py @@ -0,0 +1,400 @@ +""" +Flatpak service for managing lsfg-vk Flatpak runtime extensions. +""" + +import subprocess +import os +from pathlib import Path +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 +) +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): + super().__init__(success, message, error) + self.installed_23_08 = installed_23_08 + self.installed_24_08 = installed_24_08 + + +class FlatpakAppInfo(BaseResponse): + """Response for Flatpak app information""" + def __init__(self, success: bool = False, message: str = "", error: str = "", + apps: List[Dict[str, Any]] = None, total_apps: int = 0): + super().__init__(success, message, error) + self.apps = apps or [] + self.total_apps = total_apps + + +class FlatpakOverrideResponse(BaseResponse): + """Response for Flatpak override operations""" + def __init__(self, success: bool = False, message: str = "", error: str = "", + app_id: str = "", operation: str = ""): + super().__init__(success, message, error) + self.app_id = app_id + self.operation = operation + + +class FlatpakService(BaseService): + """Service for handling Flatpak runtime extensions and app overrides""" + + 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.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.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() + + # 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: + path_parts.insert(0, std_path) + + env['PATH'] = ':'.join(path_parts) + + return env + + def _run_flatpak_command(self, args: List[str], **kwargs): + """Run flatpak command with clean environment to avoid library conflicts""" + if self.flatpak_command is None: + raise FileNotFoundError("Flatpak command not available") + + 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""" + 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')}") + 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", + "/var/lib/flatpak/exports/bin/flatpak", + "/home/deck/.local/bin/flatpak" + ] + + for flatpak_path in flatpak_paths: + try: + result = subprocess.run([flatpak_path, "--version"], + capture_output=True, check=True, text=True, + env=self._get_clean_env()) + self.log.info(f"Flatpak found at {flatpak_path}: {result.stdout.strip()}") + self.flatpak_command = flatpak_path + return True + except (subprocess.CalledProcessError, FileNotFoundError): + self.log.debug(f"Flatpak not found at {flatpak_path}") + continue + + self.log.error("Flatpak command not found in any known locations") + self.flatpak_command = None + return False + + def get_extension_status(self) -> FlatpakExtensionStatus: + """Check if lsfg-vk Flatpak extensions are installed""" + try: + if not self.check_flatpak_available(): + error_msg = "Flatpak is not available on this system" + if self.flatpak_command is None: + error_msg += ". Command not found in PATH or common install locations." + self.log.error(error_msg) + return self._error_response(FlatpakExtensionStatus, + error_msg, + installed_23_08=False, installed_24_08=False) + + # Get list of installed runtimes + result = self._run_flatpak_command( + ["list", "--runtime"], + capture_output=True, text=True, check=True + ) + + installed_runtimes = result.stdout + + # Check for both 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 + + for line in installed_runtimes.split('\n'): + if base_extension_name in line: + if "23.08" in line: + installed_23_08 = True + elif "24.08" in line: + installed_24_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 not status_msg: + status_msg.append("No lsfg-vk runtime extensions installed") + + return self._success_response(FlatpakExtensionStatus, + "; ".join(status_msg), + installed_23_08=installed_23_08, + installed_24_08=installed_24_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) + + 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 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 + 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 + ) + + if result.returncode != 0: + error_msg = f"Failed to install Flatpak extension: {result.stderr}" + self.log.error(error_msg) + return self._error_response(BaseResponse, error_msg) + + self.log.info(f"Successfully installed lsfg-vk Flatpak extension {version}") + return self._success_response(BaseResponse, f"lsfg-vk {version} runtime extension installed successfully") + + except Exception as e: + error_msg = f"Error installing Flatpak extension {version}: {str(e)}" + self.log.error(error_msg) + return self._error_response(BaseResponse, error_msg) + + 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 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 + + # Uninstall the extension + result = self._run_flatpak_command( + ["uninstall", "--user", "--noninteractive", extension_id], + capture_output=True, text=True + ) + + if result.returncode != 0: + error_msg = f"Failed to uninstall Flatpak extension: {result.stderr}" + self.log.error(error_msg) + return self._error_response(BaseResponse, error_msg) + + self.log.info(f"Successfully uninstalled lsfg-vk Flatpak extension {version}") + return self._success_response(BaseResponse, f"lsfg-vk {version} runtime extension uninstalled successfully") + + except Exception as e: + error_msg = f"Error uninstalling Flatpak extension {version}: {str(e)}" + self.log.error(error_msg) + return self._error_response(BaseResponse, error_msg) + + def get_flatpak_apps(self) -> FlatpakAppInfo: + """Get list of installed Flatpak apps and their lsfg-vk override status""" + try: + if not self.check_flatpak_available(): + error_msg = "Flatpak is not available on this system" + if self.flatpak_command is None: + error_msg += ". Command not found in PATH or common install locations." + return self._error_response(FlatpakAppInfo, + 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 + ) + + apps = [] + for line in result.stdout.strip().split('\n'): + 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() + app_id = parts[1].strip() + + # Check override status + override_status = self._check_app_override_status(app_id) + + apps.append({ + "app_id": app_id, + "app_name": app_name, + "has_filesystem_override": override_status["filesystem"], + "has_env_override": override_status["env"] + }) + + return self._success_response(FlatpakAppInfo, + f"Found {len(apps)} Flatpak applications", + apps=apps, total_apps=len(apps)) + + except subprocess.CalledProcessError as e: + error_msg = f"Error getting Flatpak apps: {e.stderr if e.stderr else str(e)}" + self.log.error(error_msg) + return self._error_response(FlatpakAppInfo, error_msg, apps=[], total_apps=0) + + def _check_app_override_status(self, app_id: str) -> Dict[str, bool]: + """Check if an app has lsfg-vk overrides set""" + try: + result = self._run_flatpak_command( + ["override", "--user", "--show", app_id], + capture_output=True, text=True + ) + + if result.returncode != 0: + return {"filesystem": False, "env": False} + + output = result.stdout + home_path = os.path.expanduser("~") + config_path = f"{home_path}/.config/lsfg-vk" + + # Check for filesystem override in the [Context] section + # Format: filesystems=/home/kurt/.config/lsfg-vk; + filesystem_override = f"filesystems={config_path}" in output + + # Check for environment override in the [Environment] section + # Format: LSFG_CONFIG=/home/kurt/.config/lsfg-vk/conf.toml + env_override = f"LSFG_CONFIG={config_path}/conf.toml" in output + + return {"filesystem": filesystem_override, "env": env_override} + + except Exception as e: + self.log.error(f"Error checking override status for {app_id}: {e}") + return {"filesystem": False, "env": False} + + def set_app_override(self, app_id: str) -> FlatpakOverrideResponse: + """Set lsfg-vk overrides for a Flatpak app""" + try: + if not self.check_flatpak_available(): + return self._error_response(FlatpakOverrideResponse, + "Flatpak is not available on this system", + app_id=app_id, operation="set") + + home_path = os.path.expanduser("~") + config_path = f"{home_path}/.config/lsfg-vk" + + # Set filesystem override + result1 = self._run_flatpak_command( + ["override", "--user", f"--filesystem={config_path}:rw", app_id], + capture_output=True, text=True + ) + + # Set environment override + result2 = self._run_flatpak_command( + ["override", "--user", f"--env=LSFG_CONFIG={config_path}/conf.toml", app_id], + capture_output=True, text=True + ) + + if result1.returncode != 0 or result2.returncode != 0: + error_msg = f"Failed to set overrides: {result1.stderr} {result2.stderr}" + return self._error_response(FlatpakOverrideResponse, error_msg, + app_id=app_id, operation="set") + + self.log.info(f"Successfully set lsfg-vk overrides for {app_id}") + return self._success_response(FlatpakOverrideResponse, + f"lsfg-vk overrides set for {app_id}", + app_id=app_id, operation="set") + + except Exception as e: + error_msg = f"Error setting overrides for {app_id}: {str(e)}" + self.log.error(error_msg) + return self._error_response(FlatpakOverrideResponse, error_msg, + app_id=app_id, operation="set") + + def remove_app_override(self, app_id: str) -> FlatpakOverrideResponse: + """Remove lsfg-vk overrides for a Flatpak app""" + try: + if not self.check_flatpak_available(): + return self._error_response(FlatpakOverrideResponse, + "Flatpak is not available on this system", + app_id=app_id, operation="remove") + + home_path = os.path.expanduser("~") + config_path = f"{home_path}/.config/lsfg-vk" + + # Remove filesystem override + result1 = self._run_flatpak_command( + ["override", "--user", f"--nofilesystem={config_path}", app_id], + capture_output=True, text=True + ) + + # Remove environment override + result2 = self._run_flatpak_command( + ["override", "--user", "--unset-env=LSFG_CONFIG", app_id], + capture_output=True, text=True + ) + + if result1.returncode != 0 or result2.returncode != 0: + error_msg = f"Failed to remove overrides: {result1.stderr} {result2.stderr}" + return self._error_response(FlatpakOverrideResponse, error_msg, + app_id=app_id, operation="remove") + + self.log.info(f"Successfully removed lsfg-vk overrides for {app_id}") + return self._success_response(FlatpakOverrideResponse, + f"lsfg-vk overrides removed for {app_id}", + app_id=app_id, operation="remove") + + except Exception as e: + error_msg = f"Error removing overrides for {app_id}: {str(e)}" + self.log.error(error_msg) + return self._error_response(FlatpakOverrideResponse, error_msg, + app_id=app_id, operation="remove")
\ No newline at end of file diff --git a/py_modules/lsfg_vk/plugin.py b/py_modules/lsfg_vk/plugin.py index f08aa18..ccc9984 100644 --- a/py_modules/lsfg_vk/plugin.py +++ b/py_modules/lsfg_vk/plugin.py @@ -18,6 +18,7 @@ from .installation import InstallationService from .dll_detection import DllDetectionService from .configuration import ConfigurationService from .config_schema import ConfigurationManager +from .flatpak_service import FlatpakService class Plugin: @@ -35,6 +36,7 @@ class Plugin: 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]: @@ -612,6 +614,67 @@ class Plugin: "exists": False, "error": str(e) } + + # Flatpak management methods + async def check_flatpak_extension_status(self) -> Dict[str, Any]: + """Check status of lsfg-vk Flatpak runtime extensions + + Returns: + FlatpakExtensionStatus dict with installation status for both runtime versions + """ + return self.flatpak_service.get_extension_status() + + async def install_flatpak_extension(self, version: str) -> Dict[str, Any]: + """Install lsfg-vk Flatpak runtime extension + + Args: + version: Runtime version to install ("23.08" or "24.08") + + Returns: + BaseResponse dict with success status and message/error + """ + return self.flatpak_service.install_extension(version) + + async def uninstall_flatpak_extension(self, version: str) -> Dict[str, Any]: + """Uninstall lsfg-vk Flatpak runtime extension + + Args: + version: Runtime version to uninstall ("23.08" or "24.08") + + Returns: + BaseResponse dict with success status and message/error + """ + return self.flatpak_service.uninstall_extension(version) + + async def get_flatpak_apps(self) -> Dict[str, Any]: + """Get list of installed Flatpak apps and their lsfg-vk override status + + Returns: + FlatpakAppInfo dict with apps list and override status + """ + return self.flatpak_service.get_flatpak_apps() + + async def set_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: + """Set lsfg-vk overrides for a Flatpak app + + Args: + app_id: Flatpak application ID + + Returns: + FlatpakOverrideResponse dict with operation result + """ + return self.flatpak_service.set_app_override(app_id) + + async def remove_flatpak_app_override(self, app_id: str) -> Dict[str, Any]: + """Remove lsfg-vk overrides for a Flatpak app + + Args: + app_id: Flatpak application ID + + Returns: + FlatpakOverrideResponse dict with operation result + """ + return self.flatpak_service.remove_app_override(app_id) # Decky Loader lifecycle methods diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index 8db0c82..d08cd42 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -96,6 +96,38 @@ export interface FgmodCheckResult { error?: string; } +// Flatpak management interfaces +export interface FlatpakExtensionStatus { + success: boolean; + message: string; + error?: string; + installed_23_08: boolean; + installed_24_08: boolean; +} + +export interface FlatpakApp { + app_id: string; + app_name: string; + has_filesystem_override: boolean; + has_env_override: boolean; +} + +export interface FlatpakAppInfo { + success: boolean; + message: string; + error?: string; + apps: FlatpakApp[]; + total_apps: number; +} + +export interface FlatpakOperationResult { + success: boolean; + message: string; + error?: string; + app_id?: string; + operation?: string; +} + // Profile management interfaces export interface ProfilesResult { success: boolean; @@ -125,6 +157,14 @@ export const getConfigFileContent = callable<[], FileContentResult>("get_config_ export const getLaunchScriptContent = callable<[], FileContentResult>("get_launch_script_content"); export const checkFgmodDirectory = callable<[], FgmodCheckResult>("check_fgmod_directory"); +// Flatpak management API functions +export const checkFlatpakExtensionStatus = callable<[], FlatpakExtensionStatus>("check_flatpak_extension_status"); +export const installFlatpakExtension = callable<[string], FlatpakOperationResult>("install_flatpak_extension"); +export const uninstallFlatpakExtension = callable<[string], FlatpakOperationResult>("uninstall_flatpak_extension"); +export const getFlatpakApps = callable<[], FlatpakAppInfo>("get_flatpak_apps"); +export const setFlatpakAppOverride = callable<[string], FlatpakOperationResult>("set_flatpak_app_override"); +export const removeFlatpakAppOverride = callable<[string], FlatpakOperationResult>("remove_flatpak_app_override"); + // Updated config function using object-based configuration (single source of truth) export const updateLsfgConfig = callable< [ConfigurationData], diff --git a/src/components/Content.tsx b/src/components/Content.tsx index a00a595..b76ff59 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -15,6 +15,7 @@ import { FgmodClipboardButton } from "./FgmodClipboardButton"; // import { ClipboardDisplay } from "./ClipboardDisplay"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { NerdStuffModal } from "./NerdStuffModal"; +import { FlatpaksModal } from "./index"; import { ConfigurationData } from "../config/configSchema"; export function Content() { @@ -76,6 +77,10 @@ export function Content() { showModal(<NerdStuffModal />); }; + const handleShowFlatpaks = () => { + showModal(<FlatpaksModal />); + }; + return ( <PanelSection> {/* Show installation components at top when not fully installed */} @@ -165,6 +170,16 @@ export function Content() { Nerd Stuff </ButtonItem> </PanelSectionRow> + + {/* Flatpaks Button */} + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={handleShowFlatpaks} + > + Flatpaks + </ButtonItem> + </PanelSectionRow> </PanelSection> ); } diff --git a/src/components/FlatpaksModal.tsx b/src/components/FlatpaksModal.tsx index e69de29..a57c683 100644 --- a/src/components/FlatpaksModal.tsx +++ b/src/components/FlatpaksModal.tsx @@ -0,0 +1,291 @@ +import { FC, useState, useEffect } from 'react'; +import { + ModalRoot, + DialogBody, + DialogHeader, + DialogControlsSection, + DialogControlsSectionHeader, + ButtonItem, + PanelSectionRow, + Field, + Toggle, + Spinner, + Focusable, + showModal, + ConfirmModal +} from '@decky/ui'; +import { FaCheck, FaTimes, FaDownload, FaTrash, FaCog } from 'react-icons/fa'; +import { + checkFlatpakExtensionStatus, + installFlatpakExtension, + uninstallFlatpakExtension, + getFlatpakApps, + setFlatpakAppOverride, + removeFlatpakAppOverride, + FlatpakExtensionStatus, + FlatpakApp, + FlatpakAppInfo +} from '../api/lsfgApi'; + +interface FlatpaksModalProps { + closeModal?: () => void; +} + +const FlatpaksModal: FC<FlatpaksModalProps> = ({ closeModal }) => { + const [extensionStatus, setExtensionStatus] = useState<FlatpakExtensionStatus | null>(null); + const [flatpakApps, setFlatpakApps] = useState<FlatpakAppInfo | null>(null); + const [loading, setLoading] = useState(true); + const [operationInProgress, setOperationInProgress] = useState<string | null>(null); + + const loadData = async () => { + setLoading(true); + try { + const [statusResult, appsResult] = await Promise.all([ + checkFlatpakExtensionStatus(), + getFlatpakApps() + ]); + + setExtensionStatus(statusResult); + setFlatpakApps(appsResult); + } catch (error) { + console.error('Error loading Flatpak data:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadData(); + }, []); + + const handleExtensionOperation = async (operation: 'install' | 'uninstall', version: string) => { + const operationId = `${operation}-${version}`; + setOperationInProgress(operationId); + + try { + const result = operation === 'install' + ? await installFlatpakExtension(version) + : await uninstallFlatpakExtension(version); + + if (result.success) { + // Reload status after operation + const newStatus = await checkFlatpakExtensionStatus(); + setExtensionStatus(newStatus); + } + } catch (error) { + console.error(`Error ${operation}ing extension:`, error); + } finally { + setOperationInProgress(null); + } + }; + + const handleAppOverrideToggle = async (app: FlatpakApp) => { + const hasOverrides = app.has_filesystem_override && app.has_env_override; + const operationId = `app-${app.app_id}`; + setOperationInProgress(operationId); + + try { + const result = hasOverrides + ? await removeFlatpakAppOverride(app.app_id) + : await setFlatpakAppOverride(app.app_id); + + if (result.success) { + // Reload apps data after operation + const newApps = await getFlatpakApps(); + setFlatpakApps(newApps); + } + } catch (error) { + console.error('Error toggling app override:', error); + } finally { + setOperationInProgress(null); + } + }; + + const confirmOperation = (operation: () => void, title: string, description: string) => { + showModal( + <ConfirmModal + strTitle={title} + strDescription={description} + onOK={operation} + onCancel={() => {}} + /> + ); + }; + + if (loading) { + return ( + <ModalRoot closeModal={closeModal}> + <DialogHeader>Flatpak Extensions</DialogHeader> + <DialogBody> + <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}> + <Spinner /> + </div> + </DialogBody> + </ModalRoot> + ); + } + + return ( + <ModalRoot closeModal={closeModal}> + <DialogHeader>Flatpak Extensions</DialogHeader> + <DialogBody> + <Focusable> + {/* Extension Status Section */} + <DialogControlsSection> + <DialogControlsSectionHeader>Runtime Extensions</DialogControlsSectionHeader> + + {extensionStatus && extensionStatus.success ? ( + <> + {/* 23.08 Runtime */} + <PanelSectionRow> + <Field + label="Runtime 23.08" + description={extensionStatus.installed_23_08 ? "Installed" : "Not installed"} + icon={extensionStatus.installed_23_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />} + > + <ButtonItem + layout="below" + onClick={() => { + const operation = extensionStatus.installed_23_08 ? 'uninstall' : 'install'; + const action = () => handleExtensionOperation(operation, '23.08'); + + if (operation === 'uninstall') { + confirmOperation( + action, + 'Uninstall Runtime Extension', + 'Are you sure you want to uninstall the 23.08 runtime extension?' + ); + } else { + action(); + } + }} + disabled={operationInProgress === 'install-23.08' || operationInProgress === 'uninstall-23.08'} + > + {operationInProgress === 'install-23.08' || operationInProgress === 'uninstall-23.08' ? ( + <Spinner /> + ) : extensionStatus.installed_23_08 ? ( + <> + <FaTrash /> Uninstall + </> + ) : ( + <> + <FaDownload /> Install + </> + )} + </ButtonItem> + </Field> + </PanelSectionRow> + + {/* 24.08 Runtime */} + <PanelSectionRow> + <Field + label="Runtime 24.08" + description={extensionStatus.installed_24_08 ? "Installed" : "Not installed"} + icon={extensionStatus.installed_24_08 ? <FaCheck style={{color: 'green'}} /> : <FaTimes style={{color: 'red'}} />} + > + <ButtonItem + layout="below" + onClick={() => { + const operation = extensionStatus.installed_24_08 ? 'uninstall' : 'install'; + const action = () => handleExtensionOperation(operation, '24.08'); + + if (operation === 'uninstall') { + confirmOperation( + action, + 'Uninstall Runtime Extension', + 'Are you sure you want to uninstall the 24.08 runtime extension?' + ); + } else { + action(); + } + }} + disabled={operationInProgress === 'install-24.08' || operationInProgress === 'uninstall-24.08'} + > + {operationInProgress === 'install-24.08' || operationInProgress === 'uninstall-24.08' ? ( + <Spinner /> + ) : extensionStatus.installed_24_08 ? ( + <> + <FaTrash /> Uninstall + </> + ) : ( + <> + <FaDownload /> Install + </> + )} + </ButtonItem> + </Field> + </PanelSectionRow> + </> + ) : ( + <PanelSectionRow> + <Field + label="Error" + description={extensionStatus?.error || 'Failed to check extension status'} + icon={<FaTimes style={{color: 'red'}} />} + /> + </PanelSectionRow> + )} + </DialogControlsSection> + + {/* Flatpak Apps Section */} + <DialogControlsSection> + <DialogControlsSectionHeader>Flatpak Applications</DialogControlsSectionHeader> + + {flatpakApps && flatpakApps.success ? ( + flatpakApps.apps.length > 0 ? ( + flatpakApps.apps.map((app) => { + const hasOverrides = app.has_filesystem_override && app.has_env_override; + const partialOverrides = app.has_filesystem_override || app.has_env_override; + + let statusColor = 'red'; + let statusText = 'No overrides'; + + if (hasOverrides) { + statusColor = 'green'; + statusText = 'Configured'; + } else if (partialOverrides) { + statusColor = 'orange'; + statusText = 'Partial'; + } + + return ( + <PanelSectionRow key={app.app_id}> + <Field + label={app.app_name || app.app_id} + description={`${app.app_id} - ${statusText}`} + icon={<FaCog style={{color: statusColor}} />} + > + <Toggle + value={hasOverrides} + onChange={() => handleAppOverrideToggle(app)} + disabled={operationInProgress === `app-${app.app_id}`} + /> + </Field> + </PanelSectionRow> + ); + }) + ) : ( + <PanelSectionRow> + <Field + label="No Flatpak Apps Found" + description="No Flatpak applications are currently installed" + /> + </PanelSectionRow> + ) + ) : ( + <PanelSectionRow> + <Field + label="Error" + description={flatpakApps?.error || 'Failed to load Flatpak applications'} + icon={<FaTimes style={{color: 'red'}} />} + /> + </PanelSectionRow> + )} + </DialogControlsSection> + </Focusable> + </DialogBody> + </ModalRoot> + ); +}; + +export default FlatpaksModal; diff --git a/src/components/index.ts b/src/components/index.ts index 802cd3c..50480be 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -10,4 +10,5 @@ export { FgmodClipboardButton } from "./FgmodClipboardButton"; // export { ClipboardDisplay } from "./ClipboardDisplay"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; export { NerdStuffModal } from "./NerdStuffModal"; +export { default as FlatpaksModal } from "./FlatpaksModal"; export { ProfileManagement } from "./ProfileManagement"; |
