Files

140 lines
2.7 KiB
Go

package clipboard
import (
"context"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/AvengeMedia/dankgo/wayland/ext_data_control"
)
// WatchAll subscribes to clipboard selection changes like wlclipboard.Watch,
// additionally reporting every mime type each selection offers.
func WatchAll(ctx context.Context, callback func(data []byte, mimeType string, allMimeTypes []string)) error {
s, err := connectSession()
if err != nil {
return err
}
defer s.Close()
dataControlMgr, err := s.requireDataControl()
if err != nil {
return err
}
device, err := dataControlMgr.GetDataDevice(s.seat)
if err != nil {
return fmt.Errorf("get data device: %w", err)
}
defer device.Destroy()
offerMimeTypes := make(map[*ext_data_control.ExtDataControlOfferV1][]string)
device.SetDataOfferHandler(func(e ext_data_control.ExtDataControlDeviceV1DataOfferEvent) {
if e.Id == nil {
return
}
offerMimeTypes[e.Id] = nil
e.Id.SetOfferHandler(func(me ext_data_control.ExtDataControlOfferV1OfferEvent) {
offerMimeTypes[e.Id] = append(offerMimeTypes[e.Id], me.MimeType)
})
})
device.SetSelectionHandler(func(e ext_data_control.ExtDataControlDeviceV1SelectionEvent) {
if e.Id == nil {
return
}
mimes := offerMimeTypes[e.Id]
selectedMime := selectPreferredMimeType(mimes)
if selectedMime == "" {
return
}
mimesCopy := make([]string, len(mimes))
copy(mimesCopy, mimes)
r, w, err := os.Pipe()
if err != nil {
return
}
if err := e.Id.Receive(selectedMime, int(w.Fd())); err != nil {
w.Close()
r.Close()
return
}
w.Close()
go func() {
defer r.Close()
data, err := io.ReadAll(r)
if err != nil || len(data) == 0 {
return
}
callback(data, selectedMime, mimesCopy)
}()
})
s.display.Roundtrip()
s.display.Roundtrip()
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
if err := s.ctx.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
return fmt.Errorf("set read deadline: %w", err)
}
if err := s.ctx.Dispatch(); err != nil {
if isTimeoutError(err) {
continue
}
return fmt.Errorf("dispatch: %w", err)
}
}
}
}
func selectPreferredMimeType(mimes []string) string {
preferred := []string{
"text/plain;charset=utf-8",
"text/plain",
"UTF8_STRING",
"STRING",
"TEXT",
"image/png",
"image/jpeg",
}
for _, pref := range preferred {
for _, mime := range mimes {
if mime == pref {
return mime
}
}
}
if len(mimes) > 0 {
return mimes[0]
}
return ""
}
func isTimeoutError(err error) bool {
if err == nil {
return false
}
if errors.Is(err, os.ErrDeadlineExceeded) {
return true
}
if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
return true
}
return false
}