//go:build darwin package clipboard import ( "fmt" "os" "os/exec" "strings" ) type darwinClipboard struct{} // New creates a macOS clipboard (pbpaste/pbcopy). func New() (Clipboard, error) { return newDarwinClipboard() } func newDarwinClipboard() (*darwinClipboard, error) { // Verify pbpaste and pbcopy are available if err := checkBinary("pbpaste"); err != nil { return nil, fmt.Errorf("clip-sync: pbpaste not found: %w", err) } if err := checkBinary("pbcopy"); err != nil { return nil, fmt.Errorf("clip-sync: pbcopy not found: %w", err) } return &darwinClipboard{}, nil } func (c *darwinClipboard) Read() (string, error) { cmd := exec.Command("pbpaste") out, err := cmd.Output() if err != nil { return "", fmt.Errorf("clipboard read (pbpaste): %w", err) } return strings.TrimRight(string(out), "\n\r"), nil } func (c *darwinClipboard) Write(text string) error { cmd := exec.Command("pbcopy") cmd.Stdin = strings.NewReader(text) if err := cmd.Run(); err != nil { return fmt.Errorf("clipboard write (pbcopy): %w", err) } return nil } // ReadImage reads a PNG image from the clipboard via AppleScript, writing it to // a temporary file and reading it back. func (c *darwinClipboard) ReadImage() (string, []byte, error) { tmp, err := os.CreateTemp("", "clip-sync-*.png") if err != nil { return "", nil, err } tmpPath := tmp.Name() tmp.Close() defer os.Remove(tmpPath) // AppleScript type code PNGf, wrapped in the chevron quotes AppleScript uses. script := `set f to (open for access POSIX file "` + tmpPath + `" with write permission) set eof f to 0 write (the clipboard as «class PNGf») to f close access f` if err := exec.Command("osascript", "-e", script).Run(); err != nil { return "", nil, ErrNoImage } data, err := os.ReadFile(tmpPath) if err != nil || len(data) == 0 { return "", nil, ErrNoImage } return "image/png", data, nil } // WriteImage writes a PNG image to the clipboard via AppleScript. func (c *darwinClipboard) WriteImage(mime string, data []byte) error { if mime != "" && mime != "image/png" { return fmt.Errorf("clipboard: unsupported image mime %q (only PNG)", mime) } tmp, err := os.CreateTemp("", "clip-sync-*.png") if err != nil { return err } if _, err := tmp.Write(data); err != nil { tmp.Close() os.Remove(tmp.Name()) return err } tmpPath := tmp.Name() tmp.Close() defer os.Remove(tmpPath) script := `set the clipboard to (read (POSIX file "` + tmpPath + `") as «class PNGf»)` if err := exec.Command("osascript", "-e", script).Run(); err != nil { return fmt.Errorf("clipboard write image (osascript): %w", err) } return nil }