Files
mesh-catalog/modules/xclip/cmd/xclip-tools/xclip.go
T
jochen b1b7e58e4b xclip: the package, and the operator's clipboard from the mesh (hq to-be 42 phase 2.7)
A package and nothing else, the tool the desktop's scripts depend on.
Four Go tools: copy, paste (text, base64 for other types, empty when
nothing), targets and session.

The runtime is given no session words, but runs as the account in the
machine's own namespace, so the session is found rather than configured:
the process's DISPLAY, else the account's processes' DISPLAY and XAUTHORITY
from /proc (the window manager's first), else the only X socket with
~/.Xauthority. Measured with both variables unset: :1 found through i3, the
server answered. With no session every tool says so and runs nothing.
2026-10-04 13:02:23 +02:00

161 lines
4.6 KiB
Go

package main
import (
"encoding/base64"
"fmt"
"regexp"
"strings"
"unicode/utf8"
)
// Selections are the X selections xclip reaches.
var Selections = []string{"clipboard", "primary", "secondary"}
// MostCopy bounds what a caller may put on the clipboard.
const MostCopy = 1 << 20
var targetName = regexp.MustCompile(`^[A-Za-z0-9_][A-Za-z0-9_.+/;=-]{0,127}$`)
func checkTarget(t string) error {
if t != "" && !targetName.MatchString(t) {
return fmt.Errorf("%q is not a type a selection can be offered as", t)
}
return nil
}
// xclip runs xclip in the session, naming an unreachable display as such.
func xclip(s Session, c Cmd) (Result, error) {
c.Name, c.Env = "xclip", s.Env()
r := run(c)
said := r.Stderr + r.Stdout
switch {
case r.Error != "":
return r, failure(c, r)
case strings.Contains(said, "Can't open display"):
return r, fmt.Errorf("the X session at %s (found by %s) refused the connection: the display is gone, or the cookie in %s is not the server's", s.Display, s.FoundBy, orNone(s.XAuthority))
case r.Status != 0:
return r, failure(c, r)
}
return r, nil
}
func orNone(s string) string {
if s == "" {
return "(no authority file)"
}
return s
}
// CopyAnswer is what xclip_copy answers.
type CopyAnswer struct {
Selection string `json:"selection"`
Type string `json:"type"`
Bytes int `json:"bytes"`
Session Session `json:"session"`
Note string `json:"note"`
}
// Copy puts text on a selection. xclip forks a process that holds the selection until another
// program takes it, so the command runs detached.
func Copy(text, selection, typ string) (CopyAnswer, error) {
if len(text) > MostCopy {
return CopyAnswer{}, fmt.Errorf("text is %d bytes; at most %d are copied", len(text), MostCopy)
}
if err := checkTarget(typ); err != nil {
return CopyAnswer{}, err
}
s, err := findSession()
if err != nil {
return CopyAnswer{}, err
}
args := []string{"-selection", selection, "-in"}
if typ != "" {
args = append(args, "-t", typ)
}
if _, err := xclip(s, Cmd{Args: args, Stdin: text, Detached: true}); err != nil {
return CopyAnswer{}, err
}
if typ == "" {
typ = "UTF8_STRING"
}
return CopyAnswer{Selection: selection, Type: typ, Bytes: len(text), Session: s,
Note: "xclip holds the selection until another program takes it; a clipboard manager may copy it into its history"}, nil
}
// PasteAnswer is what xclip_paste answers.
type PasteAnswer struct {
Selection string `json:"selection"`
Type string `json:"type"`
Empty bool `json:"empty"`
Text string `json:"text,omitempty"`
Base64 string `json:"base64,omitempty"`
Bytes int `json:"bytes"`
Truncated bool `json:"truncated,omitempty"`
}
// Paste reads a selection.
func Paste(selection, typ string) (PasteAnswer, error) {
if err := checkTarget(typ); err != nil {
return PasteAnswer{}, err
}
s, err := findSession()
if err != nil {
return PasteAnswer{}, err
}
args := []string{"-selection", selection, "-out"}
if typ != "" {
args = append(args, "-t", typ)
} else {
typ = "UTF8_STRING"
}
out := PasteAnswer{Selection: selection, Type: typ}
r, err := xclip(s, Cmd{Args: args})
if err != nil {
// "Error: target … not available": the selection is empty, or not offered as that type.
if strings.Contains(r.Stderr, "not available") {
out.Empty = true
return out, nil
}
return PasteAnswer{}, err
}
out.Bytes, out.Truncated = len(r.Stdout), r.Truncated
if utf8.ValidString(r.Stdout) {
out.Text = r.Stdout
} else {
out.Base64 = base64.StdEncoding.EncodeToString([]byte(r.Stdout))
}
out.Empty = out.Bytes == 0
return out, nil
}
// Targets answers the types a selection is offered as.
func Targets(selection string) (map[string]any, error) {
s, err := findSession()
if err != nil {
return nil, err
}
r, err := xclip(s, Cmd{Args: []string{"-selection", selection, "-out", "-t", "TARGETS"}})
if err != nil {
if strings.Contains(r.Stderr, "not available") {
return map[string]any{"selection": selection, "targets": []string{}, "empty": true}, nil
}
return nil, err
}
return map[string]any{"selection": selection, "targets": lines(r.Stdout), "empty": strings.TrimSpace(r.Stdout) == ""}, nil
}
// SessionCheck answers the session and whether its X server answers.
func SessionCheck() (map[string]any, error) {
s, err := findSession()
if err != nil {
return map[string]any{"found": false, "why": err.Error()}, nil
}
out := map[string]any{"found": true, "session": s}
if _, err := Targets("clipboard"); err != nil {
out["answers"], out["why"] = false, err.Error()
} else {
out["answers"] = true
}
return out, nil
}