package clipboard import ( "bufio" "context" "os/exec" "strings" ) type WaylandClipboard struct{} func NewWayland() (*WaylandClipboard, error) { // Check if wl-paste is available if _, err := exec.LookPath("wl-paste"); err != nil { return nil, err } if _, err := exec.LookPath("wl-copy"); err != nil { return nil, err } return &WaylandClipboard{}, nil } func (c *WaylandClipboard) Read() (string, error) { cmd := exec.Command("wl-paste", "--no-newline") output, err := cmd.Output() if err != nil { // wl-paste returns error if clipboard is empty return "", nil } return string(output), nil } func (c *WaylandClipboard) Write(content string) error { cmd := exec.Command("wl-copy") cmd.Stdin = strings.NewReader(content) return cmd.Run() } func (c *WaylandClipboard) Watch(ctx context.Context) <-chan string { out := make(chan string) go func() { defer close(out) // Use wl-paste --watch to detect clipboard changes // When clipboard changes, it runs "echo x" which outputs a single line // Then we read the full clipboard content separately cmd := exec.CommandContext(ctx, "wl-paste", "--watch", "echo", "x") stdout, err := cmd.StdoutPipe() if err != nil { return } if err := cmd.Start(); err != nil { return } scanner := bufio.NewScanner(stdout) for scanner.Scan() { select { case <-ctx.Done(): cmd.Process.Kill() return default: // Clipboard changed, read full content content, err := c.Read() if err == nil && content != "" { out <- content } } } cmd.Wait() }() return out }