summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>2025-07-21 13:50:23 -0400
committerGitHub <noreply@github.com>2025-07-21 13:50:23 -0400
commitb0621d15c675b7b8c1615b0699cc85ea1430a728 (patch)
tree01f555d9b592be219da5a1da6a70950f11c7cdc0
parent9573344450de451b8f9c7295c11318010d67f1d5 (diff)
downloadDecky-Framegen-b0621d15c675b7b8c1615b0699cc85ea1430a728.tar.gz
Decky-Framegen-b0621d15c675b7b8c1615b0699cc85ea1430a728.zip
hooking clipboard automation button, hide plugin wiki for now (#118)v0.11.4v0.11.3
* hooking clipboard automation button, hide plugin wiki for now * add direct copy to clip buttons for patch and unpatch
-rw-r--r--justfile2
-rw-r--r--package.json2
-rw-r--r--src/components/DocumentationButton.tsx2
-rw-r--r--src/components/FGModInstallerSection.tsx17
-rw-r--r--src/components/SmartClipboardButton.tsx128
-rw-r--r--src/utils/constants.ts2
6 files changed, 150 insertions, 3 deletions
diff --git a/justfile b/justfile
index 4e9a17a..9fa9cfb 100644
--- a/justfile
+++ b/justfile
@@ -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"
};