Skip to content

Commit c1d0591

Browse files
authored
all: add Windows support
1 parent 0a8efdb commit c1d0591

File tree

11 files changed

+687
-66
lines changed

11 files changed

+687
-66
lines changed

.github/workflows/clipboard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
fail-fast: false
2121
matrix:
22-
os: [ubuntu-latest, macos-latest] # windows-latest
22+
os: [ubuntu-latest, macos-latest, windows-latest]
2323

2424
steps:
2525

README.md

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,39 @@ cross platform clipboard package in Go
66
import "golang.design/x/clipboard"
77
```
88

9-
## Dependency
9+
## Features
1010

11-
- Linux users: require X: `apt install -y libx11-dev` or `xorg-dev`
12-
- macOS users: require Cgo, no dependency
13-
- Windows users: unsupported yet
11+
- Cross platform supports: macOS, Unix-like (X11), and Windows
12+
- Copy/paste UTF-8 text
13+
- Copy/paste PNG encoded images
14+
- Command `gclip` as a demo application
1415

1516
## API Usage
1617

17-
Quick start:
18+
Package `clipboard` provides three major APIs for manipulating the
19+
clipboard: `Read`, `Write`, and `Watch`. The most common operations are
20+
`Read` and `Write`. To use them, you can:
1821

1922
```go
20-
// write/read text format data of the clipboard
23+
// write/read text format data of the clipboard, and
24+
// the byte buffer regarding the text are UTF8 encoded.
2125
clipboard.Write(clipboard.FmtText, []byte("text data"))
2226
clipboard.Read(clipboard.FmtText)
2327

24-
// write/read image format data of the clipboard, assume
25-
// image bytes are png encoded.
28+
// write/read image format data of the clipboard, and
29+
// the byte buffer regarding the image are PNG encoded.
2630
clipboard.Write(clipboard.FmtImage, []byte("image data"))
2731
clipboard.Read(clipboard.FmtImage)
2832
```
2933

30-
In addition, the `clipboard.Write` API returns a channel that
31-
can receive an empty struct as a signal that indicates the
32-
corresponding write call to the clipboard is outdated, meaning
33-
the clipboard has been overwritten by others and the previously
34-
written data is lost. For instance:
34+
Note that read/write regarding image format assumes that the bytes are
35+
PNG encoded since it serves the alpha blending purpose that might be
36+
used in other graphical software.
37+
38+
In addition, `clipboard.Write` returns a channel that can receive an
39+
empty struct as a signal, which indicates the corresponding write call
40+
to the clipboard is outdated, meaning the clipboard has been overwritten
41+
by others and the previously written data is lost. For instance:
3542

3643
```go
3744
changed := clipboard.Write(clipboard.FmtText, []byte("text data"))
@@ -42,7 +49,7 @@ case <-changed:
4249
}
4350
```
4451

45-
You can ignore the reutrning channel if you don't need this type of
52+
You can ignore the returning channel if you don't need this type of
4653
notification. Furthermore, when you need more than just knowing whether
4754
clipboard data is changed, use the watcher API:
4855

@@ -95,15 +102,29 @@ background using a shell `&` operator, for example:
95102
$ cat x.txt | gclip -copy &
96103
```
97104

98-
## Additional Notes
105+
## Platform Specific Details
106+
107+
This package spent efforts to provide cross platform abstraction regarding
108+
accessing system clipboards, but here are a few details you might need to know.
109+
110+
### Dependency
111+
112+
- Unix-like users: require X11 dev package. For instance, Linux users should install `libx11-dev` or `xorg-dev` or `libX11-devel` to access X window system.
113+
- macOS users: require Cgo, no dependency
114+
- Windows users: no Cgo, no dependency
115+
116+
### Screenshot
99117

100-
In general, to put image data to system clipboard, there are system level shortcuts:
118+
In general, when you need test your implementation regarding images,
119+
There are system level shortcuts to put screenshot image into your system clipboard:
101120

102-
- On macOS, using shortcut `Ctrl+Shift+Cmd+4`
103-
- On Linux/Ubuntu, using `Ctrl+Shift+PrintScreen`
121+
- On macOS, use `Ctrl+Shift+Cmd+4`
122+
- On Linux/Ubuntu, use `Ctrl+Shift+PrintScreen`
123+
- On Windows, use `Shift+Win+s`
104124

105-
The package supports read/write plain text or PNG encoded image data.
106-
The other types of data are not supported yet, i.e. undefined behavior.
125+
As described in the API documentation, the package supports read/write
126+
UTF8 encoded plain text or PNG encoded image data. Thus,
127+
the other types of data are not supported yet, i.e. undefined behavior.
107128

108129
## License
109130

clipboard.go

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,73 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7+
/*
8+
Package clipboard provides three major APIs for manipulating the
9+
clipboard: `Read`, `Write`, and `Watch`. The most common operations are
10+
`Read` and `Write`. To use them:
11+
12+
// write/read text format data of the clipboard, and
13+
// the byte buffer regarding the text are UTF8 encoded.
14+
clipboard.Write(clipboard.FmtText, []byte("text data"))
15+
clipboard.Read(clipboard.FmtText)
16+
17+
// write/read image format data of the clipboard, and
18+
// the byte buffer regarding the image are PNG encoded.
19+
clipboard.Write(clipboard.FmtImage, []byte("image data"))
20+
clipboard.Read(clipboard.FmtImage)
21+
22+
Note that read/write regarding image format assumes that the bytes are
23+
PNG encoded since it serves the alpha blending purpose that might be
24+
used in other graphical software.
25+
26+
In addition, `clipboard.Write` returns a channel that can receive an
27+
empty struct as a signal, which indicates the corresponding write call
28+
to the clipboard is outdated, meaning the clipboard has been overwritten
29+
by others and the previously written data is lost. For instance:
30+
31+
changed := clipboard.Write(clipboard.FmtText, []byte("text data"))
32+
33+
select {
34+
case <-changed:
35+
println(`"text data" is no longer available from clipboard.`)
36+
}
37+
38+
You can ignore the returning channel if you don't need this type of
39+
notification. Furthermore, when you need more than just knowing whether
40+
clipboard data is changed, use the watcher API:
41+
42+
ch := clipboard.Watch(context.TODO(), clipboard.FmtText)
43+
for data := range ch {
44+
// print out clipboard data whenever it is changed
45+
println(string(data))
46+
}
47+
*/
748
package clipboard // import "golang.design/x/clipboard"
849

950
import (
1051
"context"
52+
"errors"
53+
"fmt"
54+
"os"
1155
"sync"
1256
)
1357

14-
// Format represents the MIME type of clipboard data.
58+
var (
59+
// activate only for running tests.
60+
debug = false
61+
errUnavailable = errors.New("clipboard unavailable")
62+
errUnsupported = errors.New("unsupported format")
63+
errInvalidOperation = errors.New("invalid operation")
64+
)
65+
66+
// Format represents the format of clipboard data.
1567
type Format int
1668

1769
// All sorts of supported clipboard data
1870
const (
19-
// FmtText indicates plain text MIME format
71+
// FmtText indicates plain text clipboard format
2072
FmtText Format = iota
21-
// FmtImage indicates image/png MIME format
73+
// FmtImage indicates image/png clipboard format
2274
FmtImage
2375
)
2476

@@ -27,33 +79,44 @@ const (
2779
// guarantee one read at a time.
2880
var lock = sync.Mutex{}
2981

30-
// Read reads and returns the clipboard data.
82+
// Read returns a chunk of bytes of the clipboard data if it presents
83+
// in the desired format t presents. Otherwise, it returns nil.
3184
func Read(t Format) []byte {
3285
lock.Lock()
3386
defer lock.Unlock()
3487

35-
return read(t)
88+
buf, err := read(t)
89+
if err != nil {
90+
if debug {
91+
fmt.Fprintf(os.Stderr, "read clipboard err: %v\n", err)
92+
}
93+
return nil
94+
}
95+
return buf
3696
}
3797

38-
// Write writes a given buffer to the clipboard.
39-
// The returned channel can receive an empty struct as signal that
40-
// indicates the clipboard has been overwritten from this write.
41-
//
42-
// If the MIME type indicates an image, then the given buf assumes
98+
// Write writes a given buffer to the clipboard in a specified format.
99+
// Write returned a receive-only channel can receive an empty struct
100+
// as a signal, which indicates the clipboard has been overwritten from
101+
// this write.
102+
// If format t indicates an image, then the given buf assumes
43103
// the image data is PNG encoded.
44104
func Write(t Format, buf []byte) <-chan struct{} {
45105
lock.Lock()
46106
defer lock.Unlock()
47107

48-
ok, changed := write(t, buf)
49-
if !ok {
108+
changed, err := write(t, buf)
109+
if err != nil {
110+
if debug {
111+
fmt.Fprintf(os.Stderr, "write to clipboard err: %v\n", err)
112+
}
50113
return nil
51114
}
52115
return changed
53116
}
54117

55118
// Watch returns a receive-only channel that received the clipboard data
56-
// if any changes of clipboard data in the desired format happends.
119+
// whenever any change of clipboard data in the desired format happens.
57120
//
58121
// The returned channel will be closed if the given context is canceled.
59122
func Watch(ctx context.Context, t Format) <-chan []byte {

clipboard_darwin.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
"unsafe"
2929
)
3030

31-
func read(t Format) (buf []byte) {
31+
func read(t Format) (buf []byte, err error) {
3232
var (
3333
data unsafe.Pointer
3434
n C.uint
@@ -40,18 +40,18 @@ func read(t Format) (buf []byte) {
4040
n = C.clipboard_read_image(&data)
4141
}
4242
if data == nil {
43-
return nil
43+
return nil, errUnavailable
4444
}
4545
defer C.free(unsafe.Pointer(data))
4646
if n == 0 {
47-
return nil
47+
return nil, nil
4848
}
49-
return C.GoBytes(data, C.int(n))
49+
return C.GoBytes(data, C.int(n)), nil
5050
}
5151

5252
// write writes the given data to clipboard and
5353
// returns true if success or false if failed.
54-
func write(t Format, buf []byte) (bool, <-chan struct{}) {
54+
func write(t Format, buf []byte) (<-chan struct{}, error) {
5555
var ok C.int
5656
switch t {
5757
case FmtText:
@@ -70,7 +70,7 @@ func write(t Format, buf []byte) (bool, <-chan struct{}) {
7070
}
7171
}
7272
if ok != 0 {
73-
return false, nil
73+
return nil, errInvalidOperation
7474
}
7575

7676
// use unbuffered data to prevent goroutine leak
@@ -88,15 +88,15 @@ func write(t Format, buf []byte) (bool, <-chan struct{}) {
8888
}
8989
}
9090
}()
91-
return true, changed
91+
return changed, nil
9292
}
9393

9494
func watch(ctx context.Context, t Format) <-chan []byte {
9595
recv := make(chan []byte, 1)
96+
// not sure if we are too slow or the user too fast :)
97+
ti := time.NewTicker(time.Second)
98+
lastCount := C.long(C.clipboard_change_count())
9699
go func() {
97-
// not sure if we are too slow or the user too fast :)
98-
ti := time.NewTicker(time.Second)
99-
lastCount := C.long(C.clipboard_change_count())
100100
for {
101101
select {
102102
case <-ctx.Done():

clipboard_linux.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target,
141141
return size * sizeof(char);
142142
}
143143

144-
// clipboard_read reads the clipboard selection in given mime type typ.
144+
// clipboard_read reads the clipboard selection in given format typ.
145145
// the readed bytes is written into buf and returns the size of the buffer.
146146
//
147147
// The caller of this function should responsible for the free of the buf.

0 commit comments

Comments
 (0)