Skip to content

Commit 3f01782

Browse files
authored
bsd: remove the Cgo / C-toolchain dependency (share the pure-Go X11 backend) (#121)
FreeBSD, OpenBSD and NetBSD now use the same pure-Go X11 backend as Linux, so they build and run with CGO_ENABLED=0 and need neither a C toolchain nor libX11. - Generalize the X11 backend to all four platforms: rename clipboard_x11_linux.go to clipboard_x11.go with build tag (linux||freebsd||openbsd||netbsd)&&!android, and guard the Linux-only abstract-socket fallback behind runtime.GOOS. - Rewrite clipboard_bsd.go as a pure-Go X11-only dispatcher and delete the cgo binding clipboard_bsd.c. Delete clipboard_x11.c, the shared cgo C implementation that only the BSD backend still used. - Extend clipboard_nocgo.go's build tag to exclude the BSDs, so they no longer degrade to the no-op stubs. - CI: build the FreeBSD/OpenBSD VM jobs with CGO_ENABLED=0 and drop the libX11 package; they are still build-only (no X server at runtime). - Tests: replace the per-platform CGO_ENABLED=0 skip guards with a degradesWithoutCgo() helper that knows the BSDs now have a cgo-free backend. - README: the BSDs need no Cgo and no X11 dev package. The X11 logic is unchanged and shared, so the Linux runtime tests cover it; the BSD code is verified to build for all three (cross-compiled) and on the CI VMs. Advances #55.
1 parent a1e7f1f commit 3f01782

8 files changed

Lines changed: 64 additions & 488 deletions

File tree

.github/workflows/clipboard.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ jobs:
8989
go build .
9090
go build ./cmd/gclip
9191
92-
# The BSDs are not available as GitHub-hosted runners, so build the X11
93-
# cgo path inside a VM to make sure it keeps compiling (see #55). The
94-
# clipboard needs a running X server at runtime, which is out of scope
95-
# for these build-only jobs.
92+
# The BSDs are not available as GitHub-hosted runners, so build the pure-Go
93+
# X11 backend inside a VM to make sure it keeps compiling (see #55). It needs
94+
# no Cgo and no libX11; the clipboard needs a running X server at runtime,
95+
# which is out of scope for these build-only jobs.
9696
freebsd_build:
9797
runs-on: ubuntu-latest
9898
steps:
@@ -102,11 +102,11 @@ jobs:
102102
with:
103103
usesh: true
104104
prepare: |
105-
pkg install -y go libX11
105+
pkg install -y go
106106
run: |
107107
go version
108-
CGO_ENABLED=1 go build .
109-
CGO_ENABLED=1 go build ./cmd/gclip
108+
CGO_ENABLED=0 go build .
109+
CGO_ENABLED=0 go build ./cmd/gclip
110110
111111
openbsd_build:
112112
runs-on: ubuntu-latest
@@ -120,8 +120,8 @@ jobs:
120120
pkg_add -I go
121121
run: |
122122
go version
123-
CGO_ENABLED=1 go build .
124-
CGO_ENABLED=1 go build ./cmd/gclip
123+
CGO_ENABLED=0 go build .
124+
CGO_ENABLED=0 go build ./cmd/gclip
125125
126126
# Exercise the native Wayland backend against a headless sway compositor.
127127
# sway implements a data-control manager (zwlr_data_control_manager_v1),

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,10 @@ accessing system clipboards, but here are a few details you might need to know.
139139
the Wayland backend is used automatically; otherwise the package falls back
140140
to X11 (via XWayland under Wayland). Older compositors without data-control
141141
keep working through XWayland.
142-
- FreeBSD/OpenBSD/NetBSD: require Cgo and the X11 dev package (libX11).
143-
FreeBSD and OpenBSD are verified in CI; NetBSD shares the same X11
144-
implementation on a best-effort basis and is untested.
142+
- FreeBSD/OpenBSD/NetBSD: no Cgo, no build dependency. They share Linux's
143+
pure-Go X11 backend (no `libX11`); a running X server is required at runtime.
144+
FreeBSD and OpenBSD are verified to build in CI; NetBSD is best-effort and
145+
untested.
145146
- Windows: no Cgo, no dependency
146147
- iOS/Android: collaborate with [`gomobile`](https://golang.org/x/mobile)
147148

clipboard_bsd.c

Lines changed: 0 additions & 14 deletions
This file was deleted.

clipboard_bsd.go

Lines changed: 18 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,36 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7-
// NOTE: FreeBSD and OpenBSD are verified to build in CI. NetBSD shares
8-
// the same X11 implementation and is included on a best-effort basis, but
9-
// it is not covered by CI and has not been fully tested — the X11 header
10-
// prefix and runtime behavior there are unverified.
7+
// NOTE: FreeBSD and OpenBSD are verified to build in CI. NetBSD shares the
8+
// same pure-Go X11 backend and is included on a best-effort basis, but it is
9+
// not covered by CI and has not been runtime-tested.
1110

1211
//go:build (openbsd || freebsd || netbsd) && !android
1312

1413
package clipboard
1514

16-
/*
17-
// X11 headers live in different prefixes across the BSDs: OpenBSD ships
18-
// them in base under /usr/X11R6, FreeBSD installs the libX11 port under
19-
// /usr/local, and NetBSD uses /usr/X11R7 (base) or /usr/pkg (pkgsrc).
20-
// List them all; the compiler ignores include paths that do not exist.
21-
#cgo CFLAGS: -I/usr/X11R6/include -I/usr/local/include -I/usr/X11R7/include -I/usr/pkg/include
22-
#include <stdlib.h>
23-
#include <stdio.h>
24-
#include <stdint.h>
25-
#include <string.h>
26-
27-
int clipboard_test();
28-
int clipboard_write(
29-
char* typ,
30-
unsigned char* buf,
31-
size_t n,
32-
uintptr_t handle
33-
);
34-
unsigned long clipboard_read(char* typ, char **out);
35-
*/
36-
import "C"
15+
// BSD clipboard dispatch. It uses the shared pure-Go X11 backend
16+
// (clipboard_x11.go), so it needs no Cgo and no libX11. The BSDs have no native
17+
// Wayland backend here, so this dispatches only to X11.
3718

3819
import (
3920
"bytes"
4021
"context"
4122
"fmt"
42-
"os"
43-
"runtime"
44-
"runtime/cgo"
4523
"time"
46-
"unsafe"
4724
)
4825

49-
var helpmsg = `%w: Failed to initialize the X11 display, and the clipboard package
50-
will not work properly. Install the following dependency may help:
51-
52-
apt install -y libx11-dev
53-
54-
If the clipboard package is in an environment without a frame buffer,
55-
such as a cloud server, it may also be necessary to install xvfb:
56-
57-
apt install -y xvfb
58-
59-
and initialize a virtual frame buffer:
60-
61-
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
62-
export DISPLAY=:99.0
26+
var helpmsg = `%w: Failed to connect to the X11 display, so the clipboard
27+
package will not work properly. Make sure an X server is running and the
28+
DISPLAY environment variable is set.
6329
64-
Then this package should be ready to use.
30+
If the clipboard package runs in an environment without a frame buffer, it may
31+
be necessary to start a virtual frame buffer (e.g. Xvfb) and point DISPLAY at
32+
it. Then this package should be ready to use.
6533
`
6634

6735
func initialize() error {
68-
ok := C.clipboard_test()
69-
if ok != 0 {
36+
if err := x11Test(); err != nil {
7037
return fmt.Errorf(helpmsg, errUnavailable)
7138
}
7239
return nil
@@ -75,78 +42,21 @@ func initialize() error {
7542
func read(t Format) (buf []byte, err error) {
7643
switch t {
7744
case FmtText:
78-
return readc("UTF8_STRING")
45+
return x11Read("UTF8_STRING")
7946
case FmtImage:
80-
return readc("image/png")
47+
return x11Read("image/png")
8148
}
8249
return nil, errUnsupported
8350
}
8451

85-
func readc(t string) ([]byte, error) {
86-
ct := C.CString(t)
87-
defer C.free(unsafe.Pointer(ct))
88-
89-
var data *C.char
90-
n := C.clipboard_read(ct, &data)
91-
switch C.long(n) {
92-
case -1:
93-
return nil, errUnavailable
94-
case -2:
95-
return nil, errUnsupported
96-
}
97-
if data == nil {
98-
return nil, errUnavailable
99-
}
100-
defer C.free(unsafe.Pointer(data))
101-
switch {
102-
case n == 0:
103-
return nil, nil
104-
default:
105-
return C.GoBytes(unsafe.Pointer(data), C.int(n)), nil
106-
}
107-
}
108-
109-
// write writes the given data to clipboard and
110-
// returns true if success or false if failed.
11152
func write(t Format, buf []byte) (<-chan struct{}, error) {
112-
var s string
11353
switch t {
11454
case FmtText:
115-
s = "UTF8_STRING"
55+
return x11Write("UTF8_STRING", buf)
11656
case FmtImage:
117-
s = "image/png"
57+
return x11Write("image/png", buf)
11858
}
119-
120-
start := make(chan int)
121-
done := make(chan struct{}, 1)
122-
123-
go func() { // serve as a daemon until the ownership is terminated.
124-
runtime.LockOSThread()
125-
defer runtime.UnlockOSThread()
126-
127-
cs := C.CString(s)
128-
defer C.free(unsafe.Pointer(cs))
129-
130-
h := cgo.NewHandle(start)
131-
var ok C.int
132-
if len(buf) == 0 {
133-
ok = C.clipboard_write(cs, nil, 0, C.uintptr_t(h))
134-
} else {
135-
ok = C.clipboard_write(cs, (*C.uchar)(unsafe.Pointer(&(buf[0]))), C.size_t(len(buf)), C.uintptr_t(h))
136-
}
137-
if ok != C.int(0) {
138-
fmt.Fprintf(os.Stderr, "write failed with status: %d\n", int(ok))
139-
}
140-
done <- struct{}{}
141-
close(done)
142-
}()
143-
144-
status := <-start
145-
if status < 0 {
146-
return nil, errUnavailable
147-
}
148-
// wait until enter event loop
149-
return done, nil
59+
return nil, errUnsupported
15060
}
15161

15262
func watch(ctx context.Context, t Format) <-chan []byte {
@@ -173,10 +83,3 @@ func watch(ctx context.Context, t Format) <-chan []byte {
17383
}()
17484
return recv
17585
}
176-
177-
//export syncStatus
178-
func syncStatus(h uintptr, val int) {
179-
v := cgo.Handle(h).Value().(chan int)
180-
v <- val
181-
cgo.Handle(h).Delete()
182-
}

clipboard_nocgo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !darwin && !windows && !linux && !cgo
1+
//go:build !darwin && !windows && !linux && !freebsd && !openbsd && !netbsd && !cgo
22

33
package clipboard
44

clipboard_test.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,25 @@ func init() {
2525
clipboard.Debug = true
2626
}
2727

28+
// degradesWithoutCgo reports whether the clipboard falls back to the no-op
29+
// CGO_ENABLED=0 stubs on the current platform. Platforms with a cgo-free
30+
// backend (Windows, macOS, Linux, and the BSDs) keep working without cgo; only
31+
// the remaining cgo-only platforms (e.g. Android) degrade.
32+
func degradesWithoutCgo() bool {
33+
switch runtime.GOOS {
34+
case "windows", "darwin", "linux", "freebsd", "openbsd", "netbsd":
35+
return false
36+
}
37+
return true
38+
}
39+
2840
func TestClipboardInit(t *testing.T) {
2941
t.Run("no-cgo", func(t *testing.T) {
3042
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
3143
t.Skip("CGO_ENABLED is set to 1")
3244
}
33-
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
34-
t.Skip("Windows, macOS and Linux do not need to check for cgo")
45+
if !degradesWithoutCgo() {
46+
t.Skip("this platform has a cgo-free backend; nothing to check")
3547
}
3648

3749
if err := clipboard.Init(); !errors.Is(err, clipboard.ErrCgoDisabled) {
@@ -53,7 +65,7 @@ func TestClipboardInit(t *testing.T) {
5365
}
5466

5567
func TestClipboard(t *testing.T) {
56-
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
68+
if degradesWithoutCgo() {
5769
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
5870
t.Skip("CGO_ENABLED is set to 0")
5971
}
@@ -138,7 +150,7 @@ func TestClipboard(t *testing.T) {
138150
}
139151

140152
func TestClipboardMultipleWrites(t *testing.T) {
141-
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
153+
if degradesWithoutCgo() {
142154
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
143155
t.Skip("CGO_ENABLED is set to 0")
144156
}
@@ -185,7 +197,7 @@ func TestClipboardMultipleWrites(t *testing.T) {
185197
}
186198

187199
func TestClipboardConcurrentRead(t *testing.T) {
188-
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
200+
if degradesWithoutCgo() {
189201
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
190202
t.Skip("CGO_ENABLED is set to 0")
191203
}
@@ -211,7 +223,7 @@ func TestClipboardConcurrentRead(t *testing.T) {
211223
}
212224

213225
func TestClipboardWriteEmpty(t *testing.T) {
214-
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
226+
if degradesWithoutCgo() {
215227
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
216228
t.Skip("CGO_ENABLED is set to 0")
217229
}
@@ -230,7 +242,7 @@ func TestClipboardWriteEmpty(t *testing.T) {
230242
}
231243

232244
func TestClipboardWatch(t *testing.T) {
233-
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
245+
if degradesWithoutCgo() {
234246
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
235247
t.Skip("CGO_ENABLED is set to 0")
236248
}
@@ -313,8 +325,8 @@ func TestClipboardNoCgo(t *testing.T) {
313325
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
314326
t.Skip("CGO_ENABLED is set to 1")
315327
}
316-
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
317-
t.Skip("Windows, macOS and Linux should always be tested")
328+
if !degradesWithoutCgo() {
329+
t.Skip("this platform has a cgo-free backend and is always tested")
318330
}
319331

320332
// When CGO is disabled, the clipboard cannot function but the public

0 commit comments

Comments
 (0)