1//go:build windows
2
3package tui
4
5import (
6 "errors"
7 "fmt"
8 "os/exec"
9)
10
11const readClipboardImagePowerShell = `
12Add-Type -AssemblyName System.Windows.Forms
13Add-Type -AssemblyName System.Drawing
14if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) { exit 3 }
15$image = [System.Windows.Forms.Clipboard]::GetImage()
16if ($null -eq $image) { exit 3 }
17$stream = New-Object System.IO.MemoryStream
18try {
19 $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
20 $bytes = $stream.ToArray()
21 $stdout = [Console]::OpenStandardOutput()
22 $stdout.Write($bytes, 0, $bytes.Length)
23 $stdout.Flush()
24} finally {
25 $stream.Dispose()
26 $image.Dispose()
27}
28`
29
30// ReadClipboardImagePNG reads the Windows desktop clipboard through the
31// PowerShell installation included with supported Windows versions. STA mode
32// is required by the Windows Forms clipboard API.
33func ReadClipboardImagePNG() (string, []byte, bool, error) {
34 return readWindowsClipboardImagePNG(runClipboardImageCommand)
35}
36
37func readWindowsClipboardImagePNG(run func(string, int, ...string) ([]byte, error)) (string, []byte, bool, error) {
38 commands := []string{"powershell.exe", "pwsh.exe"}
39 var lastErr error
40 found := false
41 for _, name := range commands {
42 data, err := run(name, maxClipboardImageBytes,
43 "-NoProfile", "-NonInteractive", "-STA", "-Command", readClipboardImagePowerShell)
44 if errors.Is(err, errClipboardImageCommandUnavailable) {
45 continue
46 }
47 found = true
48 if clipboardHasNoImageExit(err) {
49 return "", nil, false, nil
50 }
51 if err != nil {
52 lastErr = err
53 continue
54 }
55 pngData, err := normalizeClipboardImagePNG(data)
56 if err != nil {
57 return "", nil, false, fmt.Errorf("invalid clipboard image: %w", err)
58 }
59 return "", pngData, true, nil
60 }
61 if found && lastErr != nil {
62 return "", nil, false, fmt.Errorf("read image clipboard: %w", lastErr)
63 }
64 return "", nil, false, nil
65}
66
67func clipboardHasNoImageExit(err error) bool {
68 var exitErr *exec.ExitError
69 return errors.As(err, &exitErr) && exitErr.ExitCode() == 3
70}