From 6d4c159bf50cc71974de6c40e13178b2af07c3f4 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Fri, 24 Jul 2026 18:34:53 +0300 Subject: [PATCH] fix(clipboard): force text/plain for wayland copies Signed-off-by: cyphercodes --- internal/clipboard/clipboard.go | 60 +++++++++++++++++++++++++++- internal/clipboard/clipboard_test.go | 52 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 internal/clipboard/clipboard_test.go diff --git a/internal/clipboard/clipboard.go b/internal/clipboard/clipboard.go index 3a57caf431..e7d4d5c546 100644 --- a/internal/clipboard/clipboard.go +++ b/internal/clipboard/clipboard.go @@ -1,7 +1,11 @@ package clipboard import ( + "bytes" "errors" + "fmt" + "os" + "os/exec" "github.com/zyedidia/clipper" ) @@ -42,10 +46,11 @@ func Initialize(m Method) error { var err error switch m { case External: - clips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+1) + clips := make([]clipper.Clipboard, 0, len(clipper.Clipboards)+2) clips = append(clips, &clipper.Custom{ Name: "micro-clip", }) + clips = append(clips, &waylandTextClipboard{}) clips = append(clips, clipper.Clipboards...) clipboard, err = clipper.GetClipboard(clips...) } @@ -161,3 +166,56 @@ func write(text string, r Register, m Method) error { } return nil } + +type waylandTextClipboard struct{} + +func (wl *waylandTextClipboard) Init() error { + if os.Getenv("WAYLAND_DISPLAY") == "" { + return fmt.Errorf("Wayland display not found") + } + if _, err := exec.LookPath("wl-paste"); err != nil { + return err + } + _, err := exec.LookPath("wl-copy") + return err +} + +func (wl *waylandTextClipboard) ReadAll(reg string) ([]byte, error) { + args, err := waylandPasteArgs(reg) + if err != nil { + return nil, err + } + return exec.Command("wl-paste", args...).Output() +} + +func (wl *waylandTextClipboard) WriteAll(reg string, p []byte) error { + args, err := waylandCopyArgs(reg) + if err != nil { + return err + } + cmd := exec.Command("wl-copy", args...) + cmd.Stdin = bytes.NewReader(p) + return cmd.Run() +} + +func waylandPasteArgs(reg string) ([]string, error) { + switch reg { + case clipper.RegClipboard: + return []string{"--no-newline"}, nil + case clipper.RegPrimary: + return []string{"--no-newline", "--primary"}, nil + default: + return nil, &clipper.ErrInvalidReg{Reg: reg} + } +} + +func waylandCopyArgs(reg string) ([]string, error) { + switch reg { + case clipper.RegClipboard: + return []string{"--type", "text/plain"}, nil + case clipper.RegPrimary: + return []string{"--type", "text/plain", "--primary"}, nil + default: + return nil, &clipper.ErrInvalidReg{Reg: reg} + } +} diff --git a/internal/clipboard/clipboard_test.go b/internal/clipboard/clipboard_test.go new file mode 100644 index 0000000000..bcf8871a96 --- /dev/null +++ b/internal/clipboard/clipboard_test.go @@ -0,0 +1,52 @@ +package clipboard + +import ( + "errors" + "testing" + + "github.com/zyedidia/clipper" +) + +func TestWaylandCopyArgsForceTextPlain(t *testing.T) { + tests := []struct { + name string + reg string + want []string + }{ + { + name: "clipboard", + reg: clipper.RegClipboard, + want: []string{"--type", "text/plain"}, + }, + { + name: "primary", + reg: clipper.RegPrimary, + want: []string{"--type", "text/plain", "--primary"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := waylandCopyArgs(tt.reg) + if err != nil { + t.Fatal(err) + } + if len(got) != len(tt.want) { + t.Fatalf("args = %#v, want %#v", got, tt.want) + } + for i := range got { + if got[i] != tt.want[i] { + t.Fatalf("args = %#v, want %#v", got, tt.want) + } + } + }) + } +} + +func TestWaylandCopyArgsRejectInvalidRegister(t *testing.T) { + _, err := waylandCopyArgs("invalid") + var invalid *clipper.ErrInvalidReg + if !errors.As(err, &invalid) { + t.Fatalf("err = %v, want ErrInvalidReg", err) + } +}