Skip to content

Commit

Permalink
poc: add customized format register for darwin
Browse files Browse the repository at this point in the history
See #17
  • Loading branch information
changkun committed Dec 29, 2022
1 parent 5e37dbe commit e2374dc
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 64 deletions.
33 changes: 23 additions & 10 deletions clipboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand All @@ -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.
Expand Down Expand Up @@ -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)
}
102 changes: 73 additions & 29 deletions clipboard_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,52 @@ package clipboard
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
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"
)

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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
33 changes: 8 additions & 25 deletions clipboard_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,12 @@
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>

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;
}
Expand All @@ -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;
}
Expand Down
4 changes: 4 additions & 0 deletions clipboard_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
38 changes: 38 additions & 0 deletions cmd/customformat/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
*/
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)
}

0 comments on commit e2374dc

Please sign in to comment.