//go:build linux package clipboard import ( "bytes" "fmt" "os" "os/exec" "strings" ) type linuxClipboard struct { copyCmd string // "xclip" or "wl-copy" pasteCmd string // "xclip" or "wl-paste" copyArgs []string // e.g. ["-selection", "clipboard"] for xclip } // New creates a Linux clipboard (X11 xclip or Wayland wl-clipboard). func New() (Clipboard, error) { return newLinuxClipboard() } func newLinuxClipboard() (*linuxClipboard, error) { sessionType := os.Getenv("XDG_SESSION_TYPE") if strings.EqualFold(sessionType, "wayland") { if err := checkBinary("wl-copy"); err != nil { return nil, fmt.Errorf("clip-sync: Wayland detected but wl-copy not found: %w", err) } if err := checkBinary("wl-paste"); err != nil { return nil, fmt.Errorf("clip-sync: Wayland detected but wl-paste not found: %w", err) } return &linuxClipboard{ copyCmd: "wl-copy", pasteCmd: "wl-paste", copyArgs: nil, // wl-copy reads from stdin by default }, nil } // Default: X11 via xclip if err := checkBinary("xclip"); err != nil { return nil, fmt.Errorf("clip-sync: xclip not found (install xclip for X11 or wl-clipboard for Wayland): %w", err) } return &linuxClipboard{ copyCmd: "xclip", pasteCmd: "xclip", copyArgs: []string{"-selection", "clipboard"}, }, nil } func (c *linuxClipboard) Read() (string, error) { args := []string{"-selection", "clipboard", "-o"} if c.pasteCmd == "wl-paste" { args = nil // wl-paste needs no args } cmd := exec.Command(c.pasteCmd, args...) out, err := cmd.Output() if err != nil { return "", fmt.Errorf("clipboard read (%s): %w", c.pasteCmd, err) } return strings.TrimRight(string(out), "\n\r"), nil } func (c *linuxClipboard) Write(text string) error { cmd := exec.Command(c.copyCmd, c.copyArgs...) cmd.Stdin = strings.NewReader(text) if err := cmd.Run(); err != nil { return fmt.Errorf("clipboard write (%s): %w", c.copyCmd, err) } return nil } // ReadImage returns the PNG image from the clipboard, if any. func (c *linuxClipboard) ReadImage() (string, []byte, error) { var args []string if c.pasteCmd == "wl-paste" { args = []string{"--type", "image/png"} } else { args = []string{"-selection", "clipboard", "-t", "image/png", "-o"} } cmd := exec.Command(c.pasteCmd, args...) out, err := cmd.Output() if err != nil || len(out) == 0 { return "", nil, ErrNoImage } return "image/png", out, nil } // WriteImage writes image bytes to the clipboard using the given MIME type. func (c *linuxClipboard) WriteImage(mime string, data []byte) error { var args []string if c.copyCmd == "wl-copy" { args = []string{"--type", mime} } else { args = []string{"-selection", "clipboard", "-t", mime} } cmd := exec.Command(c.copyCmd, args...) cmd.Stdin = bytes.NewReader(data) if err := cmd.Run(); err != nil { return fmt.Errorf("clipboard write image (%s): %w", c.copyCmd, err) } return nil }