Skip to content

Commit eb0cb20

Browse files
committed
all: add linux support
1 parent 068870e commit eb0cb20

File tree

11 files changed

+498
-1
lines changed

11 files changed

+498
-1
lines changed

.github/workflows/clipboard.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: clipboard
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
platform_test:
11+
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
os: [ubuntu-latest, windows-latest, macos-latest]
17+
18+
steps:
19+
- name: install libx11-dev
20+
run: |
21+
sudo apt install -y libx11-dev
22+
if: ${{ runner.os == 'Linux' }}
23+
- uses: actions/checkout@v2
24+
- uses: actions/setup-go@v2
25+
with:
26+
stable: 'false'
27+
go-version: '1.16.0-rc1'
28+
29+
- name: test
30+
run: |
31+
go test -v -covermode=atomic ./...
32+
# FIXME: on Linux, the action environment failed to use XOpenDisplay successfully.
33+
if: ${{ runner.os != 'Linux' }}

Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright 2021 The golang.design Initiative authors.
2+
# All rights reserved. Use of this source code is governed
3+
# by a GNU GPL-3 license that can be found in the LICENSE file.
4+
#
5+
# Written by Changkun Ou <changkun.de>
6+
7+
all:
8+
go test -v -count=1 -covermode=atomic ./...

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,32 @@
1-
# clipboard
1+
# clipboard [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/clipboard)](https://pkg.go.dev/golang.design/x/clipboard) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/clipboard) ![clipboard](https://github.com/golang-design/clipboard/workflows/clipboard/badge.svg?branch=main)
2+
23
clipboard access with Go
4+
5+
```go
6+
import "golang.design/x/clipboard"
7+
```
8+
9+
## Dependency
10+
11+
- Linux users: `apt install -y libx11-dev`
12+
- macOS users: no dependency
13+
14+
## Usage
15+
16+
```go
17+
// write texts to the clipboard
18+
clipboard.Write(clipboard.MIMEText, []byte("text data"))
19+
20+
// read texts from the clipboard
21+
clipboard.Read(clipboard.MIMEText)
22+
23+
// write image to the clipboard, assume image bytes are png encoded.
24+
clipboard.Write(clipboard.MIMEImage, []byte("image data"))
25+
26+
// read image from the clipboard
27+
clipboard.Read(clipboard.MIMEImage)
28+
```
29+
30+
## License
31+
32+
GNU GPL-3 Copyright &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

clipboard.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2021 The golang.design Initiative authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a GNU GPL-3 license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
package clipboard // import "golang.design/x/clipboard"
8+
9+
import (
10+
"sync"
11+
)
12+
13+
// MIMEType represents the MIME type of clipboard data.
14+
type MIMEType int
15+
16+
// All sorts of supported clipboard data
17+
const (
18+
// MIMEText indicates plain text MIME format
19+
MIMEText MIMEType = iota
20+
// MIMEImage indicates image/png MIME format
21+
MIMEImage
22+
)
23+
24+
// Due to the limitation on operating systems (such as darwin),
25+
// concurrent read can even cause panic, use a global lock to
26+
// guarantee one read at a time.
27+
var lock = sync.Mutex{}
28+
29+
// Read reads and returns the clipboard data.
30+
func Read(t MIMEType) []byte {
31+
lock.Lock()
32+
defer lock.Unlock()
33+
34+
return read(t)
35+
}
36+
37+
// Write writes the given buffer to the clipboard.
38+
//
39+
// If the MIME type indicates an image, then the given buf assumes
40+
// the image data is PNG encoded.
41+
func Write(t MIMEType, buf []byte) {
42+
lock.Lock()
43+
defer lock.Unlock()
44+
45+
write(t, buf)
46+
}

clipboard_darwin.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2021 The golang.design Initiative authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a GNU GPL-3 license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
// +build !linux,darwin
8+
9+
package clipboard
10+
11+
func readAll() (buf []byte) {
12+
panic("unsupported")
13+
}
14+
15+
func writeAll(buf []byte) {
16+
panic("unsupported")
17+
}

clipboard_linux.c

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright 2021 The golang.design Initiative authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a GNU GPL-3 license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
#include <stdlib.h>
8+
#include <stdio.h>
9+
#include <string.h>
10+
#include <X11/Xlib.h>
11+
#include <X11/Xatom.h>
12+
13+
int clipboard_test() {
14+
Display *d = XOpenDisplay(0);
15+
if (d == NULL) {
16+
return -1;
17+
}
18+
XCloseDisplay(d);
19+
return 0;
20+
}
21+
22+
// clipboard_write writes the given buf of size n as type typ.
23+
// if start is provided, the value of start will be changed to 1 to indicate
24+
// if the write is availiable for reading.
25+
int clipboard_write(char *typ, unsigned char *buf, size_t n, int *start) {
26+
Display* d = XOpenDisplay(0);
27+
if (d == NULL) {
28+
if (start != NULL && *start == 0) *start = -1;
29+
return -1;
30+
}
31+
32+
Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
33+
Atom sel = XInternAtom(d, "CLIPBOARD", True);
34+
Atom atomString = XInternAtom(d, "UTF8_STRING", True);
35+
Atom atomImage = XInternAtom(d, "image/png", True);
36+
Atom targetsAtom = XInternAtom(d, "TARGETS", True);
37+
38+
Atom target = XInternAtom(d, typ, True);
39+
if (target == None) {
40+
XCloseDisplay(d);
41+
if (start != NULL && *start == 0) *start = -2;
42+
return -2;
43+
}
44+
45+
XSetSelectionOwner(d, sel, w, CurrentTime);
46+
if (XGetSelectionOwner(d, sel) != w) {
47+
XCloseDisplay(d);
48+
if (start != NULL && *start == 0) *start = -3;
49+
return -3;
50+
}
51+
52+
XEvent event;
53+
XSelectionRequestEvent* xsr;
54+
for (;;) {
55+
// FIXME: this should race with the code on the Go side, start
56+
// should use an atomic version, and use atomic_store.
57+
if (start != NULL && *start == 0)
58+
*start = 1; // notify Go side
59+
60+
XNextEvent(d, &event);
61+
switch (event.type) {
62+
case SelectionClear:
63+
// For debugging:
64+
// printf("x11write: lost ownership of clipboard selection.\n");
65+
// fflush(stdout);
66+
XCloseDisplay(d);
67+
return 0;
68+
case SelectionNotify:
69+
// For debugging:
70+
// printf("x11write: notify.\n");
71+
// fflush(stdout);
72+
break;
73+
case SelectionRequest:
74+
if (event.xselectionrequest.selection != sel) {
75+
break;
76+
}
77+
78+
XSelectionRequestEvent * xsr = &event.xselectionrequest;
79+
XSelectionEvent ev = {0};
80+
int R = 0;
81+
82+
ev.type = SelectionNotify;
83+
ev.display = xsr->display;
84+
ev.requestor = xsr->requestor;
85+
ev.selection = xsr->selection;
86+
ev.time = xsr->time;
87+
ev.target = xsr->target;
88+
ev.property = xsr->property;
89+
90+
if (ev.target == atomString && ev.target == target) {
91+
R = XChangeProperty(ev.display, ev.requestor, ev.property,
92+
atomString, 8, PropModeReplace, buf, n);
93+
} else if (ev.target == atomImage && ev.target == target) {
94+
R = XChangeProperty(ev.display, ev.requestor, ev.property,
95+
atomImage, 8, PropModeReplace, buf, n);
96+
} else if (ev.target == targetsAtom) {
97+
// Reply atoms for supported targets, other clients should
98+
// request the clipboard again and obtain the data if their
99+
// implementation is correct.
100+
Atom targets[] = { atomString, atomImage };
101+
R = XChangeProperty(ev.display, ev.requestor, ev.property,
102+
XA_ATOM, 32, PropModeReplace,
103+
(unsigned char *)&targets, sizeof(targets)/sizeof(Atom));
104+
} else {
105+
ev.property = None;
106+
}
107+
108+
if ((R & 2) == 0) XSendEvent(d, ev.requestor, 0, 0, (XEvent *)&ev);
109+
break;
110+
}
111+
}
112+
}
113+
114+
// read_data reads the property of a selection if the target atom matches
115+
// the actual atom.
116+
unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target, char **buf) {
117+
unsigned char *data;
118+
Atom actual;
119+
int format;
120+
unsigned long n = 0;
121+
unsigned long size = 0;
122+
if (sev->property == None || sev->selection != sel || sev->property != prop) {
123+
return 0;
124+
}
125+
126+
int ret = XGetWindowProperty(sev->display, sev->requestor, sev->property,
127+
0L, (~0L), 0, AnyPropertyType, &actual, &format, &size, &n, &data);
128+
if (ret != Success) {
129+
return 0;
130+
}
131+
132+
if (actual == target && buf != NULL) {
133+
*buf = (char *)malloc(size * sizeof(char));
134+
memcpy(*buf, data, size*sizeof(char));
135+
}
136+
XFree(data);
137+
XDeleteProperty(sev->display, sev->requestor, sev->property);
138+
return size * sizeof(char);
139+
}
140+
141+
// clipboard_read reads the clipboard selection in given mime type typ.
142+
// the readed bytes is written into buf and returns the size of the buffer.
143+
//
144+
// The caller of this function should responsible for the free of the buf.
145+
unsigned long clipboard_read(char* typ, char **buf) {
146+
Display *d = XOpenDisplay(0);
147+
if (d == NULL) {
148+
return -1;
149+
}
150+
151+
Window w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, 1, 1, 0, 0, 0);
152+
Atom sele = XInternAtom(d, "CLIPBOARD", True);
153+
Atom prop = XInternAtom(d, "GOLANG_DESIGN_DATA", False);
154+
Atom target = XInternAtom(d, typ, True);
155+
if (target == None) {
156+
XCloseDisplay(d);
157+
return -1;
158+
}
159+
XConvertSelection(d, sele, target, prop, w, CurrentTime);
160+
XEvent event;
161+
for (;;) {
162+
XNextEvent(d, &event);
163+
if (event.type != SelectionNotify) continue;
164+
break;
165+
}
166+
unsigned long n = read_data((XSelectionEvent *)&event.xselection, sele, prop, target, buf);
167+
XCloseDisplay(d);
168+
return n;
169+
}

0 commit comments

Comments
 (0)