Skip to content

Commit 8f90954

Browse files
committed
all: return signal to indicate last write has changed
1 parent 4335b15 commit 8f90954

File tree

6 files changed

+63
-13
lines changed

6 files changed

+63
-13
lines changed

clipboard.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,19 @@ func Read(t MIMEType) []byte {
3434
return read(t)
3535
}
3636

37-
// Write writes the given buffer to the clipboard.
37+
// Write writes a given buffer to the clipboard.
38+
// The returned channel can receive an empty struct as signal that
39+
// indicates the clipboard has been overwritten from this write.
3840
//
3941
// If the MIME type indicates an image, then the given buf assumes
4042
// the image data is PNG encoded.
41-
func Write(t MIMEType, buf []byte) {
43+
func Write(t MIMEType, buf []byte) <-chan struct{} {
4244
lock.Lock()
4345
defer lock.Unlock()
4446

45-
write(t, buf)
47+
ok, changed := write(t, buf)
48+
if !ok {
49+
return nil
50+
}
51+
return changed
4652
}

clipboard_darwin.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ unsigned int clipboard_read_string(void **out);
1919
unsigned int clipboard_read_image(void **out);
2020
int clipboard_write_string(const void *bytes, NSInteger n);
2121
int clipboard_write_image(const void *bytes, NSInteger n);
22+
NSInteger clipboard_change_count();
2223
*/
2324
import "C"
24-
import "unsafe"
25+
import (
26+
"time"
27+
"unsafe"
28+
)
2529

2630
func read(t MIMEType) (buf []byte) {
2731
var (
@@ -46,9 +50,8 @@ func read(t MIMEType) (buf []byte) {
4650

4751
// write writes the given data to clipboard and
4852
// returns true if success or false if failed.
49-
func write(t MIMEType, buf []byte) bool {
53+
func write(t MIMEType, buf []byte) (bool, <-chan struct{}) {
5054
var ok C.int
51-
5255
switch t {
5356
case MIMEText:
5457
ok = C.clipboard_write_string(unsafe.Pointer(&buf[0]),
@@ -58,7 +61,22 @@ func write(t MIMEType, buf []byte) bool {
5861
C.NSInteger(len(buf)))
5962
}
6063
if ok != 0 {
61-
return false
64+
return false, nil
6265
}
63-
return true
66+
67+
// use unbuffered data to prevent goroutine leak
68+
changed := make(chan struct{}, 1)
69+
cnt := C.long(C.clipboard_change_count())
70+
go func() {
71+
for {
72+
time.Sleep(time.Second)
73+
cur := C.long(C.clipboard_change_count())
74+
if cnt != cur {
75+
changed <- struct{}{}
76+
close(changed)
77+
return
78+
}
79+
}
80+
}()
81+
return true, changed
6482
}

clipboard_darwin.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ int clipboard_write_image(const void *bytes, NSInteger n) {
5353
return -1;
5454
}
5555
return 0;
56+
}
57+
58+
NSInteger clipboard_change_count() {
59+
return [[NSPasteboard generalPasteboard] changeCount];
5660
}

clipboard_linux.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func readc(t string) []byte {
7474

7575
// write writes the given data to clipboard and
7676
// returns true if success or false if failed.
77-
func write(t MIMEType, buf []byte) bool {
77+
func write(t MIMEType, buf []byte) (bool, <-chan struct{}) {
7878
var s string
7979
switch t {
8080
case MIMEText:
@@ -84,6 +84,7 @@ func write(t MIMEType, buf []byte) bool {
8484
}
8585

8686
var start C.int
87+
done := make(chan struct{}, 1)
8788

8889
go func() { // surve as a daemon until the ownership is terminated.
8990
runtime.LockOSThread()
@@ -94,6 +95,8 @@ func write(t MIMEType, buf []byte) bool {
9495
if ok != C.int(0) {
9596
fmt.Fprintf(os.Stderr, "write failed with status: %d\n", int(ok))
9697
}
98+
done <- struct{}{}
99+
close(done)
97100
}()
98101

99102
// FIXME: this should race with the code on the C side, start
@@ -102,8 +105,8 @@ func write(t MIMEType, buf []byte) bool {
102105
}
103106

104107
if start < 0 {
105-
return false
108+
return false, nil
106109
}
107110
// wait until enter event loop
108-
return true
111+
return true, done
109112
}

clipboard_test.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
package clipboard_test
88

99
import (
10+
"context"
1011
"os"
1112
"reflect"
1213
"testing"
14+
"time"
1315

1416
"golang.design/x/clipboard"
1517
)
@@ -61,11 +63,27 @@ func TestClipboardMultipleWrites(t *testing.T) {
6163
if err != nil {
6264
t.Fatalf("failed to read gold file: %v", err)
6365
}
64-
clipboard.Write(clipboard.MIMEImage, data)
66+
chg := clipboard.Write(clipboard.MIMEImage, data)
6567

6668
data = []byte("golang.design/x/clipboard")
6769
clipboard.Write(clipboard.MIMEText, data)
6870

71+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
72+
defer cancel()
73+
74+
select {
75+
case <-ctx.Done():
76+
t.Fatalf("failed to receive clipboard change notification")
77+
case _, ok := <-chg:
78+
if !ok {
79+
t.Fatalf("change channel is closed before receiving the changed clipboard data")
80+
}
81+
}
82+
_, ok := <-chg
83+
if ok {
84+
t.Fatalf("changed channel should be closed after receiving the notification")
85+
}
86+
6987
b := clipboard.Read(clipboard.MIMEImage)
7088
if b != nil {
7189
t.Fatalf("read clipboard that should store text data as image should fail, but got: %d", len(b))

cmd/gclip/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ func cpy() error {
9797
}
9898
}
9999

100-
clipboard.Write(t, b)
100+
// Wait until clipboard content has been changed.
101+
<-clipboard.Write(t, b)
101102
return nil
102103
}
103104

0 commit comments

Comments
 (0)