diff options
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | assets/logo.png | bin | 21108 -> 0 bytes | |||
| -rw-r--r-- | assets/qam-screenshot.jpeg | bin | 60314 -> 0 bytes | |||
| -rw-r--r-- | decky.pyi | 184 | ||||
| -rw-r--r-- | defaults/defaults.txt | 13 | ||||
| -rw-r--r-- | src/api/lsfgApi.ts | 20 | ||||
| -rw-r--r-- | src/components/ClipboardButton.tsx | 22 | ||||
| -rw-r--r-- | src/components/ClipboardDisplay.tsx | 139 | ||||
| -rw-r--r-- | src/components/Content.tsx | 16 | ||||
| -rw-r--r-- | src/components/PluginUpdateChecker.tsx | 176 | ||||
| -rw-r--r-- | src/components/WikiButton.tsx | 22 | ||||
| -rw-r--r-- | src/components/index.ts | 11 |
12 files changed, 17 insertions, 596 deletions
@@ -3,6 +3,10 @@ > **Note:** > This is an **unofficial community plugin**. It is independently developed and **not officially supported** by the creators of Lossless Scaling or lsfg-vk. For support, please use the [decky-lsfg-vk Discord Channel](https://discord.gg/TwvHdVucC3). + +<p align="center"> + <img src="assets/decky-lossless-logo.png" alt="decky-lsfg-vk Logo" width="200"/> +</p> <p align="center"> <a href="https://ko-fi.com/B0B71HZTAX" target="_blank" rel="noopener noreferrer"> <img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="Support on Ko-fi"/> @@ -10,12 +14,6 @@ </p> - -<p align="center"> - <img src="assets/decky-lossless-logo.png" alt="decky-lsfg-vk Logo" width="200"/> -</p> - - ## What is this? A Decky plugin that streamlines the installation of **lsfg-vk** ([Lossless Scaling Frame Generation Vulkan layer](https://github.com/PancakeTAS/lsfg-vk)) on Steam Deck, allowing you to use the Lossless Scaling frame generation features on Linux with a controller friendly UI in SteamOS, Bazzite, or any other Linux platform compatible with Decky Loader. diff --git a/assets/logo.png b/assets/logo.png Binary files differdeleted file mode 100644 index 48c4851..0000000 --- a/assets/logo.png +++ /dev/null diff --git a/assets/qam-screenshot.jpeg b/assets/qam-screenshot.jpeg Binary files differdeleted file mode 100644 index 8df12b2..0000000 --- a/assets/qam-screenshot.jpeg +++ /dev/null diff --git a/decky.pyi b/decky.pyi deleted file mode 100644 index a72c74c..0000000 --- a/decky.pyi +++ /dev/null @@ -1,184 +0,0 @@ -""" -This module exposes various constants and helpers useful for decky plugins. - -* Plugin's settings and configurations should be stored under `DECKY_PLUGIN_SETTINGS_DIR`. -* Plugin's runtime data should be stored under `DECKY_PLUGIN_RUNTIME_DIR`. -* Plugin's persistent log files should be stored under `DECKY_PLUGIN_LOG_DIR`. - -Avoid writing outside of `DECKY_HOME`, storing under the suggested paths is strongly recommended. - -Some basic migration helpers are available: `migrate_any`, `migrate_settings`, `migrate_runtime`, `migrate_logs`. - -A logging facility `logger` is available which writes to the recommended location. -""" - -__version__ = '1.0.0' - -import logging - -from typing import Any - -""" -Constants -""" - -HOME: str -""" -The home directory of the effective user running the process. -Environment variable: `HOME`. -If `root` was specified in the plugin's flags it will be `/root` otherwise the user whose home decky resides in. -e.g.: `/home/deck` -""" - -USER: str -""" -The effective username running the process. -Environment variable: `USER`. -It would be `root` if `root` was specified in the plugin's flags otherwise the user whose home decky resides in. -e.g.: `deck` -""" - -DECKY_VERSION: str -""" -The version of the decky loader. -Environment variable: `DECKY_VERSION`. -e.g.: `v2.5.0-pre1` -""" - -DECKY_USER: str -""" -The user whose home decky resides in. -Environment variable: `DECKY_USER`. -e.g.: `deck` -""" - -DECKY_USER_HOME: str -""" -The home of the user where decky resides in. -Environment variable: `DECKY_USER_HOME`. -e.g.: `/home/deck` -""" - -DECKY_HOME: str -""" -The root of the decky folder. -Environment variable: `DECKY_HOME`. -e.g.: `/home/deck/homebrew` -""" - -DECKY_PLUGIN_SETTINGS_DIR: str -""" -The recommended path in which to store configuration files (created automatically). -Environment variable: `DECKY_PLUGIN_SETTINGS_DIR`. -e.g.: `/home/deck/homebrew/settings/decky-plugin-template` -""" - -DECKY_PLUGIN_RUNTIME_DIR: str -""" -The recommended path in which to store runtime data (created automatically). -Environment variable: `DECKY_PLUGIN_RUNTIME_DIR`. -e.g.: `/home/deck/homebrew/data/decky-plugin-template` -""" - -DECKY_PLUGIN_LOG_DIR: str -""" -The recommended path in which to store persistent logs (created automatically). -Environment variable: `DECKY_PLUGIN_LOG_DIR`. -e.g.: `/home/deck/homebrew/logs/decky-plugin-template` -""" - -DECKY_PLUGIN_DIR: str -""" -The root of the plugin's directory. -Environment variable: `DECKY_PLUGIN_DIR`. -e.g.: `/home/deck/homebrew/plugins/decky-plugin-template` -""" - -DECKY_PLUGIN_NAME: str -""" -The name of the plugin as specified in the 'plugin.json'. -Environment variable: `DECKY_PLUGIN_NAME`. -e.g.: `Example Plugin` -""" - -DECKY_PLUGIN_VERSION: str -""" -The version of the plugin as specified in the 'package.json'. -Environment variable: `DECKY_PLUGIN_VERSION`. -e.g.: `0.0.1` -""" - -DECKY_PLUGIN_AUTHOR: str -""" -The author of the plugin as specified in the 'plugin.json'. -Environment variable: `DECKY_PLUGIN_AUTHOR`. -e.g.: `John Doe` -""" - -DECKY_PLUGIN_LOG: str -""" -The path to the plugin's main logfile. -Environment variable: `DECKY_PLUGIN_LOG`. -e.g.: `/home/deck/homebrew/logs/decky-plugin-template/plugin.log` -""" - -""" -Migration helpers -""" - - -def migrate_any(target_dir: str, *files_or_directories: str) -> dict[str, str]: - """ - Migrate files and directories to a new location and remove old locations. - Specified files will be migrated to `target_dir`. - Specified directories will have their contents recursively migrated to `target_dir`. - - Returns the mapping of old -> new location. - """ - - -def migrate_settings(*files_or_directories: str) -> dict[str, str]: - """ - Migrate files and directories relating to plugin settings to the recommended location and remove old locations. - Specified files will be migrated to `DECKY_PLUGIN_SETTINGS_DIR`. - Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_SETTINGS_DIR`. - - Returns the mapping of old -> new location. - """ - - -def migrate_runtime(*files_or_directories: str) -> dict[str, str]: - """ - Migrate files and directories relating to plugin runtime data to the recommended location and remove old locations - Specified files will be migrated to `DECKY_PLUGIN_RUNTIME_DIR`. - Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_RUNTIME_DIR`. - - Returns the mapping of old -> new location. - """ - - -def migrate_logs(*files_or_directories: str) -> dict[str, str]: - """ - Migrate files and directories relating to plugin logs to the recommended location and remove old locations. - Specified files will be migrated to `DECKY_PLUGIN_LOG_DIR`. - Specified directories will have their contents recursively migrated to `DECKY_PLUGIN_LOG_DIR`. - - Returns the mapping of old -> new location. - """ - - -""" -Logging -""" - -logger: logging.Logger -"""The main plugin logger writing to `DECKY_PLUGIN_LOG`.""" - -""" -Event handling -""" -# TODO better docstring im lazy -async def emit(event: str, *args: Any) -> None: - """ - Send an event to the frontend. - """
\ No newline at end of file diff --git a/defaults/defaults.txt b/defaults/defaults.txt deleted file mode 100644 index ebf140b..0000000 --- a/defaults/defaults.txt +++ /dev/null @@ -1,13 +0,0 @@ -If you have plain-text json configs, theme templates, or templates for usage for your plugin of any description you should have those files be in here. -Those files will be pulled into the zip during the build process and included with the upload. Example: CssLoader with it's themes in "default/themes" would have the "themes" folder will be added alongside with the dist folder, main.py, LICENSE and README files in the subfolder of the zip containing the plugin. -Files can also be put in here such as a config, just keep in mind that they this directory cannot be utilized to put files in arbitrary locations, just within the extracted root folder of the plugin, ex: CssLoader has "defaults/themes/..." setup in it's repo, but when packaged to go to the store, the file structure will be: - -- LICENSE -- README -- dist - - index.js -- main.py -- package.json -- plugin.json -- themes - - exampletheme.css
\ No newline at end of file diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index dda6c23..82f37c8 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -59,23 +59,6 @@ export interface ConfigSchemaResult { current_profile?: string; } -export interface UpdateCheckResult { - success: boolean; - update_available: boolean; - current_version: string; - latest_version: string; - release_notes: string; - release_date: string; - download_url: string; - error?: string; -} - -export interface UpdateDownloadResult { - success: boolean; - download_path?: string; - error?: string; -} - export interface LaunchOptionResult { launch_option: string; instructions: string; @@ -178,9 +161,6 @@ export const updateLsfgConfigFromObject = async (config: ConfigurationData): Pro }; // Self-updater API functions -export const checkForPluginUpdate = callable<[], UpdateCheckResult>("check_for_plugin_update"); -export const downloadPluginUpdate = callable<[string], UpdateDownloadResult>("download_plugin_update"); - // Profile management API functions export const getProfiles = callable<[], ProfilesResult>("get_profiles"); export const createProfile = callable<[string, string?], ProfileResult>("create_profile"); diff --git a/src/components/ClipboardButton.tsx b/src/components/ClipboardButton.tsx deleted file mode 100644 index cac1863..0000000 --- a/src/components/ClipboardButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaBook } from "react-icons/fa"; - -export function ClipboardButton() { - const handleClipboardClick = () => { - window.open("https://github.com/xXJSONDeruloXx/decky-lossless-scaling-vk/wiki/Clipboard", "_blank"); - }; - - return ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleClipboardClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaBook /> - <div>Plugin Wiki</div> - </div> - </ButtonItem> - </PanelSectionRow> - ); -} diff --git a/src/components/ClipboardDisplay.tsx b/src/components/ClipboardDisplay.tsx deleted file mode 100644 index 852a50f..0000000 --- a/src/components/ClipboardDisplay.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { useState, useEffect } from "react"; -import { PanelSectionRow } from "@decky/ui"; -import { FaClipboard, FaEye } from "react-icons/fa"; - -export function ClipboardDisplay() { - const [clipboardContent, setClipboardContent] = useState<string>(""); - const [isReading, setIsReading] = useState(false); - - const readClipboard = async () => { - if (isReading) return; // Prevent concurrent reads - - setIsReading(true); - try { - console.log("ClipboardDisplay: Attempting to read clipboard..."); - - if (!navigator.clipboard) { - console.log("ClipboardDisplay: navigator.clipboard not available"); - setClipboardContent("Clipboard API not available"); - return; - } - - if (!navigator.clipboard.readText) { - console.log("ClipboardDisplay: navigator.clipboard.readText not available"); - setClipboardContent("Clipboard read not supported"); - return; - } - - console.log("ClipboardDisplay: Calling navigator.clipboard.readText()..."); - const content = await navigator.clipboard.readText(); - console.log("ClipboardDisplay: Successfully read clipboard:", content.length, "characters"); - setClipboardContent(content); - } catch (error) { - // This is expected if user hasn't granted clipboard permissions - // or if we're in a context where reading isn't allowed - console.log("ClipboardDisplay: Error reading clipboard:", error); - console.log("ClipboardDisplay: Error name:", (error as Error).name); - console.log("ClipboardDisplay: Error message:", (error as Error).message); - - // More specific error messages based on error type - if (error instanceof DOMException) { - switch (error.name) { - case 'NotAllowedError': - setClipboardContent("Clipboard access denied - check permissions"); - break; - case 'NotFoundError': - setClipboardContent("No clipboard data found"); - break; - case 'SecurityError': - setClipboardContent("Clipboard access blocked by security policy"); - break; - default: - setClipboardContent(`Clipboard error: ${error.name}`); - } - } else { - setClipboardContent("Unable to read clipboard"); - } - } finally { - setIsReading(false); - } - }; - - // Read clipboard on mount and then every 3 seconds - useEffect(() => { - readClipboard(); - - const interval = setInterval(() => { - readClipboard(); - }, 3000); - - return () => clearInterval(interval); - }, []); - - const truncateText = (text: string, maxLength: number = 60) => { - if (text.length <= maxLength) return text; - return text.substring(0, maxLength) + "..."; - }; - - const displayText = truncateText(clipboardContent); - - return ( - <PanelSectionRow> - <div style={{ - padding: "8px", - backgroundColor: "rgba(255, 255, 255, 0.05)", - borderRadius: "4px", - border: "1px solid rgba(255, 255, 255, 0.1)", - marginBottom: "8px" - }}> - <div style={{ - display: "flex", - alignItems: "center", - gap: "8px", - marginBottom: "4px" - }}> - <FaClipboard style={{ color: "#888", fontSize: "12px" }} /> - <div style={{ - fontSize: "12px", - fontWeight: "bold", - color: "#888", - textTransform: "uppercase" - }}> - Current Clipboard - </div> - {isReading && ( - <FaEye style={{ - color: "#888", - fontSize: "10px", - animation: "pulse 1s ease-in-out infinite" - }} /> - )} - </div> - <div style={{ - fontSize: "11px", - color: clipboardContent.includes("error") || - clipboardContent.includes("denied") || - clipboardContent.includes("not available") || - clipboardContent.includes("not supported") || - clipboardContent.includes("blocked") || - clipboardContent === "Unable to read clipboard" - ? "#ff6b6b" - : "#fff", - fontFamily: "monospace", - wordBreak: "break-word", - lineHeight: "1.3", - minHeight: "14px" - }}> - {displayText || "Reading clipboard..."} - </div> - </div> - <style>{` - @keyframes pulse { - 0% { opacity: 0.5; } - 50% { opacity: 1; } - 100% { opacity: 0.5; } - } - `}</style> - </PanelSectionRow> - ); -} diff --git a/src/components/Content.tsx b/src/components/Content.tsx index fdb8672..08027d3 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -8,12 +8,8 @@ import { InstallationButton } from "./InstallationButton"; import { ConfigurationSection } from "./ConfigurationSection"; import { ProfileManagement } from "./ProfileManagement"; import { UsageInstructions } from "./UsageInstructions"; -// import { WikiButton } from "./WikiButton"; -// import { ClipboardButton } from "./ClipboardButton"; import { SmartClipboardButton } from "./SmartClipboardButton"; import { FgmodClipboardButton } from "./FgmodClipboardButton"; -// import { ClipboardDisplay } from "./ClipboardDisplay"; -// import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { NerdStuffModal } from "./NerdStuffModal"; import FlatpaksModal from "./FlatpaksModal"; import { ConfigurationData } from "../config/configSchema"; @@ -106,7 +102,6 @@ export function Content() { {/* Clipboard buttons - only show if installed */} {isInstalled && ( <> - {/* <ClipboardDisplay /> */} <SmartClipboardButton /> <FgmodClipboardButton /> </> @@ -131,15 +126,8 @@ export function Content() { /> )} - {/* Usage instructions - always visible for user guidance */} - <UsageInstructions config={config} /> - - {/* Wiki and clipboard buttons - always available for documentation */} - {/* <WikiButton /> */} - {/* <ClipboardButton /> */} - - {/* Plugin Update Checker */} - {/* <PluginUpdateChecker /> */} + {/* Usage instructions - always visible for user guidance */} + <UsageInstructions config={config} /> {/* Show installation components at bottom when fully installed */} {isInstalled && ( diff --git a/src/components/PluginUpdateChecker.tsx b/src/components/PluginUpdateChecker.tsx deleted file mode 100644 index 9fa2afb..0000000 --- a/src/components/PluginUpdateChecker.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - ButtonItem, - PanelSection, - PanelSectionRow, - Field, - Focusable -} from '@decky/ui'; -import { checkForPluginUpdate, downloadPluginUpdate, UpdateCheckResult, UpdateDownloadResult } from '../api/lsfgApi'; - -interface PluginUpdateCheckerProps { - // Add any props if needed -} - -interface UpdateInfo { - updateAvailable: boolean; - currentVersion: string; - latestVersion: string; - releaseNotes: string; - releaseDate: string; - downloadUrl: string; -} - -export const PluginUpdateChecker: React.FC<PluginUpdateCheckerProps> = () => { - const [checkingUpdate, setCheckingUpdate] = useState(false); - const [downloadingUpdate, setDownloadingUpdate] = useState(false); - const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null); - const [updateError, setUpdateError] = useState<string | null>(null); - const [downloadResult, setDownloadResult] = useState<UpdateDownloadResult | null>(null); - - // Auto-hide error messages after 5 seconds - useEffect(() => { - if (updateError) { - const timer = setTimeout(() => { - setUpdateError(null); - }, 5000); - return () => clearTimeout(timer); - } - return undefined; - }, [updateError]); - - const handleCheckForUpdate = async () => { - setCheckingUpdate(true); - setUpdateError(null); - setUpdateInfo(null); - setDownloadResult(null); // Clear previous download result - - try { - const result: UpdateCheckResult = await checkForPluginUpdate(); - - if (result.success) { - setUpdateInfo({ - updateAvailable: result.update_available, - currentVersion: result.current_version, - latestVersion: result.latest_version, - releaseNotes: result.release_notes, - releaseDate: result.release_date, - downloadUrl: result.download_url - }); - - // Simple console log instead of toast since showToast may not be available - if (result.update_available) { - console.log("Update available!", `Version ${result.latest_version} is now available.`); - } else { - console.log("Up to date!", "You have the latest version installed."); - } - } else { - setUpdateError(result.error || "Failed to check for updates"); - } - } catch (error) { - setUpdateError(`Error checking for updates: ${error}`); - } finally { - setCheckingUpdate(false); - } - }; - - const handleDownloadUpdate = async () => { - if (!updateInfo?.downloadUrl) return; - - setDownloadingUpdate(true); - setUpdateError(null); - setDownloadResult(null); - - try { - const result: UpdateDownloadResult = await downloadPluginUpdate(updateInfo.downloadUrl); - - if (result.success) { - setDownloadResult(result); - console.log("✓ Download complete!", `Plugin downloaded to ${result.download_path}`); - } else { - setUpdateError(result.error || "Failed to download update"); - } - } catch (error) { - setUpdateError(`Error downloading update: ${error}`); - } finally { - setDownloadingUpdate(false); - } - }; - - const getStatusMessage = () => { - if (!updateInfo) return null; - - if (updateInfo.updateAvailable) { - if (downloadResult?.success) { - return "✓ v" + updateInfo.latestVersion + " downloaded - ready to install"; - } else { - return "Update available: v" + updateInfo.latestVersion; - } - } else { - return "Up to date (v" + updateInfo.currentVersion + ")"; - } - }; - - return ( - <PanelSection title="PLUGIN UPDATES"> - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleCheckForUpdate} - disabled={checkingUpdate} - description={getStatusMessage()} - > - {checkingUpdate ? 'Checking for updates...' : 'Check for Updates'} - </ButtonItem> - </PanelSectionRow> - - {updateInfo && updateInfo.updateAvailable && !downloadResult?.success && ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleDownloadUpdate} - disabled={downloadingUpdate} - description={`Download version ${updateInfo.latestVersion}`} - > - {downloadingUpdate ? 'Downloading...' : 'Download Update'} - </ButtonItem> - </PanelSectionRow> - )} - - {downloadResult?.success && ( - <> - <PanelSectionRow> - <Field label="Download Complete!"> - <Focusable> - File saved to: {downloadResult.download_path} - </Focusable> - </Field> - </PanelSectionRow> - - <PanelSectionRow> - <Field label="Installation Instructions:"> - <Focusable> - 1. Go to Decky Loader settings - <br />2. Click "Developer" tab - <br />3. Click "Uninstall" next to "decky-lsfg-vk" - <br />4. Click "Install from ZIP" - <br />5. Select the downloaded file - <br />6. Restart Steam or reload plugins - </Focusable> - </Field> - </PanelSectionRow> - </> - )} - - {updateError && ( - <PanelSectionRow> - <Field label="Error:"> - <Focusable> - {updateError} - </Focusable> - </Field> - </PanelSectionRow> - )} - </PanelSection> - ); -}; diff --git a/src/components/WikiButton.tsx b/src/components/WikiButton.tsx deleted file mode 100644 index 065fb8e..0000000 --- a/src/components/WikiButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { PanelSectionRow, ButtonItem } from "@decky/ui"; -import { FaBook } from "react-icons/fa"; - -export function WikiButton() { - const handleWikiClick = () => { - window.open("https://github.com/PancakeTAS/lsfg-vk/wiki", "_blank"); - }; - - return ( - <PanelSectionRow> - <ButtonItem - layout="below" - onClick={handleWikiClick} - > - <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> - <FaBook /> - <div>LSFG-VK Wiki</div> - </div> - </ButtonItem> - </PanelSectionRow> - ); -} diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..95e7451 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,11 @@ +export { Content } from "./Content"; +export { StatusDisplay } from "./StatusDisplay"; +export { InstallationButton } from "./InstallationButton"; +export { ConfigurationSection } from "./ConfigurationSection"; +export { FpsMultiplierControl } from "./FpsMultiplierControl"; +export { UsageInstructions } from "./UsageInstructions"; +export { SmartClipboardButton } from "./SmartClipboardButton"; +export { FgmodClipboardButton } from "./FgmodClipboardButton"; +export { NerdStuffModal } from "./NerdStuffModal"; +export { default as FlatpaksModal } from "./FlatpaksModal"; +export { ProfileManagement } from "./ProfileManagement"; |
