master clipboard_linux.go
 1//go:build linux
 2
 3package senpai
 4
 5import (
 6	"fmt"
 7	"io"
 8	"os"
 9	"os/exec"
10	"strings"
11)
12
13type clipboardReader struct {
14	io.ReadCloser
15	cmd *exec.Cmd
16}
17
18func (r *clipboardReader) Close() error {
19	err := r.ReadCloser.Close()
20	if waitErr := r.cmd.Wait(); err == nil {
21		err = waitErr
22	}
23	return err
24}
25
26func readClipboard() (io.ReadCloser, string, error) {
27	if os.Getenv("WAYLAND_DISPLAY") != "" || os.Getenv("XDG_SESSION_TYPE") == "wayland" {
28		return readClipboardWayland()
29	}
30	return readClipboardX11()
31}
32
33func startClipboardCmd(cmd *exec.Cmd) (io.ReadCloser, error) {
34	stdout, err := cmd.StdoutPipe()
35	if err != nil {
36		return nil, err
37	}
38	if err := cmd.Start(); err != nil {
39		return nil, err
40	}
41	return &clipboardReader{ReadCloser: stdout, cmd: cmd}, nil
42}
43
44func readClipboardWayland() (io.ReadCloser, string, error) {
45	out, err := exec.Command("wl-paste", "--list-types").Output()
46	if err != nil {
47		return nil, "", fmt.Errorf("listing clipboard types: %v", err)
48	}
49	var mimetype string
50	for _, line := range strings.Split(string(out), "\n") {
51		line = strings.TrimSpace(line)
52		if line != "" {
53			mimetype = line
54			break
55		}
56	}
57	if mimetype == "" {
58		return nil, "", fmt.Errorf("clipboard is empty")
59	}
60	rc, err := startClipboardCmd(exec.Command("wl-paste", "-t", mimetype))
61	if err != nil {
62		return nil, "", fmt.Errorf("reading clipboard: %v", err)
63	}
64	return rc, mimetype, nil
65}
66
67var x11SkipTargets = map[string]bool{
68	"TARGETS":      true,
69	"TIMESTAMP":    true,
70	"MULTIPLE":     true,
71	"SAVE_TARGETS": true,
72}
73
74func readClipboardX11() (io.ReadCloser, string, error) {
75	out, err := exec.Command("xclip", "-selection", "clipboard", "-o", "-t", "TARGETS").Output()
76	if err != nil {
77		return nil, "", fmt.Errorf("listing clipboard types: %v", err)
78	}
79	var mimetype string
80	for _, line := range strings.Split(string(out), "\n") {
81		line = strings.TrimSpace(line)
82		if line == "" || x11SkipTargets[line] {
83			continue
84		}
85		mimetype = line
86		break
87	}
88	if mimetype == "" {
89		return nil, "", fmt.Errorf("clipboard is empty")
90	}
91	rc, err := startClipboardCmd(exec.Command("xclip", "-selection", "clipboard", "-o", "-t", mimetype))
92	if err != nil {
93		return nil, "", fmt.Errorf("reading clipboard: %v", err)
94	}
95	return rc, mimetype, nil
96}