Skip to content

Commit 076d92f

Browse files
authored
all: add Init() method (#18)
1 parent 10ba29a commit 076d92f

13 files changed

+136
-30
lines changed

README.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,20 @@ import "golang.design/x/clipboard"
1616

1717
## API Usage
1818

19-
Package `clipboard` provides three major APIs for manipulating the
20-
clipboard: `Read`, `Write`, and `Watch`. The most common operations are
21-
`Read` and `Write`. To use them, you can:
19+
Package clipboard provides cross platform clipboard access and supports
20+
macOS/Linux/Windows/Android/iOS platform. Before interacting with the
21+
clipboard, one must call Init to assert if it is possible to use this
22+
package:
23+
24+
```go
25+
// Init returns an error if the package is not ready for use.
26+
err := clipboard.Init()
27+
if err != nil {
28+
panic(err)
29+
}
30+
```
31+
32+
The most common operations are `Read` and `Write`. To use them:
2233

2334
```go
2435
// write/read text format data of the clipboard, and

clipboard.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66

77
/*
88
Package clipboard provides cross platform clipboard access and supports
9-
macOS/Linux/Windows/Android/iOS platform. There are three major APIs
10-
to interact with the clipboard: `Read`, `Write`, and `Watch`.
9+
macOS/Linux/Windows/Android/iOS platform. Before interacting with the
10+
clipboard, one must call Init to assert if it is possible to use this
11+
package:
12+
13+
err := clipboard.Init()
14+
if err != nil {
15+
panic(err)
16+
}
1117
1218
The most common operations are `Read` and `Write`. To use them:
1319
@@ -59,10 +65,9 @@ import (
5965

6066
var (
6167
// activate only for running tests.
62-
debug = false
63-
errUnavailable = errors.New("clipboard unavailable")
64-
errUnsupported = errors.New("unsupported format")
65-
errInvalidOperation = errors.New("invalid operation")
68+
debug = false
69+
errUnavailable = errors.New("clipboard unavailable")
70+
errUnsupported = errors.New("unsupported format")
6671
)
6772

6873
// Format represents the format of clipboard data.
@@ -81,6 +86,22 @@ const (
8186
// guarantee one read at a time.
8287
var lock = sync.Mutex{}
8388

89+
// Init initializes the clipboard package. It returns an error
90+
// if the clipboard is not available to use. This may happen if the
91+
// target system lacks required dependency, such as libx11-dev in X11
92+
// environment. For example,
93+
//
94+
// err := clipboard.Init()
95+
// if err != nil {
96+
// panic(err)
97+
// }
98+
//
99+
// If Init returns an error, any subsequent Read/Write/Watch call
100+
// may result in an unrecoverable panic.
101+
func Init() error {
102+
return initialize()
103+
}
104+
84105
// Read returns a chunk of bytes of the clipboard data if it presents
85106
// in the desired format t presents. Otherwise, it returns nil.
86107
func Read(t Format) []byte {

clipboard_android.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"golang.org/x/mobile/app"
2929
)
3030

31+
func initialize() error { return nil }
32+
3133
func read(t Format) (buf []byte, err error) {
3234
switch t {
3335
case FmtText:
@@ -44,9 +46,9 @@ func read(t Format) (buf []byte, err error) {
4446
})
4547
return []byte(s), nil
4648
case FmtImage:
47-
return nil, errors.New("unsupported")
49+
return nil, errUnsupported
4850
default:
49-
return nil, errors.New("unsupported")
51+
return nil, errUnsupported
5052
}
5153
}
5254

@@ -66,9 +68,9 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
6668
})
6769
return done, nil
6870
case FmtImage:
69-
return nil, errors.New("unsupported")
71+
return nil, errUnsupported
7072
default:
71-
return nil, errors.New("unsupported")
73+
return nil, errUnsupported
7274
}
7375
}
7476

clipboard_darwin.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import (
2828
"unsafe"
2929
)
3030

31+
func initialize() error { return nil }
32+
3133
func read(t Format) (buf []byte, err error) {
3234
var (
3335
data unsafe.Pointer
@@ -68,9 +70,11 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
6870
ok = C.clipboard_write_image(unsafe.Pointer(&buf[0]),
6971
C.NSInteger(len(buf)))
7072
}
73+
default:
74+
return nil, errUnsupported
7175
}
7276
if ok != 0 {
73-
return nil, errInvalidOperation
77+
return nil, errUnavailable
7478
}
7579

7680
// use unbuffered data to prevent goroutine leak

clipboard_ios.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,16 @@ import (
2626
"unsafe"
2727
)
2828

29+
func initialize() error { return nil }
30+
2931
func read(t Format) (buf []byte, err error) {
3032
switch t {
3133
case FmtText:
3234
return []byte(C.GoString(C.clipboard_read_string())), nil
3335
case FmtImage:
34-
return nil, errors.New("unimplemented")
36+
return nil, errUnsupported
3537
default:
36-
return nil, errors.New("unimplemented")
38+
return nil, errUnsupported
3739
}
3840
}
3941

@@ -48,9 +50,9 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
4850
C.clipboard_write_string(cs)
4951
return done, nil
5052
case FmtImage:
51-
return nil, errors.New("unimplemented")
53+
return nil, errUnsupported
5254
default:
53-
return nil, errors.New("unimplemented")
55+
return nil, errUnsupported
5456
}
5557
}
5658

clipboard_linux.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import (
4141
"golang.design/x/clipboard/internal/cgo"
4242
)
4343

44-
const errmsg = `Failed to initialize the X11 display, and the clipboard package
44+
var helpmsg = `%w: Failed to initialize the X11 display, and the clipboard package
4545
will not work properly. Install the following dependency may help:
4646
4747
apt install -y libx11-dev
@@ -59,11 +59,12 @@ and initialize a virtual frame buffer:
5959
Then this package should be ready to use.
6060
`
6161

62-
func init() {
62+
func initialize() error {
6363
ok := C.clipboard_test()
64-
if ok < 0 {
65-
panic(errmsg)
64+
if ok != 0 {
65+
return fmt.Errorf(helpmsg, errUnavailable)
6666
}
67+
return nil
6768
}
6869

6970
func read(t Format) (buf []byte, err error) {
@@ -97,7 +98,6 @@ func readc(t string) ([]byte, error) {
9798
// write writes the given data to clipboard and
9899
// returns true if success or false if failed.
99100
func write(t Format, buf []byte) (<-chan struct{}, error) {
100-
101101
var s string
102102
switch t {
103103
case FmtText:
@@ -132,7 +132,7 @@ func write(t Format, buf []byte) (<-chan struct{}, error) {
132132

133133
status := <-start
134134
if status < 0 {
135-
return nil, errInvalidOperation
135+
return nil, errUnavailable
136136
}
137137
// wait until enter event loop
138138
return done, nil

clipboard_nocgo.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ package clipboard
55

66
import "context"
77

8+
func initialize() error {
9+
panic("clipboard: cannot use when CGO_ENABLED=0")
10+
}
11+
812
func read(t Format) (buf []byte, err error) {
913
panic("clipboard: cannot use when CGO_ENABLED=0")
1014
}

clipboard_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package clipboard_test
99
import (
1010
"bytes"
1111
"context"
12+
"errors"
1213
"image/png"
1314
"os"
1415
"reflect"
@@ -23,6 +24,38 @@ func init() {
2324
clipboard.Debug = true
2425
}
2526

27+
func TestClipboardInit(t *testing.T) {
28+
t.Run("no-cgo", func(t *testing.T) {
29+
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
30+
t.Skip("CGO_ENABLED is set to 1")
31+
}
32+
if runtime.GOOS == "windows" {
33+
t.Skip("Windows does not need to check for cgo")
34+
}
35+
36+
defer func() {
37+
if r := recover(); r != nil {
38+
return
39+
}
40+
t.Fatalf("expect to fail when CGO_ENABLED=0")
41+
}()
42+
43+
clipboard.Init()
44+
})
45+
t.Run("with-cgo", func(t *testing.T) {
46+
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
47+
t.Skip("CGO_ENABLED is set to 0")
48+
}
49+
if runtime.GOOS != "linux" {
50+
t.Skip("Only Linux may return error at the moment.")
51+
}
52+
53+
if err := clipboard.Init(); err != nil && !errors.Is(err, clipboard.ErrUnavailable) {
54+
t.Fatalf("expect ErrUnavailable, but got: %v", err)
55+
}
56+
})
57+
}
58+
2659
func TestClipboard(t *testing.T) {
2760
if runtime.GOOS != "windows" {
2861
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
@@ -255,7 +288,7 @@ func BenchmarkClipboard(b *testing.B) {
255288
}
256289

257290
func TestClipboardNoCgo(t *testing.T) {
258-
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "1" {
291+
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
259292
t.Skip("CGO_ENABLED is set to 1")
260293
}
261294
if runtime.GOOS == "windows" {

clipboard_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
"unsafe"
2828
)
2929

30+
func initialize() error { return nil }
31+
3032
// readText reads the clipboard and returns the text data if presents.
3133
// The caller is responsible for opening/closing the clipboard before
3234
// calling this function.

cmd/gclip-gui/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@ func (g *GclipApp) OnDraw() {
207207
g.l.Draw(g.siz)
208208
}
209209

210+
func init() {
211+
err := clipboard.Init()
212+
if err != nil {
213+
panic(err)
214+
}
215+
}
216+
210217
func main() {
211218
app.Main(func(a app.App) {
212219
gclip := GclipApp{app: a}

cmd/gclip/main.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package main // go install golang.design/x/clipboard/cmd/gclip@latest
88

99
import (
10-
"errors"
1110
"flag"
1211
"fmt"
1312
"io"
@@ -44,9 +43,12 @@ var (
4443
file = flag.String("f", "", "source or destination to a given file path")
4544
)
4645

47-
var (
48-
errUnsupported = errors.New("unsupported data format")
49-
)
46+
func init() {
47+
err := clipboard.Init()
48+
if err != nil {
49+
panic(err)
50+
}
51+
}
5052

5153
func main() {
5254
flag.Usage = usage

example_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,32 @@ import (
1818
)
1919

2020
func ExampleWrite() {
21+
err := clipboard.Init()
22+
if err != nil {
23+
panic(err)
24+
}
25+
2126
clipboard.Write(clipboard.FmtText, []byte("Hello, 世界"))
2227
// Output:
2328
}
2429

2530
func ExampleRead() {
31+
err := clipboard.Init()
32+
if err != nil {
33+
panic(err)
34+
}
35+
2636
fmt.Println(string(clipboard.Read(clipboard.FmtText)))
2737
// Output:
2838
// Hello, 世界
2939
}
3040

3141
func ExampleWatch() {
42+
err := clipboard.Init()
43+
if err != nil {
44+
panic(err)
45+
}
46+
3247
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
3348
defer cancel()
3449

export_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,7 @@
77
package clipboard
88

99
// for debugging errors
10-
var Debug = debug
10+
var (
11+
Debug = debug
12+
ErrUnavailable = errUnavailable
13+
)

0 commit comments

Comments
 (0)