diff options
| author | Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> | 2025-07-21 13:50:23 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-21 13:50:23 -0400 |
| commit | b0621d15c675b7b8c1615b0699cc85ea1430a728 (patch) | |
| tree | 01f555d9b592be219da5a1da6a70950f11c7cdc0 | |
| parent | 9573344450de451b8f9c7295c11318010d67f1d5 (diff) | |
| download | Decky-Framegen-0.11.3.tar.gz Decky-Framegen-0.11.3.zip | |
* hooking clipboard automation button, hide plugin wiki for now
* add direct copy to clip buttons for patch and unpatch
| -rw-r--r-- | justfile | 2 | ||||
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/components/DocumentationButton.tsx | 2 | ||||
| -rw-r--r-- | src/components/FGModInstallerSection.tsx | 17 | ||||
| -rw-r--r-- | src/components/SmartClipboardButton.tsx | 128 | ||||
| -rw-r--r-- | src/utils/constants.ts | 2 |
6 files changed, 150 insertions, 3 deletions
@@ -5,7 +5,7 @@ build: sudo rm -rf node_modules && .vscode/build.sh test: - scp "/Users/kurt/Developer/FG-plugins/Decky-Framegen/out/Decky-Framegen.zip" deck@192.168.0.6:~/Desktop + scp "out/Decky-Framegen.zip" deck@192.168.0.6:~/Desktop clean: rm -rf node_modules dist
\ No newline at end of file diff --git a/package.json b/package.json index fba3228..b614bc4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decky-framegen", - "version": "0.11.2", + "version": "0.11.3", "description": "plugin to install OptiScaler bleeding-edge and enable upscaling and framegen in a large variety of games.", "type": "module", "scripts": { diff --git a/src/components/DocumentationButton.tsx b/src/components/DocumentationButton.tsx index 4125fd3..7069bc2 100644 --- a/src/components/DocumentationButton.tsx +++ b/src/components/DocumentationButton.tsx @@ -12,6 +12,7 @@ export function DocumentationButton() { return ( <PanelSection> + {/* <PanelSectionRow> <ButtonItem layout="below" @@ -23,6 +24,7 @@ export function DocumentationButton() { </div> </ButtonItem> </PanelSectionRow> + */} <PanelSectionRow> <ButtonItem layout="below" diff --git a/src/components/FGModInstallerSection.tsx b/src/components/FGModInstallerSection.tsx index 1e347c0..6777301 100644 --- a/src/components/FGModInstallerSection.tsx +++ b/src/components/FGModInstallerSection.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import { PanelSection, PanelSectionRow, ButtonItem } from "@decky/ui"; import { runInstallFGMod, runUninstallFGMod } from "../api"; import { OperationResult } from "./ResultDisplay"; +import { SmartClipboardButton } from "./SmartClipboardButton"; import { createAutoCleanupTimer } from "../utils"; import { TIMEOUTS, MESSAGES, STYLES } from "../utils/constants"; @@ -98,6 +99,22 @@ export function FGModInstallerSection({ pathExists, setPathExists }: FGModInstal </div> </PanelSectionRow> ) : null} + + {pathExists === true ? ( + <SmartClipboardButton + command="~/fgmod/fgmod %command%" + buttonText="Copy Patch Command" + successMessage="Patch command ready to paste" + /> + ) : null} + + {pathExists === true ? ( + <SmartClipboardButton + command="~/fgmod/fgmod-uninstaller.sh %command%" + buttonText="Copy Unpatch Command" + successMessage="Unpatch command ready to paste" + /> + ) : null} </PanelSection> ); } diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx new file mode 100644 index 0000000..7d250f5 --- /dev/null +++ b/src/components/SmartClipboardButton.tsx @@ -0,0 +1,128 @@ +import { useState } from "react"; +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { FaClipboard } from "react-icons/fa"; +import { toaster } from "@decky/api"; + +interface SmartClipboardButtonProps { + command?: string; + buttonText?: string; + successMessage?: string; +} + +export function SmartClipboardButton({ + command = "~/fgmod/fgmod %command%", + buttonText = "Copy Launch Command", + successMessage = "Launch option ready to paste" +}: SmartClipboardButtonProps) { + const [isLoading, setIsLoading] = useState(false); + + const getLaunchOptionText = (): string => { + return command; + }; + + const copyToClipboard = async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const text = getLaunchOptionText(); + + // Use the proven input simulation method + const tempInput = document.createElement('input'); + tempInput.value = text; + tempInput.style.position = 'absolute'; + tempInput.style.left = '-9999px'; + document.body.appendChild(tempInput); + + // 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; + } catch (clipboardError) { + console.error('Both copy methods failed:', e, clipboardError); + } + } + + // Clean up + document.body.removeChild(tempInput); + + if (copySuccess) { + // Verify the copy worked by reading back + try { + const readBack = await navigator.clipboard.readText(); + if (readBack === text) { + toaster.toast({ + title: "Copied to Clipboard!", + body: successMessage + }); + } else { + // Copy worked but verification failed - still consider it success + toaster.toast({ + title: "Copied to Clipboard!", + body: "Launch option copied (verification unavailable)" + }); + } + } catch (e) { + // Verification failed but copy likely worked + toaster.toast({ + title: "Copied to Clipboard!", + body: "Launch option copied successfully" + }); + } + } else { + toaster.toast({ + title: "Copy Failed", + body: "Unable to copy to clipboard" + }); + } + + } catch (error) { + toaster.toast({ + title: "Copy Failed", + body: `Error: ${String(error)}` + }); + } finally { + setIsLoading(false); + } + }; + + return ( + <PanelSectionRow> + <ButtonItem + layout="below" + onClick={copyToClipboard} + disabled={isLoading} + > + <div style={{ display: "flex", alignItems: "center", gap: "8px" }}> + {isLoading ? ( + <FaClipboard style={{ + animation: "pulse 1s ease-in-out infinite", + opacity: 0.7 + }} /> + ) : ( + <FaClipboard /> + )} + <div>{isLoading ? "Copying..." : buttonText}</div> + </div> + </ButtonItem> + <style>{` + @keyframes pulse { + 0% { opacity: 0.7; } + 50% { opacity: 1; } + 100% { opacity: 0.7; } + } + `}</style> + </PanelSectionRow> + ); +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index c949521..3e39067 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -62,5 +62,5 @@ export const MESSAGES = { installSuccess: "✅ OptiScaler mod installed successfully!", uninstallSuccess: "✅ OptiScaler mod removed successfully.", instructionTitle: "How to Use:", - instructionText: "Select and patch a game below to enable frame generation (or use clipboard button to copy and paste into launch options)\n\nIn-game: Enable DLSS in graphics settings, or assign a back button to keyboard's 'Insert' key for extended OptiScaler options" + instructionText: "Select and patch a game below to enable frame generation, or use the Copy Patch/Unpatch Command buttons for manual setup.\n\nIn-game: Enable DLSS in graphics settings, or assign a back button to keyboard's 'Insert' key for extended OptiScaler options" }; |
