Fix clipboard actions over ssh

This commit is contained in:
2026-04-27 12:45:04 +00:00
parent e73fb24422
commit 53ad7b729e

View File

@@ -8,6 +8,36 @@ import { promisify } from "util";
const execAsync = promisify(exec);
// Define a list of clipboard methods with their platform and command.
// The platform is a function that returns true if the method is available on the current platform.
// The command is a function that returns a promise that resolves to the result of the command.
const clipboardMethods = {
pbCopy: {
platform: (platform: string) => platform === 'darwin',
command: async (text: string) => execAsync(`printf '%s' '${text}' | pbcopy`),
},
xclip: {
platform: (platform: string) => platform === 'linux',
command: async (text: string) => execAsync(`printf '%s' '${text}' | xclip -selection clipboard`),
},
xsel: {
platform: (platform: string) => platform === 'linux',
command: async (text: string) => execAsync(`printf '%s' '${text}' | xsel --clipboard --input`),
},
ssh: {
platform: (platform: string) => platform === 'linux',
command: async (text: string) => process.stdout.write(`\x1b]52;c;${Buffer.from(text, 'utf-8').toString('base64')}\x07`),
},
clip: {
platform: (platform: string) => platform === 'windows',
command: async (text: string) => execAsync(`echo|set /p="${text}" | clip`),
},
clipboardy: {
platform: (platform: string) => platform === 'windows',
command: async (text: string) => clipboardy.writeSync(text),
},
}
/**
* Attempts to copy text to clipboard using multiple methods.
* Tries native commands first (most reliable), then clipboardy as fallback.
@@ -21,46 +51,25 @@ export async function copyToClipboard(text: string): Promise<void> {
// Escape the text for shell commands
const escapedText = text.replace(/'/g, "'\\''");
// Try native commands first - they're more reliable
try {
if (platform === "darwin") {
// macOS - use pbcopy directly
await execAsync(`printf '%s' '${escapedText}' | pbcopy`);
return;
} else if (platform === "linux") {
// Linux - try xclip, then xsel
try {
await execAsync(
`printf '%s' '${escapedText}' | xclip -selection clipboard`,
);
return;
} catch {
try {
await execAsync(
`printf '%s' '${escapedText}' | xsel --clipboard --input`,
);
return;
} catch {
// Fall through to clipboardy
}
}
} else if (platform === "win32") {
// Windows - use clip.exe
await execAsync(`echo|set /p="${text}" | clip`);
return;
}
} catch {
// Native command failed, try clipboardy
}
const availableMethods = Object.values(clipboardMethods).filter(method => method.platform(platform));
// Fallback to clipboardy
const errors: Error[] = [];
for (const method of availableMethods) {
try {
clipboardy.writeSync(text);
if (method.platform(platform)) {
await method.command(escapedText);
} else {
continue;
}
return;
} catch {
// clipboardy also failed
} catch(error) {
if (error instanceof Error) {
errors.push(error);
}
}
}
// All methods failed
throw new Error(`Clipboard not available. Install xclip or xsel on Linux.`);
throw new Error(`Clipboard not available. ${errors.map(error => error.message).join('\n')}`);
}