From e2374dcea5e221df6dea688a80946023c5c36cd2 Mon Sep 17 00:00:00 2001 From: Changkun Ou Date: Thu, 29 Dec 2022 20:01:22 +0100 Subject: [PATCH] poc: add customized format register for darwin See #17 --- clipboard.go | 33 +++++++++---- clipboard_darwin.go | 102 ++++++++++++++++++++++++++++----------- clipboard_darwin.m | 33 +++---------- clipboard_nocgo.go | 4 ++ cmd/customformat/main.go | 38 +++++++++++++++ 5 files changed, 146 insertions(+), 64 deletions(-) create mode 100644 cmd/customformat/main.go diff --git a/clipboard.go b/clipboard.go index b00f415..684dd59 100644 --- a/clipboard.go +++ b/clipboard.go @@ -71,22 +71,24 @@ var ( ) // Format represents the format of clipboard data. -type Format int +type Format interface{} + +type internalFormat int // All sorts of supported clipboard data -const ( +var ( // FmtText indicates plain text clipboard format - FmtText Format = iota + FmtText Format = internalFormat(0) // FmtImage indicates image/png clipboard format - FmtImage + FmtImage Format = internalFormat(1) ) var ( // Due to the limitation on operating systems (such as darwin), // concurrent read can even cause panic, use a global lock to // guarantee one read at a time. - lock = sync.Mutex{} - initOnce sync.Once + lock = sync.Mutex{} + initOnce sync.Once initError error ) @@ -95,10 +97,10 @@ var ( // target system lacks required dependency, such as libx11-dev in X11 // environment. For example, // -// err := clipboard.Init() -// if err != nil { -// panic(err) -// } +// err := clipboard.Init() +// if err != nil { +// panic(err) +// } // // If Init returns an error, any subsequent Read/Write/Watch call // may result in an unrecoverable panic. @@ -152,3 +154,14 @@ func Write(t Format, buf []byte) <-chan struct{} { func Watch(ctx context.Context, t Format) <-chan []byte { return watch(ctx, t) } + +// Handler is a clipboard content handler. +type Handler interface { + Format() interface{} + // TODO: add reader and writer using generics. +} + +// Register allows caller to provide customized clipboard handler. +func Register(h Handler) error { + return register(h) +} diff --git a/clipboard_darwin.go b/clipboard_darwin.go index 5990e68..18b5ef1 100644 --- a/clipboard_darwin.go +++ b/clipboard_darwin.go @@ -15,15 +15,14 @@ package clipboard #import #import -unsigned int clipboard_read_string(void **out); -unsigned int clipboard_read_image(void **out); -int clipboard_write_string(const void *bytes, NSInteger n); -int clipboard_write_image(const void *bytes, NSInteger n); +unsigned int clipboard_read(void **out, void* t); +int clipboard_write(const void *bytes, NSInteger n, void* t); NSInteger clipboard_change_count(); */ import "C" import ( "context" + "sync" "time" "unsafe" ) @@ -31,16 +30,37 @@ import ( func initialize() error { return nil } func read(t Format) (buf []byte, err error) { - var ( - data unsafe.Pointer - n C.uint - ) - switch t { - case FmtText: - n = C.clipboard_read_string(&data) - case FmtImage: - n = C.clipboard_read_image(&data) + + var format unsafe.Pointer + switch tt := t.(type) { + case internalFormat: + switch tt { + case FmtText: + format = unsafe.Pointer(C.NSPasteboardTypeString) + case FmtImage: + format = unsafe.Pointer(C.NSPasteboardTypePNG) + } + default: + found := false + registeredFormats.Range(func(key, value interface{}) bool { + if t == key { + found = true + return false + } + return true + }) + if !found { + return nil, errUnsupported + } + actualFormat, ok := t.(unsafe.Pointer) + if !ok { + return nil, errUnsupported + } + format = actualFormat } + + var data unsafe.Pointer + n := C.clipboard_read(&data, unsafe.Pointer(&format)) if data == nil { return nil, errUnavailable } @@ -54,24 +74,40 @@ func read(t Format) (buf []byte, err error) { // write writes the given data to clipboard and // returns true if success or false if failed. func write(t Format, buf []byte) (<-chan struct{}, error) { - var ok C.int - switch t { - case FmtText: - if len(buf) == 0 { - ok = C.clipboard_write_string(unsafe.Pointer(nil), 0) - } else { - ok = C.clipboard_write_string(unsafe.Pointer(&buf[0]), - C.NSInteger(len(buf))) - } - case FmtImage: - if len(buf) == 0 { - ok = C.clipboard_write_image(unsafe.Pointer(nil), 0) - } else { - ok = C.clipboard_write_image(unsafe.Pointer(&buf[0]), - C.NSInteger(len(buf))) + var format unsafe.Pointer + switch tt := t.(type) { + case internalFormat: + switch tt { + case FmtText: + format = unsafe.Pointer(C.NSPasteboardTypeString) + case FmtImage: + format = unsafe.Pointer(C.NSPasteboardTypePNG) + default: + return nil, errUnsupported } default: - return nil, errUnsupported + found := false + registeredFormats.Range(func(key, value interface{}) bool { + if t == key { + found = true + return false + } + return true + }) + if !found { + return nil, errUnsupported + } + actualFormat, ok := t.(unsafe.Pointer) + if !ok { + return nil, errUnsupported + } + format = actualFormat + } + var ok C.int + if len(buf) == 0 { + ok = C.clipboard_write(unsafe.Pointer(nil), 0, unsafe.Pointer(&format)) + } else { + ok = C.clipboard_write(unsafe.Pointer(&buf[0]), C.NSInteger(len(buf)), unsafe.Pointer(&format)) } if ok != 0 { return nil, errUnavailable @@ -121,3 +157,11 @@ func watch(ctx context.Context, t Format) <-chan []byte { }() return recv } + +var registeredFormats sync.Map // map[any]any + +func register(h Handler) error { + t := h.Format() + registeredFormats.Store(t, struct{}{}) + return nil +} diff --git a/clipboard_darwin.m b/clipboard_darwin.m index d076c4b..ace0770 100644 --- a/clipboard_darwin.m +++ b/clipboard_darwin.m @@ -13,21 +13,12 @@ #import #import -unsigned int clipboard_read_string(void **out) { - NSPasteboard * pasteboard = [NSPasteboard generalPasteboard]; - NSData *data = [pasteboard dataForType:NSPasteboardTypeString]; - if (data == nil) { - return 0; - } - NSUInteger siz = [data length]; - *out = malloc(siz); - [data getBytes: *out length: siz]; - return siz; -} +unsigned int clipboard_read(void **out, void* t) { + NSString *cs = *((__unsafe_unretained NSString **)(t)); + NSPasteboardType tt = (NSPasteboardType)cs; -unsigned int clipboard_read_image(void **out) { NSPasteboard * pasteboard = [NSPasteboard generalPasteboard]; - NSData *data = [pasteboard dataForType:NSPasteboardTypePNG]; + NSData *data = [pasteboard dataForType:tt]; if (data == nil) { return 0; } @@ -37,21 +28,13 @@ unsigned int clipboard_read_image(void **out) { return siz; } -int clipboard_write_string(const void *bytes, NSInteger n) { - NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; - NSData *data = [NSData dataWithBytes: bytes length: n]; - [pasteboard clearContents]; - BOOL ok = [pasteboard setData: data forType:NSPasteboardTypeString]; - if (!ok) { - return -1; - } - return 0; -} -int clipboard_write_image(const void *bytes, NSInteger n) { +int clipboard_write(const void *bytes, NSInteger n, void* t) { + NSString *cs = *((__unsafe_unretained NSString **)(t)); + NSPasteboardType tt = (NSPasteboardType)cs; NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; NSData *data = [NSData dataWithBytes: bytes length: n]; [pasteboard clearContents]; - BOOL ok = [pasteboard setData: data forType:NSPasteboardTypePNG]; + BOOL ok = [pasteboard setData: data forType:tt]; if (!ok) { return -1; } diff --git a/clipboard_nocgo.go b/clipboard_nocgo.go index 8684272..4d069cd 100644 --- a/clipboard_nocgo.go +++ b/clipboard_nocgo.go @@ -24,3 +24,7 @@ func write(t Format, buf []byte) (<-chan struct{}, error) { func watch(ctx context.Context, t Format) <-chan []byte { panic("clipboard: cannot use when CGO_ENABLED=0") } + +func register(h Handler) error { + panic("clipboard: cannot use when CGO_ENABLED=0") +} diff --git a/cmd/customformat/main.go b/cmd/customformat/main.go new file mode 100644 index 0000000..86764ec --- /dev/null +++ b/cmd/customformat/main.go @@ -0,0 +1,38 @@ +package main + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework Cocoa +#import +#import +*/ +import "C" +import ( + "os" + "unsafe" + + "golang.design/x/clipboard" +) + +var f = unsafe.Pointer(C.NSPasteboardTypePDF) + +type audioHandler struct{} + +func (ah *audioHandler) Format() interface{} { return f } + +func main() { + err := clipboard.Init() + if err != nil { + panic(err) + } + clipboard.Register(&audioHandler{}) + + content, err := os.ReadFile("~/test.pdf") + if err != nil { + panic(err) + } + + clipboard.Write(f, content) + b := clipboard.Read(clipboard.FmtText) + os.WriteFile("x.txt", b, os.ModePerm) +}