mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-30 23:17:51 -04:00
140 lines
2.7 KiB
Go
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
|
|
}
|