//go:build linux package clipboard import ( "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 }