Skip to content

Commit c534f34

Browse files
committed
all: watch API to watch clipboard changes
1 parent de98835 commit c534f34

File tree

4 files changed

+111
-0
lines changed

4 files changed

+111
-0
lines changed

clipboard.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package clipboard // import "golang.design/x/clipboard"
88

99
import (
10+
"context"
1011
"sync"
1112
)
1213

@@ -50,3 +51,9 @@ func Write(t Format, buf []byte) <-chan struct{} {
5051
}
5152
return changed
5253
}
54+
55+
// Watch returns a receive-only channel that received the clipboard data
56+
// if any changes of clipboard data in the desired format happends.
57+
func Watch(ctx context.Context, t Format) <-chan []byte {
58+
return watch(ctx, t)
59+
}

clipboard_darwin.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ NSInteger clipboard_change_count();
2323
*/
2424
import "C"
2525
import (
26+
"context"
2627
"time"
2728
"unsafe"
2829
)
@@ -77,6 +78,7 @@ func write(t Format, buf []byte) (bool, <-chan struct{}) {
7778
cnt := C.long(C.clipboard_change_count())
7879
go func() {
7980
for {
81+
// not sure if we are too slow or the user too fast :)
8082
time.Sleep(time.Second)
8183
cur := C.long(C.clipboard_change_count())
8284
if cnt != cur {
@@ -88,3 +90,30 @@ func write(t Format, buf []byte) (bool, <-chan struct{}) {
8890
}()
8991
return true, changed
9092
}
93+
94+
func watch(ctx context.Context, t Format) <-chan []byte {
95+
recv := make(chan []byte, 1)
96+
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())
100+
for {
101+
select {
102+
case <-ctx.Done():
103+
close(recv)
104+
return
105+
case <-ti.C:
106+
this := C.long(C.clipboard_change_count())
107+
if lastCount != this {
108+
b := Read(t)
109+
if b == nil {
110+
continue
111+
}
112+
recv <- b
113+
lastCount = this
114+
}
115+
}
116+
}
117+
}()
118+
return recv
119+
}

clipboard_linux.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ unsigned long clipboard_read(char* typ, char **out);
2929
*/
3030
import "C"
3131
import (
32+
"bytes"
33+
"context"
3234
"fmt"
3335
"os"
3436
"runtime"
37+
"time"
3538
"unsafe"
3639
)
3740

@@ -75,6 +78,7 @@ func readc(t string) []byte {
7578
// write writes the given data to clipboard and
7679
// returns true if success or false if failed.
7780
func write(t Format, buf []byte) (bool, <-chan struct{}) {
81+
7882
var s string
7983
switch t {
8084
case FmtText:
@@ -115,3 +119,28 @@ func write(t Format, buf []byte) (bool, <-chan struct{}) {
115119
// wait until enter event loop
116120
return true, done
117121
}
122+
123+
func watch(ctx context.Context, t Format) <-chan []byte {
124+
recv := make(chan []byte, 1)
125+
go func() {
126+
ti := time.NewTicker(time.Second)
127+
last := read(t)
128+
for {
129+
select {
130+
case <-ctx.Done():
131+
close(recv)
132+
return
133+
case <-ti.C:
134+
b := read(t)
135+
if b == nil {
136+
continue
137+
}
138+
if bytes.Compare(last, b) != 0 {
139+
recv <- b
140+
last = b
141+
}
142+
}
143+
}
144+
}()
145+
return recv
146+
}

clipboard_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package clipboard_test
88

99
import (
10+
"bytes"
1011
"context"
1112
"os"
1213
"reflect"
@@ -131,6 +132,51 @@ func TestClipboardWriteEmpty(t *testing.T) {
131132
t.Fatalf("write empty string to clipboard should read empty string, got: `%v`", string(got))
132133
}
133134
}
135+
136+
func TestClipboardWatch(t *testing.T) {
137+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
138+
defer cancel()
139+
140+
// clear clipboard
141+
clipboard.Write(clipboard.FmtText, []byte(""))
142+
lastRead := clipboard.Read(clipboard.FmtText)
143+
144+
changed := clipboard.Watch(ctx, clipboard.FmtText)
145+
146+
want := []byte("golang.design/x/clipboard")
147+
go func(ctx context.Context) {
148+
t := time.NewTicker(time.Millisecond * 500)
149+
for {
150+
select {
151+
case <-ctx.Done():
152+
return
153+
case <-t.C:
154+
clipboard.Write(clipboard.FmtText, want)
155+
}
156+
}
157+
}(ctx)
158+
for {
159+
select {
160+
case <-ctx.Done():
161+
if string(lastRead) == "" {
162+
t.Fatalf("clipboard watch never receives a notification")
163+
}
164+
return
165+
case data, ok := <-changed:
166+
if !ok {
167+
if string(lastRead) == "" {
168+
t.Fatalf("clipboard watch never receives a notification")
169+
}
170+
return
171+
}
172+
if bytes.Compare(data, want) != 0 {
173+
t.Fatalf("received data from watch mismatch, want: %v, got %v", string(want), string(data))
174+
}
175+
lastRead = data
176+
}
177+
}
178+
}
179+
134180
func BenchmarkClipboard(b *testing.B) {
135181

136182
b.Run("text", func(b *testing.B) {

0 commit comments

Comments
 (0)