feat(clipboard): add wayland support (#657)

Signed-off-by: xqrs <3bd3bdr@gmail.com>
Co-authored-by: Ayyan <ayn2op@gmail.com>
This commit is contained in:
xqrs 2025-12-11 00:02:38 +03:00 • committed by GitHub
parent b2a0e7f012
commit 9a83762580
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 96 additions and 11 deletions

View file

@ -44,10 +44,7 @@ go build .
### Wayland clipboard support
`x11-dev` is required for X11 clipboard compatibility:
- Ubuntu: `apt install xwayland`
- Arch Linux: `pacman -S xorg-xwayland`
`wl-clipboard` is required for clipboard support.
## Usage

View file

@ -3,11 +3,11 @@ package cmd
import (
"log/slog"
"github.com/ayn2op/discordo/internal/clipboard"
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/login"
"github.com/ayn2op/tview"
"github.com/gdamore/tcell/v3"
"golang.design/x/clipboard"
)
type application struct {

View file

@ -6,6 +6,7 @@ import (
"log/slog"
"slices"
"github.com/ayn2op/discordo/internal/clipboard"
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/ui"
"github.com/ayn2op/tview"
@ -13,7 +14,6 @@ import (
"github.com/diamondburned/arikawa/v3/gateway"
"github.com/diamondburned/ningen/v3"
"github.com/gdamore/tcell/v3"
"golang.design/x/clipboard"
)
type guildsTree struct {

View file

@ -15,6 +15,7 @@ import (
"unicode"
"github.com/ayn2op/discordo/internal/cache"
"github.com/ayn2op/discordo/internal/clipboard"
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/consts"
"github.com/ayn2op/discordo/internal/ui"
@ -29,7 +30,6 @@ import (
"github.com/ncruces/zenity"
"github.com/sahilm/fuzzy"
"github.com/yuin/goldmark/ast"
"golang.design/x/clipboard"
)
const tmpFilePattern = consts.Name + "_*.md"

View file

@ -14,6 +14,7 @@ import (
"sync"
"time"
"github.com/ayn2op/discordo/internal/clipboard"
"github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/consts"
"github.com/ayn2op/discordo/internal/markdown"
@ -30,7 +31,6 @@ import (
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"golang.design/x/clipboard"
)
type messagesList struct {

View file

@ -147,7 +147,7 @@ func onReady(r *gateway.ReadyEvent) {
func onMessageCreate(message *gateway.MessageCreateEvent) {
if app.chatView.selectedChannel != nil &&
app.chatView.selectedChannel.ID == message.ChannelID {
app.chatView.selectedChannel.ID == message.ChannelID {
app.chatView.messagesList.drawMessage(app.chatView.messagesList, message.Message)
app.Draw()
}
@ -159,14 +159,14 @@ func onMessageCreate(message *gateway.MessageCreateEvent) {
func onMessageUpdate(message *gateway.MessageUpdateEvent) {
if app.chatView.selectedChannel != nil &&
app.chatView.selectedChannel.ID == message.ChannelID {
app.chatView.selectedChannel.ID == message.ChannelID {
onMessageDelete(&gateway.MessageDeleteEvent{ID: message.ID, ChannelID: message.ChannelID, GuildID: message.GuildID})
}
}
func onMessageDelete(message *gateway.MessageDeleteEvent) {
if app.chatView.selectedChannel != nil &&
app.chatView.selectedChannel.ID == message.ChannelID {
app.chatView.selectedChannel.ID == message.ChannelID {
messages, err := discordState.Cabinet.Messages(message.ChannelID)
if err != nil {
slog.Error("failed to get messages from state", "err", err, "channel_id", message.ChannelID)

View file

@ -0,0 +1,10 @@
package clipboard
import designClipb "golang.design/x/clipboard"
type Format = designClipb.Format
const (
FmtText Format = designClipb.FmtText
FmtImage = designClipb.FmtImage
)

View file

@ -0,0 +1,11 @@
//go:build !linux
package clipboard
import designClipb "golang.design/x/clipboard"
var (
Init = designClipb.Init
Read = designClipb.Read
Write = designClipb.Write
)

View file

@ -0,0 +1,67 @@
//go:build linux
package clipboard
import (
"bytes"
designClipb "golang.design/x/clipboard"
"log/slog"
"os"
"os/exec"
)
var wayland bool
func Init() error {
if _, ok := os.LookupEnv("WAYLAND_DISPLAY"); !ok {
return designClipb.Init()
}
if _, err := exec.LookPath("wl-copy"); err != nil {
return err
}
if _, err := exec.LookPath("wl-paste"); err != nil {
return err
}
wayland = true
return nil
}
func Read(t Format) []byte {
if !wayland {
return designClipb.Read(designClipb.Format(t))
}
// -n: Don't print a newline at the end
// -t type: MIME type specifier
cmd := exec.Command("wl-paste", "-nt", formatToType(t))
outBuffer := bytes.Buffer{}
cmd.Stdout = &outBuffer
if err := cmd.Run(); err != nil {
slog.Error("failed to read clipboard", "err", err)
return nil
}
return outBuffer.Bytes()
}
func Write(t Format, buf []byte) <-chan struct{} {
if !wayland {
return designClipb.Write(designClipb.Format(t), buf)
}
// -t type: MIME type specifier
cmd := exec.Command("wl-copy", "-t", formatToType(t))
cmd.Stdin = bytes.NewReader(buf)
if err := cmd.Run(); err != nil {
slog.Error("failed to write to clipboard", "err", err)
}
return nil
}
func formatToType(t Format) string {
switch t {
case FmtImage:
return "image"
case FmtText:
fallthrough
default:
return "text/plain;charset=utf-8"
}
}