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}