chore: internal clipboard package

This commit is contained in:
adamdottv 2025-07-09 04:55:19 -05:00
parent 67765fa47c
commit 3f25e5bf86
No known key found for this signature in database
GPG key ID: 9CB48779AF150E75
40 changed files with 8 additions and 1627 deletions

View file

@ -1,12 +0,0 @@
# These are supported funding model platforms
github: [changkun] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View file

@ -1,71 +0,0 @@
# Copyright 2021 The golang.design Initiative Authors.
# All rights reserved. Use of this source code is governed
# by a MIT license that can be found in the LICENSE file.
#
# Written by Changkun Ou <changkun.de>
name: clipboard
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
platform_test:
env:
DISPLAY: ':0.0'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ['1.24.x']
steps:
- name: Install and run dependencies (xvfb libx11-dev)
if: ${{ runner.os == 'Linux' }}
run: |
sudo apt update
sudo apt install -y xvfb libx11-dev x11-utils libegl1-mesa-dev libgles2-mesa-dev
Xvfb :0 -screen 0 1024x768x24 > /dev/null 2>&1 &
# Wait for Xvfb
MAX_ATTEMPTS=120 # About 60 seconds
COUNT=0
echo -n "Waiting for Xvfb to be ready..."
while ! xdpyinfo -display "${DISPLAY}" >/dev/null 2>&1; do
echo -n "."
sleep 0.50s
COUNT=$(( COUNT + 1 ))
if [ "${COUNT}" -ge "${MAX_ATTEMPTS}" ]; then
echo " Gave up waiting for X server on ${DISPLAY}"
exit 1
fi
done
echo "Done - Xvfb is ready!"
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
stable: 'false'
go-version: ${{ matrix.go }}
- name: Build (${{ matrix.go }})
run: |
go build -o gclip cmd/gclip/main.go
go build -o gclip-gui cmd/gclip-gui/main.go
- name: Run Tests with CGO_ENABLED=1 (${{ matrix.go }})
if: ${{ runner.os == 'Linux' || runner.os == 'macOS'}}
run: |
CGO_ENABLED=1 go test -v -covermode=atomic .
- name: Run Tests with CGO_ENABLED=0 (${{ matrix.go }})
if: ${{ runner.os == 'Linux' || runner.os == 'macOS'}}
run: |
CGO_ENABLED=0 go test -v -covermode=atomic .
- name: Run Tests on Windows (${{ matrix.go }})
if: ${{ runner.os == 'Windows'}}
run: |
go test -v -covermode=atomic .

View file

@ -1,15 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/

View file

@ -1,13 +0,0 @@
# Copyright 2021 The golang.design Initiative Authors.
# All rights reserved. Use of this source code is governed
# by a MIT license that can be found in the LICENSE file.
#
# Written by Changkun Ou <changkun.de>
FROM golang:1.24
RUN apt-get update && apt-get install -y \
xvfb libx11-dev libegl1-mesa-dev libgles2-mesa-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /app
COPY . .
CMD [ "sh", "-c", "./tests/test-docker.sh" ]

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Changkun Ou <contact@changkun.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,162 +0,0 @@
# 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)
Cross platform (macOS/Linux/Windows/Android/iOS) clipboard package in Go
```go
import "golang.design/x/clipboard"
```
## Features
- Cross platform supports: **macOS, Linux (X11), Windows, iOS, and Android**
- Copy/paste UTF-8 text
- Copy/paste PNG encoded images (Desktop-only)
- Command `gclip` as a demo application
- Mobile app `gclip-gui` as a demo application
## API Usage
Package clipboard provides cross platform clipboard access and supports
macOS/Linux/Windows/Android/iOS platform. Before interacting with the
clipboard, one must call Init to assert if it is possible to use this
package:
```go
// Init returns an error if the package is not ready for use.
err := clipboard.Init()
if err != nil {
panic(err)
}
```
The most common operations are `Read` and `Write`. To use them:
```go
// write/read text format data of the clipboard, and
// the byte buffer regarding the text are UTF8 encoded.
clipboard.Write(clipboard.FmtText, []byte("text data"))
clipboard.Read(clipboard.FmtText)
// write/read image format data of the clipboard, and
// the byte buffer regarding the image are PNG encoded.
clipboard.Write(clipboard.FmtImage, []byte("image data"))
clipboard.Read(clipboard.FmtImage)
```
Note that read/write regarding image format assumes that the bytes are
PNG encoded since it serves the alpha blending purpose that might be
used in other graphical software.
In addition, `clipboard.Write` returns a channel that can receive an
empty struct as a signal, which indicates the corresponding write call
to the clipboard is outdated, meaning the clipboard has been overwritten
by others and the previously written data is lost. For instance:
```go
changed := clipboard.Write(clipboard.FmtText, []byte("text data"))
select {
case <-changed:
println(`"text data" is no longer available from clipboard.`)
}
```
You can ignore the returning channel if you don't need this type of
notification. Furthermore, when you need more than just knowing whether
clipboard data is changed, use the watcher API:
```go
ch := clipboard.Watch(context.TODO(), clipboard.FmtText)
for data := range ch {
// print out clipboard data whenever it is changed
println(string(data))
}
```
## Demos
- A command line tool `gclip` for command line clipboard accesses, see document [here](./cmd/gclip/README.md).
- A GUI application `gclip-gui` for functionality verifications on mobile systems, see a document [here](./cmd/gclip-gui/README.md).
## Command Usage
`gclip` command offers the ability to interact with the system clipboard
from the shell. To install:
```bash
$ go install golang.design/x/clipboard/cmd/gclip@latest
```
```bash
$ gclip
gclip is a command that provides clipboard interaction.
usage: gclip [-copy|-paste] [-f <file>]
options:
-copy
copy data to clipboard
-f string
source or destination to a given file path
-paste
paste data from clipboard
examples:
gclip -paste paste from clipboard and prints the content
gclip -paste -f x.txt paste from clipboard and save as text to x.txt
gclip -paste -f x.png paste from clipboard and save as image to x.png
cat x.txt | gclip -copy copy content from x.txt to clipboard
gclip -copy -f x.txt copy content from x.txt to clipboard
gclip -copy -f x.png copy x.png as image data to clipboard
```
If `-copy` is used, the command will exit when the data is no longer
available from the clipboard. You can always send the command to the
background using a shell `&` operator, for example:
```bash
$ cat x.txt | gclip -copy &
```
## Platform Specific Details
This package spent efforts to provide cross platform abstraction regarding
accessing system clipboards, but here are a few details you might need to know.
### Dependency
- macOS: require Cgo, no dependency
- Linux: require X11 dev package. For instance, install `libx11-dev` or `xorg-dev` or `libX11-devel` to access X window system.
Wayland sessions are currently unsupported; running under Wayland
typically requires an XWayland bridge and `DISPLAY` to be set.
- Windows: no Cgo, no dependency
- iOS/Android: collaborate with [`gomobile`](https://golang.org/x/mobile)
### Screenshot
In general, when you need test your implementation regarding images,
There are system level shortcuts to put screenshot image into your system clipboard:
- On macOS, use `Ctrl+Shift+Cmd+4`
- On Linux/Ubuntu, use `Ctrl+Shift+PrintScreen`
- On Windows, use `Shift+Win+s`
As described in the API documentation, the package supports read/write
UTF8 encoded plain text or PNG encoded image data. Thus,
the other types of data are not supported yet, i.e. undefined behavior.
## Who is using this package?
The main purpose of building this package is to support the
[midgard](https://changkun.de/s/midgard) project, which offers
clipboard-based features like universal clipboard service that syncs
clipboard content across multiple systems, allocating public accessible
for clipboard content, etc.
To know more projects, check our [wiki](https://github.com/golang-design/clipboard/wiki) page.
## License
MIT | &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

View file

@ -1,80 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build android
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, \
"GOLANG.DESIGN/X/CLIPBOARD", __VA_ARGS__)
static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
if (m == 0) {
(*env)->ExceptionClear(env);
LOG_FATAL("cannot find method %s %s", name, sig);
return 0;
}
return m;
}
jobject get_clipboard(uintptr_t jni_env, uintptr_t ctx) {
JNIEnv *env = (JNIEnv*)jni_env;
jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jstring service = (*env)->NewStringUTF(env, "clipboard");
jobject ret = (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service);
jthrowable err = (*env)->ExceptionOccurred(env);
if (err != NULL) {
LOG_FATAL("cannot find clipboard");
(*env)->ExceptionClear(env);
return NULL;
}
return ret;
}
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject mgr = get_clipboard(jni_env, ctx);
if (mgr == NULL) {
return NULL;
}
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
jmethodID getText = find_method(env, mgrClass, "getText", "()Ljava/lang/CharSequence;");
jobject content = (jstring)(*env)->CallObjectMethod(env, mgr, getText);
if (content == NULL) {
return NULL;
}
jclass clzCharSequence = (*env)->GetObjectClass(env, content);
jmethodID toString = (*env)->GetMethodID(env, clzCharSequence, "toString", "()Ljava/lang/String;");
jobject s = (*env)->CallObjectMethod(env, content, toString);
const char *chars = (*env)->GetStringUTFChars(env, s, NULL);
char *copy = strdup(chars);
(*env)->ReleaseStringUTFChars(env, s, chars);
return copy;
}
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject mgr = get_clipboard(jni_env, ctx);
if (mgr == NULL) {
return;
}
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
jmethodID setText = find_method(env, mgrClass, "setText", "(Ljava/lang/CharSequence;)V");
(*env)->CallVoidMethod(env, mgr, setText, (*env)->NewStringUTF(env, str));
}

View file

@ -1,102 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build android
package clipboard
/*
#cgo LDFLAGS: -landroid -llog
#include <stdlib.h>
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str);
*/
import "C"
import (
"bytes"
"context"
"time"
"unsafe"
"golang.org/x/mobile/app"
)
func initialize() error { return nil }
func read(t Format) (buf []byte, err error) {
switch t {
case FmtText:
s := ""
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
cs := C.clipboard_read_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
if cs == nil {
return nil
}
s = C.GoString(cs)
C.free(unsafe.Pointer(cs))
return nil
}); err != nil {
return nil, err
}
return []byte(s), nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
// write writes the given data to clipboard and
// returns true if success or false if failed.
func write(t Format, buf []byte) (<-chan struct{}, error) {
done := make(chan struct{}, 1)
switch t {
case FmtText:
cs := C.CString(string(buf))
defer C.free(unsafe.Pointer(cs))
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
C.clipboard_write_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), cs)
done <- struct{}{}
return nil
}); err != nil {
return nil, err
}
return done, nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
ti := time.NewTicker(time.Second)
last := Read(t)
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
b := Read(t)
if b == nil {
continue
}
if bytes.Compare(last, b) != 0 {
recv <- b
last = b
}
}
}
}()
return recv
}

View file

@ -1,80 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build ios
package clipboard
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework MobileCoreServices
#import <stdlib.h>
void clipboard_write_string(char *s);
char *clipboard_read_string();
*/
import "C"
import (
"bytes"
"context"
"time"
"unsafe"
)
func initialize() error { return nil }
func read(t Format) (buf []byte, err error) {
switch t {
case FmtText:
return []byte(C.GoString(C.clipboard_read_string())), nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
// SetContent sets the clipboard content for iOS
func write(t Format, buf []byte) (<-chan struct{}, error) {
done := make(chan struct{}, 1)
switch t {
case FmtText:
cs := C.CString(string(buf))
defer C.free(unsafe.Pointer(cs))
C.clipboard_write_string(cs)
return done, nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
ti := time.NewTicker(time.Second)
last := Read(t)
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
b := Read(t)
if b == nil {
continue
}
if bytes.Compare(last, b) != 0 {
recv <- b
last = b
}
}
}
}()
return recv
}

View file

@ -1,20 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build ios
#import <UIKit/UIKit.h>
#import <MobileCoreServices/MobileCoreServices.h>
void clipboard_write_string(char *s) {
NSString *value = [NSString stringWithUTF8String:s];
[[UIPasteboard generalPasteboard] setString:value];
}
char *clipboard_read_string() {
NSString *str = [[UIPasteboard generalPasteboard] string];
return (char *)[str UTF8String];
}

View file

@ -1,336 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
package clipboard_test
import (
"bytes"
"context"
"errors"
"image/color"
"image/png"
"os"
"reflect"
"runtime"
"testing"
"time"
"golang.design/x/clipboard"
)
func init() {
clipboard.Debug = true
}
func TestClipboardInit(t *testing.T) {
t.Run("no-cgo", func(t *testing.T) {
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
t.Skip("CGO_ENABLED is set to 1")
}
if runtime.GOOS == "windows" {
t.Skip("Windows does not need to check for cgo")
}
if err := clipboard.Init(); !errors.Is(err, clipboard.ErrCgoDisabled) {
t.Fatalf("expect ErrCgoDisabled, got: %v", err)
}
})
t.Run("with-cgo", func(t *testing.T) {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
if runtime.GOOS != "linux" {
t.Skip("Only Linux may return error at the moment.")
}
if err := clipboard.Init(); err != nil && !errors.Is(err, clipboard.ErrUnavailable) {
t.Fatalf("expect ErrUnavailable, but got: %v", err)
}
})
}
func TestClipboard(t *testing.T) {
if runtime.GOOS != "windows" {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
}
t.Run("image", func(t *testing.T) {
data, err := os.ReadFile("tests/testdata/clipboard.png")
if err != nil {
t.Fatalf("failed to read gold file: %v", err)
}
clipboard.Write(clipboard.FmtImage, data)
b := clipboard.Read(clipboard.FmtText)
if b != nil {
t.Fatalf("read clipboard that stores image data as text should fail, but got len: %d", len(b))
}
b = clipboard.Read(clipboard.FmtImage)
if b == nil {
t.Fatalf("read clipboard that stores image data as image should success, but got: nil")
}
img1, err := png.Decode(bytes.NewReader(data))
if err != nil {
t.Fatalf("write image is not png encoded: %v", err)
}
img2, err := png.Decode(bytes.NewReader(b))
if err != nil {
t.Fatalf("read image is not png encoded: %v", err)
}
w := img2.Bounds().Dx()
h := img2.Bounds().Dy()
incorrect := 0
for i := 0; i < w; i++ {
for j := 0; j < h; j++ {
wr, wg, wb, wa := img1.At(i, j).RGBA()
gr, gg, gb, ga := img2.At(i, j).RGBA()
want := color.RGBA{
R: uint8(wr),
G: uint8(wg),
B: uint8(wb),
A: uint8(wa),
}
got := color.RGBA{
R: uint8(gr),
G: uint8(gg),
B: uint8(gb),
A: uint8(ga),
}
if !reflect.DeepEqual(want, got) {
t.Logf("read data from clipbaord is inconsistent with previous written data, pix: (%d,%d), got: %+v, want: %+v", i, j, got, want)
incorrect++
}
}
}
if incorrect > 0 {
t.Fatalf("read data from clipboard contains too much inconsistent pixels to the previous written data, number of incorrect pixels: %v", incorrect)
}
})
t.Run("text", func(t *testing.T) {
data := []byte("golang.design/x/clipboard")
clipboard.Write(clipboard.FmtText, data)
b := clipboard.Read(clipboard.FmtImage)
if b != nil {
t.Fatalf("read clipboard that stores text data as image should fail, but got len: %d", len(b))
}
b = clipboard.Read(clipboard.FmtText)
if b == nil {
t.Fatal("read clipboard taht stores text data as text should success, but got: nil")
}
if !reflect.DeepEqual(data, b) {
t.Fatalf("read data from clipbaord is inconsistent with previous written data, got: %d, want: %d", len(b), len(data))
}
})
}
func TestClipboardMultipleWrites(t *testing.T) {
if runtime.GOOS != "windows" {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
}
data, err := os.ReadFile("tests/testdata/clipboard.png")
if err != nil {
t.Fatalf("failed to read gold file: %v", err)
}
chg := clipboard.Write(clipboard.FmtImage, data)
data = []byte("golang.design/x/clipboard")
clipboard.Write(clipboard.FmtText, data)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
select {
case <-ctx.Done():
t.Fatalf("failed to receive clipboard change notification")
case _, ok := <-chg:
if !ok {
t.Fatalf("change channel is closed before receiving the changed clipboard data")
}
}
_, ok := <-chg
if ok {
t.Fatalf("changed channel should be closed after receiving the notification")
}
b := clipboard.Read(clipboard.FmtImage)
if b != nil {
t.Fatalf("read clipboard that should store text data as image should fail, but got: %d", len(b))
}
b = clipboard.Read(clipboard.FmtText)
if b == nil {
t.Fatalf("read clipboard that should store text data as text should success, got: nil")
}
if !reflect.DeepEqual(data, b) {
t.Fatalf("read data from clipbaord is inconsistent with previous write, want %s, got: %s", string(data), string(b))
}
}
func TestClipboardConcurrentRead(t *testing.T) {
if runtime.GOOS != "windows" {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
}
// This test check that concurrent read/write to the clipboard does
// not cause crashes on some specific platform, such as macOS.
done := make(chan bool, 2)
go func() {
defer func() {
done <- true
}()
clipboard.Read(clipboard.FmtText)
}()
go func() {
defer func() {
done <- true
}()
clipboard.Read(clipboard.FmtImage)
}()
<-done
<-done
}
func TestClipboardWriteEmpty(t *testing.T) {
if runtime.GOOS != "windows" {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
}
chg1 := clipboard.Write(clipboard.FmtText, nil)
if got := clipboard.Read(clipboard.FmtText); got != nil {
t.Fatalf("write nil to clipboard should read nil, got: %v", string(got))
}
clipboard.Write(clipboard.FmtText, []byte(""))
<-chg1
if got := clipboard.Read(clipboard.FmtText); string(got) != "" {
t.Fatalf("write empty string to clipboard should read empty string, got: `%v`", string(got))
}
}
func TestClipboardWatch(t *testing.T) {
if runtime.GOOS != "windows" {
if val, ok := os.LookupEnv("CGO_ENABLED"); ok && val == "0" {
t.Skip("CGO_ENABLED is set to 0")
}
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
// clear clipboard
clipboard.Write(clipboard.FmtText, []byte(""))
lastRead := clipboard.Read(clipboard.FmtText)
changed := clipboard.Watch(ctx, clipboard.FmtText)
want := []byte("golang.design/x/clipboard")
go func(ctx context.Context) {
t := time.NewTicker(time.Millisecond * 500)
for {
select {
case <-ctx.Done():
return
case <-t.C:
clipboard.Write(clipboard.FmtText, want)
}
}
}(ctx)
for {
select {
case <-ctx.Done():
if string(lastRead) == "" {
t.Fatalf("clipboard watch never receives a notification")
}
t.Log(string(lastRead))
return
case data, ok := <-changed:
if !ok {
if string(lastRead) == "" {
t.Fatalf("clipboard watch never receives a notification")
}
return
}
if !bytes.Equal(data, want) {
t.Fatalf("received data from watch mismatch, want: %v, got %v", string(want), string(data))
}
lastRead = data
}
}
}
func BenchmarkClipboard(b *testing.B) {
b.Run("text", func(b *testing.B) {
data := []byte("golang.design/x/clipboard")
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
clipboard.Write(clipboard.FmtText, data)
_ = clipboard.Read(clipboard.FmtText)
}
})
}
func TestClipboardNoCgo(t *testing.T) {
if val, ok := os.LookupEnv("CGO_ENABLED"); !ok || val != "0" {
t.Skip("CGO_ENABLED is set to 1")
}
if runtime.GOOS == "windows" {
t.Skip("Windows should always be tested")
}
t.Run("Read", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("expect to fail when CGO_ENABLED=0")
}()
clipboard.Read(clipboard.FmtText)
})
t.Run("Write", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("expect to fail when CGO_ENABLED=0")
}()
clipboard.Write(clipboard.FmtText, []byte("dummy"))
})
t.Run("Watch", func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
return
}
t.Fatalf("expect to fail when CGO_ENABLED=0")
}()
clipboard.Watch(context.TODO(), clipboard.FmtText)
})
}

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2021 The golang.design Initiative Authors.
All rights reserved. Use of this source code is governed
by a MIT license that can be found in the LICENSE file.
Written by Changkun Ou <changkun.de>
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="design.golang.clipboard.gclip"
android:versionCode="1"
android:versionName="1.0">
<!-- In order to access the clipboard, the application manifest must
specify the permission requirement. See the following page for
details.
http://developer.android.com/guide/topics/manifest/manifest-intro.html#perms -->
<uses-permission android:name="android.permission.CLIPBOARD" />
<application android:label="gclip" android:debuggable="true">
<activity android:name="org.golang.app.GoNativeActivity"
android:label="Gclip"
android:configChanges="orientation|keyboardHidden">
<meta-data android:name="android.app.lib_name" android:value="Gclip" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,31 +0,0 @@
# gclip-gui
This is a very basic example for verification purpose that demonstrates
how the [golang.design/x/clipboard](https://golang.design/x/clipboard)
can interact with macOS/Linux/Windows/Android/iOS system clipboard.
The gclip GUI application writes a string to the system clipboard
periodically then reads it back and renders it if possible.
Because of the system limitation, on mobile devices, only string data is
supported at the moment. Hence, one must use clipboard.FmtText. Other supplied
formats result in a panic.
This example is intentded as cross platform application. To build it, one
must use [gomobile](https://golang.org/x/mobile). You may follow the instructions
provided in the [GoMobile wiki](https://github.com/golang/go/wiki/Mobile) page.
- For desktop: `go build -o gclip-gui`
- For Android: `gomobile build -v -target=android -o gclip-gui.apk`
- For iOS: `gomobile build -v -target=ios -bundleid design.golang.gclip-gui.app`
## Screenshots
| macOS | iOS | Windows | Android | Linux |
|:-----:|:---:|:-------:|:-------:|:-----:|
|![](../../tests/testdata/darwin.png)|![](../../tests/testdata/ios.png)|![](../../tests/testdata/windows.png)|![](../../tests/testdata/android.png)|![](../../tests/testdata/linux.png)|
## License
MIT | &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

View file

@ -1,236 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build android || ios || linux || darwin || windows
// This is a very basic example for verification purpose that
// demonstrates how the golang.design/x/clipboard can interact
// with macOS/Linux/Windows/Android/iOS system clipboard.
//
// The gclip GUI application writes a string to the system clipboard
// periodically then reads it back and renders it if possible.
//
// Because of the system limitation, on mobile devices, only string
// data is supported at the moment. Hence, one must use clipboard.FmtText.
// Other supplied formats result in a panic.
//
// This example is intentded as cross platform application.
// To build it, one must use gomobile (https://golang.org/x/mobile).
// You may follow the instructions provided in the GoMobile's wiki page:
// https://github.com/golang/go/wiki/Mobile.
//
// - For desktop:
//
// go build -o gclip-gui
//
// - For Android:
//
// gomobile build -v -target=android -o gclip-gui.apk
//
// - For iOS:
//
// gomobile build -v -target=ios -bundleid design.golang.gclip-gui.app
//
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"sync"
"time"
"golang.design/x/clipboard"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
"golang.org/x/mobile/app"
"golang.org/x/mobile/event/lifecycle"
"golang.org/x/mobile/event/paint"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
type Label struct {
sz size.Event
images *glutil.Images
m *glutil.Image
drawer *font.Drawer
mu sync.Mutex
data string
}
func NewLabel(images *glutil.Images) *Label {
return &Label{
images: images,
data: "Hello! Gclip.",
drawer: nil,
}
}
func (l *Label) SetLabel(s string) {
l.mu.Lock()
defer l.mu.Unlock()
l.data = s
}
const (
lineWidth = 100
lineHeight = 120
)
func (l *Label) Draw(sz size.Event) {
l.mu.Lock()
s := l.data
l.mu.Unlock()
imgW, imgH := lineWidth*basicfont.Face7x13.Width, lineHeight*basicfont.Face7x13.Height
if sz.WidthPx == 0 && sz.HeightPx == 0 {
return
}
if imgW > sz.WidthPx {
imgW = sz.WidthPx
}
if l.sz != sz {
l.sz = sz
if l.m != nil {
l.m.Release()
}
l.m = l.images.NewImage(imgW, imgH)
}
// Clear the drawing image.
for i := 0; i < len(l.m.RGBA.Pix); i++ {
l.m.RGBA.Pix[i] = 0
}
l.drawer = &font.Drawer{
Dst: l.m.RGBA,
Src: image.NewUniform(color.RGBA{0, 100, 125, 255}),
Face: basicfont.Face7x13,
Dot: fixed.P(5, 10),
}
l.drawer.DrawString(s)
l.m.Upload()
l.m.Draw(
sz,
geom.Point{X: 0, Y: 50},
geom.Point{X: geom.Pt(imgW), Y: 50},
geom.Point{X: 0, Y: geom.Pt(imgH)},
l.m.RGBA.Bounds(),
)
}
func (l *Label) Release() {
if l.m != nil {
l.m.Release()
l.m = nil
l.images = nil
}
}
// GclipApp is the application instance.
type GclipApp struct {
app app.App
ctx gl.Context
siz size.Event
images *glutil.Images
l *Label
counter int
}
// WatchClipboard watches the system clipboard every seconds.
func (g *GclipApp) WatchClipboard() {
go func() {
tk := time.NewTicker(time.Second)
for range tk.C {
// Write something to the clipboard
w := fmt.Sprintf("(gclip: %d)", g.counter)
clipboard.Write(clipboard.FmtText, []byte(w))
g.counter++
log.Println(w)
// Read it back and render it, if possible.
data := clipboard.Read(clipboard.FmtText)
if len(data) == 0 {
continue
}
// Set the current clipboard data as label content and render on the screen.
r := fmt.Sprintf("clipboard: %s", string(data))
g.l.SetLabel(r)
g.app.Send(paint.Event{})
}
}()
}
func (g *GclipApp) OnStart(e lifecycle.Event) {
g.ctx, _ = e.DrawContext.(gl.Context)
g.images = glutil.NewImages(g.ctx)
g.l = NewLabel(g.images)
g.app.Send(paint.Event{})
}
func (g *GclipApp) OnStop() {
g.l.Release()
g.images.Release()
g.ctx = nil
}
func (g *GclipApp) OnSize(size size.Event) {
g.siz = size
}
func (g *GclipApp) OnDraw() {
if g.ctx == nil {
return
}
defer g.app.Send(paint.Event{})
defer g.app.Publish()
g.ctx.ClearColor(0, 0, 0, 1)
g.ctx.Clear(gl.COLOR_BUFFER_BIT)
g.l.Draw(g.siz)
}
func init() {
err := clipboard.Init()
if err != nil {
panic(err)
}
}
func main() {
app.Main(func(a app.App) {
gclip := GclipApp{app: a}
gclip.app.Send(size.Event{WidthPx: 800, HeightPx: 500})
gclip.WatchClipboard()
for e := range gclip.app.Events() {
switch e := gclip.app.Filter(e).(type) {
case lifecycle.Event:
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
gclip.OnStart(e)
case lifecycle.CrossOff:
gclip.OnStop()
os.Exit(0)
}
case size.Event:
gclip.OnSize(e)
case paint.Event:
gclip.OnDraw()
}
}
})
}

View file

@ -1,40 +0,0 @@
# gclip
`gclip` command offers the ability to interact with the system clipboard
from the shell. To install:
```bash
$ go install golang.design/x/clipboard/cmd/gclip@latest
```
```bash
$ gclip
gclip is a command that provides clipboard interaction.
usage: gclip [-copy|-paste] [-f <file>]
options:
-copy
copy data to clipboard
-f string
source or destination to a given file path
-paste
paste data from clipboard
examples:
gclip -paste paste from clipboard and prints the content
gclip -paste -f x.txt paste from clipboard and save as text to x.txt
gclip -paste -f x.png paste from clipboard and save as image to x.png
cat x.txt | gclip -copy copy content from x.txt to clipboard
gclip -copy -f x.txt copy content from x.txt to clipboard
gclip -copy -f x.png copy x.png as image data to clipboard
```
If `-copy` is used, the command will exit when the data is no longer
available from the clipboard. You can always send the command to the
background using a shell `&` operator, for example:
```bash
$ cat x.txt | gclip -copy &
```
## License
MIT | &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

View file

@ -1,131 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
package main // go install golang.design/x/clipboard/cmd/gclip@latest
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"golang.design/x/clipboard"
)
func usage() {
fmt.Fprintf(os.Stderr, `gclip is a command that provides clipboard interaction.
usage: gclip [-copy|-paste] [-f <file>]
options:
`)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, `
examples:
gclip -paste paste from clipboard and prints the content
gclip -paste -f x.txt paste from clipboard and save as text to x.txt
gclip -paste -f x.png paste from clipboard and save as image to x.png
cat x.txt | gclip -copy copy content from x.txt to clipboard
gclip -copy -f x.txt copy content from x.txt to clipboard
gclip -copy -f x.png copy x.png as image data to clipboard
`)
os.Exit(2)
}
var (
in = flag.Bool("copy", false, "copy data to clipboard")
out = flag.Bool("paste", false, "paste data from clipboard")
file = flag.String("f", "", "source or destination to a given file path")
)
func init() {
err := clipboard.Init()
if err != nil {
panic(err)
}
}
func main() {
flag.Usage = usage
flag.Parse()
if *out {
if err := pst(); err != nil {
usage()
}
return
}
if *in {
if err := cpy(); err != nil {
usage()
}
return
}
usage()
}
func cpy() error {
t := clipboard.FmtText
ext := filepath.Ext(*file)
switch ext {
case ".png":
t = clipboard.FmtImage
case ".txt":
fallthrough
default:
t = clipboard.FmtText
}
var (
b []byte
err error
)
if *file != "" {
b, err = os.ReadFile(*file)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read given file: %v", err)
return err
}
} else {
b, err = io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to read from stdin: %v", err)
return err
}
}
// Wait until clipboard content has been changed.
<-clipboard.Write(t, b)
return nil
}
func pst() (err error) {
var b []byte
b = clipboard.Read(clipboard.FmtText)
if b == nil {
b = clipboard.Read(clipboard.FmtImage)
}
if *file != "" && b != nil {
err = os.WriteFile(*file, b, os.ModePerm)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to write data to file %s: %v", *file, err)
}
return err
}
for len(b) > 0 {
n, err := os.Stdout.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}

View file

@ -1,56 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
//go:build cgo
package clipboard_test
import (
"context"
"fmt"
"time"
"golang.design/x/clipboard"
)
func ExampleWrite() {
err := clipboard.Init()
if err != nil {
panic(err)
}
clipboard.Write(clipboard.FmtText, []byte("Hello, 世界"))
// Output:
}
func ExampleRead() {
err := clipboard.Init()
if err != nil {
panic(err)
}
fmt.Println(string(clipboard.Read(clipboard.FmtText)))
// Output:
// Hello, 世界
}
func ExampleWatch() {
err := clipboard.Init()
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
changed := clipboard.Watch(context.Background(), clipboard.FmtText)
go func(ctx context.Context) {
clipboard.Write(clipboard.FmtText, []byte("你好world"))
}(ctx)
fmt.Println(string(<-changed))
// Output:
// 你好world
}

View file

@ -1,14 +0,0 @@
// Copyright 2021 The golang.design Initiative Authors.
// All rights reserved. Use of this source code is governed
// by a MIT license that can be found in the LICENSE file.
//
// Written by Changkun Ou <changkun.de>
package clipboard
// for debugging errors
var (
Debug = debug
ErrUnavailable = errUnavailable
ErrCgoDisabled = errNoCgo
)

View file

@ -1,13 +0,0 @@
module golang.design/x/clipboard
go 1.24
require (
golang.org/x/image v0.28.0
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f
)
require (
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 // indirect
golang.org/x/sys v0.33.0 // indirect
)

View file

@ -1,8 +0,0 @@
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476 h1:Wdx0vgH5Wgsw+lF//LJKmWOJBLWX6nprsMqnf99rYDE=
golang.org/x/exp/shiny v0.0.0-20250606033433-dcc06ee1d476/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View file

@ -1,109 +0,0 @@
#!/bin/bash
# Test script for Linux CGO-free clipboard implementation
echo "Testing Linux clipboard implementation without CGO..."
# Check for required tools
echo "Checking for clipboard tools..."
for tool in xclip xsel wl-copy; do
if command -v $tool &> /dev/null; then
echo "$tool is installed"
else
echo "$tool is not installed"
fi
done
# Create test program
cat > test_linux_clipboard.go << 'EOF'
package main
import (
"fmt"
"log"
"os"
"golang.design/x/clipboard"
)
func main() {
err := clipboard.Init()
if err != nil {
log.Fatal("Failed to initialize clipboard:", err)
}
// Test text
fmt.Println("\n=== Testing Text Clipboard ===")
testText := []byte("Hello from CGO-free Linux clipboard!")
clipboard.Write(clipboard.FmtText, testText)
fmt.Println("Wrote text:", string(testText))
readText := clipboard.Read(clipboard.FmtText)
fmt.Println("Read text:", string(readText))
if string(testText) == string(readText) {
fmt.Println("✓ Text clipboard test passed")
} else {
fmt.Println("✗ Text clipboard test failed")
}
// Test empty write
fmt.Println("\n=== Testing Empty Write ===")
clipboard.Write(clipboard.FmtText, []byte{})
emptyRead := clipboard.Read(clipboard.FmtText)
if emptyRead == nil || len(emptyRead) == 0 {
fmt.Println("✓ Empty write test passed")
} else {
fmt.Println("✗ Empty write test failed, got:", string(emptyRead))
}
// Test image if requested
if len(os.Args) > 1 && os.Args[1] == "image" {
fmt.Println("\n=== Testing Image Clipboard ===")
// Try to read test image
imageData, err := os.ReadFile("tests/testdata/clipboard.png")
if err != nil {
fmt.Println("Could not read test image:", err)
return
}
clipboard.Write(clipboard.FmtImage, imageData)
fmt.Println("Wrote image data, length:", len(imageData))
readImage := clipboard.Read(clipboard.FmtImage)
if readImage != nil {
fmt.Println("Read image data, length:", len(readImage))
if len(imageData) == len(readImage) {
fmt.Println("✓ Image clipboard test passed")
} else {
fmt.Println("✗ Image lengths don't match")
}
} else {
fmt.Println("✗ Failed to read image from clipboard")
}
// Test that reading text from image clipboard returns nil
textFromImage := clipboard.Read(clipboard.FmtText)
if textFromImage == nil {
fmt.Println("✓ Reading text from image clipboard correctly returned nil")
} else {
fmt.Println("✗ Reading text from image clipboard should return nil, got:", string(textFromImage))
}
}
}
EOF
# Run tests with CGO disabled
echo -e "\n=== Running with CGO_ENABLED=0 ==="
CGO_ENABLED=0 go run test_linux_clipboard.go
echo -e "\n=== Running with CGO_ENABLED=0 and image test ==="
CGO_ENABLED=0 go run test_linux_clipboard.go image
# Run actual tests
echo -e "\n=== Running go test with CGO_ENABLED=0 ==="
CGO_ENABLED=0 go test -v -run TestClipboard
# Clean up
rm -f test_linux_clipboard.go
echo -e "\nTest script completed!"

View file

@ -1,15 +0,0 @@
# Copyright 2021 The golang.design Initiative Authors.
# All rights reserved. Use of this source code is governed
# by a MIT license that can be found in the LICENSE file.
#
# Written by Changkun Ou <changkun.de>
all: test
test:
go test -v -count=1 -covermode=atomic ..
test-docker:
docker build -t golang-design/x/clipboard ..
docker run --rm --name cb golang-design/x/clipboard
docker rmi golang-design/x/clipboard

View file

@ -1,11 +0,0 @@
# Copyright 2021 The golang.design Initiative Authors.
# All rights reserved. Use of this source code is governed
# by a MIT license that can be found in the LICENSE file.
#
# Written by Changkun Ou <changkun.de>
# require apt-get install xvfb
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
export DISPLAY=:99.0
go test -v -covermode=atomic ./...

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -13,8 +13,8 @@ import (
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode-sdk-go/option"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/clipboard"
"github.com/sst/opencode/internal/tui"
"golang.design/x/clipboard"
)
var Version = "dev"

View file

@ -17,14 +17,11 @@ require (
github.com/muesli/termenv v0.16.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/sst/opencode-sdk-go v0.1.0-alpha.8
golang.design/x/clipboard v0.7.1
golang.org/x/image v0.28.0
rsc.io/qr v0.2.0
)
replace (
github.com/sst/opencode-sdk-go => ./sdk
golang.design/x/clipboard => ./clipboard
)
replace github.com/sst/opencode-sdk-go => ./sdk
require golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
@ -58,8 +55,6 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
golang.org/x/exp/shiny v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/tools v0.34.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@ -87,7 +82,6 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
golang.org/x/image v0.28.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect

View file

@ -216,12 +216,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/exp/shiny v0.0.0-20250620022241-b7579e27df2b h1:zELBzk+7ERc6m8BxhzU2VYjp03wlEvi+cIgYQR5H3CI=
golang.org/x/exp/shiny v0.0.0-20250620022241-b7579e27df2b/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f h1:/n+PL2HlfqeSiDCuhdBbRNlGS/g2fM4OHufalHaTVG8=
golang.org/x/mobile v0.0.0-20250606033058-a2a15c67f36f/go.mod h1:ESkJ836Z6LpG6mTVAhA48LpfW/8fNR0ifStlH2axyfg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=

View file

@ -12,13 +12,13 @@ import (
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode/internal/clipboard"
"github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/toast"
"github.com/sst/opencode/internal/config"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"golang.design/x/clipboard"
)
type App struct {

View file

@ -53,7 +53,7 @@ clipboard data is changed, use the watcher API:
println(string(data))
}
*/
package clipboard // import "golang.design/x/clipboard"
package clipboard
import (
"context"

View file

@ -4,7 +4,7 @@
//
// Written by Changkun Ou <changkun.de>
//go:build darwin && !ios
//go:build darwin
package clipboard

View file

@ -4,7 +4,7 @@
//
// Written by Changkun Ou <changkun.de>
//go:build linux && !android
//go:build linux
package clipboard

View file

@ -15,13 +15,13 @@ import (
"github.com/google/uuid"
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/clipboard"
"github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/dialog"
"github.com/sst/opencode/internal/components/textarea"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"golang.design/x/clipboard"
)
type EditorComponent interface {

BIN
packages/tui/opencode Executable file

Binary file not shown.