feat(ui): add Windows clipboard image support and Alt+V paste workaround (#15218)

Co-authored-by: sgeraldes <sgeraldes@users.noreply.github.com>
This commit is contained in:
Jacob Richman
2025-12-17 14:05:25 -08:00
committed by GitHub
parent a6d1245a54
commit 3d486ec1e9
5 changed files with 136 additions and 21 deletions

View File

@@ -30,10 +30,24 @@ export const IMAGE_EXTENSIONS = [
const PATH_PREFIX_PATTERN = /^([/~.]|[a-zA-Z]:|\\\\)/;
/**
* Checks if the system clipboard contains an image (macOS only for now)
* Checks if the system clipboard contains an image (macOS and Windows)
* @returns true if clipboard contains an image
*/
export async function clipboardHasImage(): Promise<boolean> {
if (process.platform === 'win32') {
try {
const { stdout } = await spawnAsync('powershell', [
'-NoProfile',
'-Command',
'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Clipboard]::ContainsImage()',
]);
return stdout.trim() === 'True';
} catch (error) {
debugLogger.warn('Error checking clipboard for image:', error);
return false;
}
}
if (process.platform !== 'darwin') {
return false;
}
@@ -44,20 +58,21 @@ export async function clipboardHasImage(): Promise<boolean> {
const imageRegex =
/«class PNGf»|TIFF picture|JPEG picture|GIF picture|«class JPEG»|«class TIFF»/;
return imageRegex.test(stdout);
} catch {
} catch (error) {
debugLogger.warn('Error checking clipboard for image:', error);
return false;
}
}
/**
* Saves the image from clipboard to a temporary file (macOS only for now)
* Saves the image from clipboard to a temporary file (macOS and Windows)
* @param targetDir The target directory to create temp files within
* @returns The path to the saved image file, or null if no image or error
*/
export async function saveClipboardImage(
targetDir?: string,
): Promise<string | null> {
if (process.platform !== 'darwin') {
if (process.platform !== 'darwin' && process.platform !== 'win32') {
return null;
}
@@ -71,6 +86,40 @@ export async function saveClipboardImage(
// Generate a unique filename with timestamp
const timestamp = new Date().getTime();
if (process.platform === 'win32') {
const tempFilePath = path.join(tempDir, `clipboard-${timestamp}.png`);
// The path is used directly in the PowerShell script.
const psPath = tempFilePath.replace(/'/g, "''");
const script = `
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
if ([System.Windows.Forms.Clipboard]::ContainsImage()) {
$image = [System.Windows.Forms.Clipboard]::GetImage()
$image.Save('${psPath}', [System.Drawing.Imaging.ImageFormat]::Png)
Write-Output "success"
}
`;
const { stdout } = await spawnAsync('powershell', [
'-NoProfile',
'-Command',
script,
]);
if (stdout.trim() === 'success') {
try {
const stats = await fs.stat(tempFilePath);
if (stats.size > 0) {
return tempFilePath;
}
} catch {
// File doesn't exist
}
}
return null;
}
// AppleScript clipboard classes to try, in order of preference.
// macOS converts clipboard images to these formats (WEBP/HEIC/HEIF not supported by osascript).
const formats = [