Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

all: add customized format register #43

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}