From 0668428a5ebc221d39b907f251dc0dc43e30a2df Mon Sep 17 00:00:00 2001 From: xXJSONDeruloXx Date: Mon, 21 Jul 2025 12:28:36 -0400 Subject: testing alt keyboard copy methods --- src/api/lsfgApi.ts | 10 + src/components/ClipboardExperiments.tsx | 509 ++++++++++++++++++++++++++++++++ src/components/Content.tsx | 6 + src/components/SmartClipboardButton.tsx | 245 +++++++++++++++ src/components/index.ts | 2 + 5 files changed, 772 insertions(+) create mode 100644 src/components/ClipboardExperiments.tsx create mode 100644 src/components/SmartClipboardButton.tsx (limited to 'src') diff --git a/src/api/lsfgApi.ts b/src/api/lsfgApi.ts index 74caa57..b984612 100644 --- a/src/api/lsfgApi.ts +++ b/src/api/lsfgApi.ts @@ -36,6 +36,13 @@ export interface DllStatsResult { error?: string; } +export interface ClipboardResult { + success: boolean; + method?: string; + message?: string; + error?: string; +} + // Use centralized configuration data type export type LsfgConfig = ConfigurationData; @@ -99,6 +106,9 @@ export const getLaunchOption = callable<[], LaunchOptionResult>("get_launch_opti export const getConfigFileContent = callable<[], FileContentResult>("get_config_file_content"); export const getLaunchScriptContent = callable<[], FileContentResult>("get_launch_script_content"); +// Clipboard API +export const copyToSystemClipboard = callable<[string], ClipboardResult>("copy_to_system_clipboard"); + // Updated config function using centralized configuration export const updateLsfgConfig = callable< [string, number, number, boolean, boolean, string, number, boolean, boolean], diff --git a/src/components/ClipboardExperiments.tsx b/src/components/ClipboardExperiments.tsx new file mode 100644 index 0000000..ec7d9ab --- /dev/null +++ b/src/components/ClipboardExperiments.tsx @@ -0,0 +1,509 @@ +import { useState } from "react"; +import { PanelSectionRow, ButtonItem, Field, Focusable } from "@decky/ui"; +import { FaClipboard, FaFlask, FaRocket, FaCog, FaTerminal } from "react-icons/fa"; +import { toaster } from "@decky/api"; +import { getLaunchOption, copyToSystemClipboard } from "../api/lsfgApi"; + +interface ExperimentResult { + success: boolean; + method: string; + error?: string; + details?: string; +} + +export function ClipboardExperiments() { + const [results, setResults] = useState([]); + const [isLoading, setIsLoading] = useState(null); + + const addResult = (result: ExperimentResult) => { + setResults(prev => [...prev, { ...result, timestamp: Date.now() }]); + }; + + const getLaunchOptionText = async (): Promise => { + try { + const result = await getLaunchOption(); + return result.launch_option || "~/lsfg %command%"; + } catch (error) { + return "~/lsfg %command%"; + } + }; + + // Approach 1: Direct Navigator Clipboard API + const testDirectClipboard = async () => { + setIsLoading("direct"); + try { + const text = await getLaunchOptionText(); + await navigator.clipboard.writeText(text); + + // Test if it actually worked by reading back + const readBack = await navigator.clipboard.readText(); + const success = readBack === text; + + addResult({ + success, + method: "Direct Navigator Clipboard", + details: success ? `Successfully copied: "${text}"` : `Mismatch: wrote "${text}", read "${readBack}"` + }); + + if (success) { + toaster.toast({ + title: "Clipboard Success!", + body: "Direct navigator.clipboard API worked" + }); + } + } catch (error) { + addResult({ + success: false, + method: "Direct Navigator Clipboard", + error: String(error) + }); + } finally { + setIsLoading(null); + } + }; + + // Approach 2: CEF Browser with Data URL + const testDataUrlApproach = async () => { + setIsLoading("dataurl"); + try { + const text = await getLaunchOptionText(); + const htmlContent = ` + + + + + Clipboard Helper + + + +
+

๐Ÿš€ Clipboard Automation Test

+

Attempting to copy launch option: ${text}

+
Working...
+
+ +
+ + + + `; + + const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); + window.open(dataUrl, '_blank'); + + addResult({ + success: true, + method: "Data URL Browser Window", + details: "Opened data URL with auto-copy script" + }); + } catch (error) { + addResult({ + success: false, + method: "Data URL Browser Window", + error: String(error) + }); + } finally { + setIsLoading(null); + } + }; + + // Approach 3: Focused Element + Selection + Input API + const testInputSimulation = async () => { + setIsLoading("input"); + try { + const text = await getLaunchOptionText(); + + // Create a temporary input element + 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 different copy methods + let copySuccess = false; + let method = ''; + + // Method 1: execCommand (deprecated but might work) + try { + if (document.execCommand('copy')) { + copySuccess = true; + method = 'execCommand'; + } + } catch (e) {} + + // Method 2: Navigator clipboard on selected text + if (!copySuccess) { + try { + await navigator.clipboard.writeText(text); + copySuccess = true; + method = 'navigator.clipboard'; + } catch (e) {} + } + + // Clean up + document.body.removeChild(tempInput); + + if (copySuccess) { + // Verify + try { + const readBack = await navigator.clipboard.readText(); + const verified = readBack === text; + addResult({ + success: verified, + method: `Input Simulation (${method})`, + details: verified ? "Successfully copied and verified" : "Copy worked but verification failed" + }); + } catch (e) { + addResult({ + success: true, + method: `Input Simulation (${method})`, + details: "Copy appeared to work but couldn't verify" + }); + } + } else { + addResult({ + success: false, + method: "Input Simulation", + error: "All copy methods failed" + }); + } + } catch (error) { + addResult({ + success: false, + method: "Input Simulation", + error: String(error) + }); + } finally { + setIsLoading(null); + } + }; + + // Approach 4: Backend Clipboard + const testBackendClipboard = async () => { + setIsLoading("backend"); + try { + const text = await getLaunchOptionText(); + + const result = await copyToSystemClipboard(text); + + if (result.success) { + addResult({ + success: true, + method: `Backend System Clipboard (${result.method})`, + details: result.message || "Successfully copied to system clipboard" + }); + + toaster.toast({ + title: "Clipboard Success!", + body: `Copied using ${result.method}` + }); + } else { + addResult({ + success: false, + method: "Backend System Clipboard", + error: result.error || "Unknown error" + }); + } + } catch (error) { + addResult({ + success: false, + method: "Backend System Clipboard", + error: String(error) + }); + } finally { + setIsLoading(null); + } + }; + + // Approach 5: Hybrid approach with immediate feedback + const testHybridApproach = async () => { + setIsLoading("hybrid"); + try { + const text = await getLaunchOptionText(); + + // Try direct first + let directWorked = false; + try { + await navigator.clipboard.writeText(text); + const readBack = await navigator.clipboard.readText(); + directWorked = readBack === text; + } catch (e) {} + + if (directWorked) { + addResult({ + success: true, + method: "Hybrid (Direct Success)", + details: "Direct clipboard API worked, no browser needed" + }); + + toaster.toast({ + title: "Clipboard Success!", + body: "Launch option copied to clipboard" + }); + } else { + // Fall back to optimized browser approach + const htmlContent = ` + + + + + Quick Copy + + + +
+

๐Ÿš€ Clipboard Helper

+

Copying: ${text}

+
โณ Working...
+ + +
+ + + + `; + + const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); + window.open(dataUrl, '_blank', 'width=500,height=300'); + + addResult({ + success: true, + method: "Hybrid (Browser Fallback)", + details: "Direct failed, opened optimized browser window" + }); + } + } catch (error) { + addResult({ + success: false, + method: "Hybrid Approach", + error: String(error) + }); + } finally { + setIsLoading(null); + } + }; + + const clearResults = () => { + setResults([]); + }; + + return ( + <> + +
+ ๐Ÿงช Clipboard Automation Experiments +
+
+ + +
+ Testing different approaches to automate clipboard access in Steam Deck gaming mode: +
+
+ + {/* Test Buttons */} + + +
+ +
Test Direct Clipboard API
+ {isLoading === "direct" &&
โณ
} +
+
+
+ + + +
+ +
Test Data URL Browser
+ {isLoading === "dataurl" &&
โณ
} +
+
+
+ + + +
+ +
Test Input Simulation
+ {isLoading === "input" &&
โณ
} +
+
+
+ + + +
+ +
Test Backend Clipboard
+ {isLoading === "backend" &&
โณ
} +
+
+
+ + + +
+ +
Test Hybrid Approach (Recommended)
+ {isLoading === "hybrid" &&
โณ
} +
+
+
+ + {/* Results Section */} + {results.length > 0 && ( + <> + + +
+
+ {results.filter(r => r.success).length} successful, {results.filter(r => !r.success).length} failed +
+ + Clear + +
+
+
+ + {results.slice(-5).map((result, index) => ( + + +
+
+ {result.success ? "โœ…" : "โŒ"} {result.method} +
+ {result.details && ( +
+ {result.details} +
+ )} + {result.error && ( +
+ Error: {result.error} +
+ )} +
+
+
+ ))} + + )} + + ); +} diff --git a/src/components/Content.tsx b/src/components/Content.tsx index 9ce4c35..f6f24dc 100644 --- a/src/components/Content.tsx +++ b/src/components/Content.tsx @@ -8,6 +8,8 @@ import { ConfigurationSection } from "./ConfigurationSection"; import { UsageInstructions } from "./UsageInstructions"; import { WikiButton } from "./WikiButton"; import { ClipboardButton } from "./ClipboardButton"; +import { SmartClipboardButton } from "./SmartClipboardButton"; +import { ClipboardExperiments } from "./ClipboardExperiments"; import { PluginUpdateChecker } from "./PluginUpdateChecker"; import { NerdStuffModal } from "./NerdStuffModal"; import { ConfigurationData } from "../config/configSchema"; @@ -83,6 +85,10 @@ export function Content() { + + + {/* Experimental Clipboard Automation */} + {/* Plugin Update Checker */} diff --git a/src/components/SmartClipboardButton.tsx b/src/components/SmartClipboardButton.tsx new file mode 100644 index 0000000..229560d --- /dev/null +++ b/src/components/SmartClipboardButton.tsx @@ -0,0 +1,245 @@ +import { useState } from "react"; +import { PanelSectionRow, ButtonItem } from "@decky/ui"; +import { FaClipboard, FaRocket } from "react-icons/fa"; +import { toaster } from "@decky/api"; +import { getLaunchOption, copyToSystemClipboard } from "../api/lsfgApi"; + +export function SmartClipboardButton() { + const [isLoading, setIsLoading] = useState(false); + + const getLaunchOptionText = async (): Promise => { + try { + const result = await getLaunchOption(); + return result.launch_option || "~/lsfg %command%"; + } catch (error) { + return "~/lsfg %command%"; + } + }; + + const copyToClipboard = async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const text = await getLaunchOptionText(); + + // Strategy 1: Try direct navigator.clipboard first (fastest) + let directSuccess = false; + try { + await navigator.clipboard.writeText(text); + // Verify it worked + const readBack = await navigator.clipboard.readText(); + directSuccess = readBack === text; + } catch (e) { + // Direct clipboard failed, will try alternatives + } + + if (directSuccess) { + toaster.toast({ + title: "Copied to Clipboard!", + body: "Launch option ready to paste" + }); + return; + } + + // Strategy 2: Try backend system clipboard + try { + const result = await copyToSystemClipboard(text); + if (result.success) { + toaster.toast({ + title: "Copied to Clipboard!", + body: `Using ${result.method || "system clipboard"}` + }); + return; + } + } catch (e) { + // Backend failed, fall back to browser + } + + // Strategy 3: Fall back to optimized browser window + const htmlContent = ` + + + + + Quick Copy - Steam Deck Clipboard Helper + + + +
+

๐Ÿš€ Steam Deck Clipboard Helper

+
Copy this launch option for your Steam games:
+
${text}
+
โณ Copying to clipboard...
+
+ + +
+
+
+ + + + `; + + const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(htmlContent); + window.open(dataUrl, '_blank', 'width=600,height=400,scrollbars=no,resizable=yes'); + + toaster.toast({ + title: "Browser Helper Opened", + body: "Clipboard helper window opened with auto-copy" + }); + + } catch (error) { + toaster.toast({ + title: "Copy Failed", + body: `Error: ${String(error)}` + }); + } finally { + setIsLoading(false); + } + }; + + return ( + + +
+ {isLoading ? : } +
{isLoading ? "Copying..." : "Smart Clipboard Copy"}
+
+
+ +
+ ); +} diff --git a/src/components/index.ts b/src/components/index.ts index c0c4804..1a36327 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,6 +5,8 @@ export { ConfigurationSection } from "./ConfigurationSection"; // export { UsageInstructions } from "./UsageInstructions"; export { WikiButton } from "./WikiButton"; export { ClipboardButton } from "./ClipboardButton"; +export { SmartClipboardButton } from "./SmartClipboardButton"; +export { ClipboardExperiments } from "./ClipboardExperiments"; export { LaunchOptionInfo } from "./LaunchOptionInfo"; export { PluginUpdateChecker } from "./PluginUpdateChecker"; export { NerdStuffModal } from "./NerdStuffModal"; -- cgit v1.2.3