Files
clip-sync/internal/clipboard/clipboard_darwin.go
T

99 lines
2.6 KiB
Go

//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
}